栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

四、SpringBoot与Web开发

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

四、SpringBoot与Web开发

web开发

一、简介二、SpringBoot对静态资源的映射规则

1、所有/webjarsfavicon.ico 都是在静态资源文件下找; 三、模板引擎

1、引入thymeleaf;2、thymeleaf使用&语法3、语法规则 四、SpringMVC自动配置

1、SpringMVC autoConfiguration2、扩展SpringMVC3、全面接管SpringMVC 五、如何修改springboot的默认配置六、RestfulCRUD

1、默认访问页面2、国际化3、登录4、拦截器实现登录检测5、CRUD员工列表6、CRUD-员工添加7.CRUD员工修改8.CRUD员工删除 七、错误处理机制

1.SpringBoot默认的错误处理机制2.如何定制错误响应:

1. 如何定制错误的页面;2. 如何定制错误的JSON数据;3. 将我们的定制数据携带出去 八、配置嵌入式servlet容器

1.如何定制和修改Servlet容器的相关配置:2.注册Servlet三大组件【Servlet、Filter、Listener】3.替换其他的嵌入式Servlet容器4.嵌入式Servlet自动配置原理5、嵌入式Servlet容器启动原理 九、使用外置的servlet容器

1、原理2、规则3、流程

一、简介

使用SpringBoot:

1.创建SpringBoot应用,选中我们需要的模块;
2.SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来;
3.自己编写业务代码

自动配置原理?
这个场景SpringBoot帮我们配置了什么?能不能修改?能不能扩展?…

XXXAutoConfiguration:帮我们给容器中自动配置组件
XXXProperties:配置类来封装配置文件的内容

二、SpringBoot对静态资源的映射规则
@ConfigurationProperties("spring.web")
public class WebProperties {
	//可以设置和资源有关的参数
}
		@Override
		public void addResourceHandlers(ResourceHandlerRegistry registry) {
			if (!this.resourceProperties.isAddMappings()) {
				logger.debug("Default resource handling disabled");
				return;
			}
			addResourceHandler(registry, "/webjarsfavicon.ico 都是在静态资源文件下找; 
三、模板引擎 

jsp、Velocity、Freemarker、Thymeleaf

SpringBoot推荐的Thymeleaf:语法更简单,功能更强大;

1、引入thymeleaf;
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

    
        1.8
        
        3.0.2.RELEASE
        
        
        2.1.1
    
2、thymeleaf使用&语法
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {

	private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8;

	public static final String DEFAULT_PREFIX = "classpath:/templates/";

	public static final String DEFAULT_SUFFIX = ".html";
}

只要我们吧HTML页面放在classpath:/templates/,thyemleaf就能自动渲染;
使用:

    导入thymeleaf的名称空间

    使用thymeleaf语法;



    
    Title


    

成功!!

这里显示欢迎信息
3、语法规则
    th:text;改变当前元素里面的文本内容;
    th:任意HTML属性;来替换原生属性的值;
序号特征描述属性
1Fragment inclusion片段包含:jsp:includeth:insert ;th:replace
2Fragment iteration遍历: c:forEachth:each
3Conditional evaluation条件判断: c:ifth:if ;th:unless ;th:switch ;th:case
4Local variable definition声明变量: c:setth:object ;th:with
5General attribute modification任意属性修改,支持prepend,appendth:attr ;th:attrprepend ;th:attrappend
6Specific attribute modification修改指定属性默认值th:value ;th:herf ; th:src …
7Text(tag body modification)修改标签内容th:text(转义特殊字符) ;th:utext(不转义特殊字符)
8Franment specification声明片段th:fragment
9Fragment removalth:remove
    表达式

表达式语法:

变量表达式: ${…}

    获取对象的属性、调用方法使用 内置的基本对象

    #ctx : the context object.#vars: the context variables.#locale : the context locale.#request : (only in Web Contexts) the HttpServletRequest object.#response : (only in Web Contexts) the HttpServletResponse object.#session : (only in Web Contexts) the HttpSession object.#servletContext : (only in Web Contexts) the ServletContext object. 内置的工具对象

    #execInfo : information about the template being processed.#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.#uris : methods for escaping parts of URLs/URIs#conversions : methods for executing the configured conversion service (if any).#dates : methods for java.util.Date objects: formatting, component extraction, etc.#calendars : analogous to #dates , but for java.util.Calendar objects.#numbers : methods for formatting numeric objects.#strings : methods for String objects: contains, startsWith, prepending/appending, etc.#objects : methods for objects in general.#bools : methods for boolean evaluation.#arrays : methods for arrays.#lists : methods for lists.#sets : methods for sets.#maps : methods for maps.#aggregates : methods for creating aggregates on arrays or collections.#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).

选择表达式: *{…}:和${}在功能上是一样的;补充:配合 th:object="${session.user}"

Name: Sebastian.

Surname: Pepper.

Nationality: Saturn.

获取国际化内容: #{…}定义URL连接: @{…}

view

片段引用表达式: ~{…}

...
Literals(字面量)

Text literals: ‘one text’ , ‘Another one!’ ,…Number literals: 0 , 34 , 3.0 , 12.3 ,…Boolean literals: true , falseNull literal: nullLiteral tokens: one , sometext , main ,… Text operations(文本操作):

String concatenation: +Literal substitutions: |The name is ${name}| Arithmetic operations(数学运算):

Binary operators: + , - , * , / , %Minus sign (unary operator): - Boolean operations(Bool运算):

Binary operators: and , orBoolean negation (unary operator): ! , not Comparisons and equality(比较运算):

Comparators: > , < , >= , <= ( gt , lt , ge , le )Equality operators: == , != ( eq , ne ) Conditional operators(条件表达式):

If-then: (if) ? (then)If-then-else: (if) ? (then) : (else)Default: (value) ?: (defaultvalue) Special tokens(特殊操作):

No-Operation: _ 四、SpringMVC自动配置 1、SpringMVC autoConfiguration

spring-mvc文档说明

Spring Boot 自动配置好了SpringMVC.

以下是Springboot对SpringMVC的默认配置:

Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象*(View),视图对象决定如何渲染(转发?重定向?))ContentNegotiatingViewResolver :组合所有的视图解析器;如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来;

Support for serving static resources, including support for WebJars (covered later in this document).静态资源文件夹路径和webjars

自动注册了 Converter, GenericConverter, 和 Formatter beans.

Converter:类型转换器Formatter :格式化器(例如时间日期格式化)自己添加的格式化转换器,我们只需要放在容器中即可

Support for HttpMessageConverters (covered later in this document).

HttpMessageConverters :SpringMVC用来转换http请求和响应的;User–>jsonHttpMessageConverters 是从容器中确定;获取所有的HttpMessageConverters ;自己给容器中添加HttpMessageConverter,只需要自己将HttpMessageConverter注册到容器中(@Bean,@Component)

Automatic registration of MessageCodesResolver (covered later in this document).定义错误代码生成规则的

Static index.html support.静态首页访问

Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器中)

初始化WebDataBinder;
请求数据======JavaBean

org.springframework.boot.autoconfigure.web:web的所有自动场景

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc.If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.

2、扩展SpringMVC
    
    
        
            
            
        
    

编写一个配置类(@Configuration),是WebMvcConfigurer 类型;不能标注@EnableWebMvc
既保留了所有的自动配置,也能用我们扩展的配置;

//使用WebMvcConfigurer扩展SpringMVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    
    //浏览器发送/abc请求来到success页面
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/abc").setViewName("success");
    }
}

原理:
1. WebMvcAutoConfiguration是SpringMVC的自动配置类
2. 在做其他自动配置时会导入@import(EnableWebMvcConfiguration.class)

	@Configuration(proxyBeanMethods = false)
	@EnableConfigurationProperties(WebProperties.class)
	public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
	    //从容器中获取所有的WebMvcAutoConfiguration
	    @Autowired(required = false)
   		 public void setConfigurers(List configurers) {
        		if (!CollectionUtils.isEmpty(configurers)) {
           		 this.configurers.addWebMvcConfigurers(configurers);
           		 //一个参考实现;将所有的WebMvcAutoConfiguration相关配置都来一起调用
           		 //public void addViewControllers(ViewControllerRegistry registry) {
        		//Iterator var2 = this.delegates.iterator();
        		//while(var2.hasNext()) {
        		//WebMvcConfigurer delegate = (WebMvcConfigurer)var2.next();
           		// delegate.addViewControllers(registry);
       			// }
       			// }
        	}
    	}
	}
    容器中所有的WebMvcAutoConfiguration都会一起起作用;我们的配置类也会被调用;
    效果:SpringMVC的自动配置和我们的扩展配置都会起作用;
3、全面接管SpringMVC

SpringBoot对springMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都有失效了;
我们需要在配置类中添加@EnableWebMvc即可

//使用WebMvcConfigurer扩展SpringMVC的功能
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {

    //浏览器发送/abc请求来到success页面
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/abc").setViewName("success");
    }
}

原理:
为什么@EnableWebMvc自动配置失效?

    EnableWebMvc 的核心
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@documented
@import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
    @Configuration(
        proxyBeanMethods = false
    )
    public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    
      @Configuration(proxyBeanMethods = false)
      @ConditionalOnWebApplication(type = Type.SERVLET)
      @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
      //容器中没有这个组件的时候,这个自动配置类才生效
      @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
      @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
      @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
      		ValidationAutoConfiguration.class })
      public class WebMvcAutoConfiguration {}
      
        @EnableWebMvc将WebMvcConfigurationSupport组件导入进来;导入WebMvcConfigurationSupport只是SpringMVC最基本的功能;
      五、如何修改springboot的默认配置

      模式:
      1. Springboot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean,@Component),如果有就用用户配置的,如果没有才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置和自己默认的结合起来;
      2. 在springboot中会有非常多的XXXConfigurer帮助我们进行扩展配置;
      3. 在SpringBoot中会有很多WebServerFactoryCustomizer帮助我们进行定制配置;

      六、RestfulCRUD 1、默认访问页面
          //在templates下面寻找index.html
      //    @RequestMapping({"/","/index"})
      //    public String index(){
      //        return "index";
      //    }
      
          //所有的WebMvcConfigurer都会一起起作用
          @Bean//将组件注册到容器
          public WebMvcConfigurer WebMvcConfigurer(){
              WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer(){
                  @Override
                  public void addViewControllers(ViewControllerRegistry registry) {
                      registry.addViewController("/").setViewName("login");
                      registry.addViewController("/index.html").setViewName("login");
                  }
              };
              return webMvcConfigurer;
          }
      
      2、国际化
        编写国际化配置文件;使用resourceBundleMessageSource管理国际化资源文件;在页面使用fmt:message取出国际化内容;

      步骤:
      1. 编写国际化配置文件,抽取页面需要显示的国际化消息

      2. springboot自动配置好了管理国际化资源文件的组件;

      @Configuration(
          proxyBeanMethods = false
      )
      @ConditionalOnMissingBean(
          name = {"messageSource"},
          search = SearchStrategy.CURRENT
      )
      @AutoConfigureOrder(-2147483648)
      @Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class})
      @EnableConfigurationProperties
      public class MessageSourceAutoConfiguration {
      	//我们的配置文件可以直接放置到类路径下叫message.properties
      	private String basename = "messages";
          @Bean
          public MessageSource messageSource(MessageSourceProperties properties) {
              ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
              if (StringUtils.hasText(properties.getbasename())) {
              //设置国际化资源文件的基础名(去掉语言国家代码的)
                  messageSource.setbasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getbasename())));
              }
      
              if (properties.getEncoding() != null) {
                  messageSource.setDefaultEncoding(properties.getEncoding().name());
              }
      
              messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
              Duration cacheDuration = properties.getCacheDuration();
              if (cacheDuration != null) {
                  messageSource.setCacheMillis(cacheDuration.toMillis());
              }
      
              messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
              messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
              return messageSource;
          }
      }
      
      spring.messages.basename=i18n.login
      
        去页面获取国际化的值
      
      
      	
      		
      		
      		
      		
      		Signin Template for Bootstrap
      		
      		
      		
      		
      	
      	
      		
      	
      
      
      

      效果:根据浏览器语言设置的信息切换了国际化;
      原理:

      国际化Locale(区域信息对象);localeResolver (获取区域信息对象)

      		@Override
      		@Bean
      		@ConditionalOnMissingBean(name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)
      		public LocaleResolver localeResolver() {
      			if (this.webProperties.getLocaleResolver() == WebProperties.LocaleResolver.FIXED) {
      				return new FixedLocaleResolver(this.webProperties.getLocale());
      			}
      			AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
      			localeResolver.setDefaultLocale(this.webProperties.getLocale());
      			return localeResolver;
      		}
      
        点击链接切换国际化
      public class MyLocaleResolver implements LocaleResolver {
          @Override
          public Locale resolveLocale(HttpServletRequest request) {
              String localeString = request.getParameter("l");
              Locale locale = Locale.getDefault();
              if (StringUtils.hasLength(localeString)) {
                  String[] split = localeString.split("_");
                  locale = new Locale(split[0],split[1]);
              }
              return locale;
          }
      
          @Override
          public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
      
          }
      }
          @Bean
          public LocaleResolver localeResolver(){
              return new MyLocaleResolver();
          }
      
      3、登录

      开发期间模板引擎修改以后,要实时生效:

        禁用模板引擎的缓存
        spring.thymeleaf.cache=false;按CTRL+F9重新编译;

      登录错误消息的表示:

        防止表单重复提交:使用重定向;
      	//首先进行视图映射,将/main.html映射到实际的页面dashboard.html
      	registry.addViewController("/main.html").setViewName("dashboard");
      
          @PostMapping("/user/login")
          //@RequestMapping(value = "/user/login",method = RequestMethod.POST)
          public String login(@RequestParam String username,
                              @RequestParam String password,
                              Map map){
              if (StringUtils.hasLength(username) && "12345".equals(password)){
                  //登录成功,防止表单重复提交,重定向到目标页面
                  return "redirect:main.html";
              }else {
                  //登录失败
                  map.put("msg","用户名密码错误");
                  return "login";
              }
          }
      
      4、拦截器实现登录检测
      //验证用户登录的拦截器
      public class LoginHandlerInterceptor implements HandlerInterceptor {
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              final Object loginUser = request.getSession().getAttribute("loginUser");
              if (loginUser == null){
                  //未登录,返回登录页面
                  request.setAttribute("msg","没有权限,请先登录");
                  request.getRequestDispatcher("/index.html").forward(request,response);
                  return false;
              }else {
                  //以登录,放行请求
                  return true;
              }
          }
      }
      
      		 	@Override
                  public void addInterceptors(InterceptorRegistry registry) {
                      
                      registry
                              .addInterceptor(new LoginHandlerInterceptor())
                              .addPathPatterns("/**")
                              .excludePathPatterns("/index.html","/","/user/login");
                  }
      
      5、CRUD员工列表

      实验要求:

        RestfulCRUD:CRUD满足Rest风格;
        URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
      普通CRUD(uri来区分操作)RestfulCRUD
      查询getEmpemp----GET
      添加addEmp?xxemp----POST
      修改updateEmp?id=xxx&xxx=xxxemp/{id}----PUT
      删除deleteEmp?id=1emp/{id}----DELETE
        实验的请求架构:
      请求URI请求方式
      查询所有员工empsGET
      查询某个员工(来到修改页面)emp/1GET
      来到添加页面empGET
      添加员工empPOST
      来到修改页面(查出员工进行信息回显)emp/1GET
      修改员工empPUT
      删除员工emp/1DELETE
        员工列表
        thymeleaf公共页面抽取
      1.抽取公共片段
      
      © 2011 The Good Thymes Virtual Grocery
      2.引入公共片段
      ~{templatename::selector}:模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3.默认效果: insert的功能片段在div标签中

      三种引入功能片段的th属性:
      th:insert:将公共片段整个插入到声明引入的元素中
      th:replace:将声明引入的元素替换为公共片段
      th:include:将被引入的片段的内容包含进这个标签中

      1.公共片段
      
      © 2011 The Good Thymes Virtual Grocery
      2.引入方式
      3.效果
      © 2011 The Good Thymes Virtual Grocery
      © 2011 The Good Thymes Virtual Grocery
      © 2011 The Good Thymes Virtual Grocery
      6、CRUD-员工添加

      提交的数据格式不对:生日、日期;
      2017-12-12;2017/12/12;2017.12.12
      日期格式化:SpringMVC将页面提交的值需要转换为指定的类型;
      2017-12-12-------------->类型转换,格式化;
      默认日期是按照/的方式;

      		@Bean
      		@Override
      		public FormattingConversionService mvcConversionService() {
      			Format format = this.mvcProperties.getFormat();
      			WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
      					.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
      			addFormatters(conversionService);
      			return conversionService;
      		}
      
      spring.mvc.format.date=yyyy-MM-dd HH:mm
      
      7.CRUD员工修改

      8.CRUD员工删除
      1.自定义button属性del_uri,传入删除的uri值
      
      2.按照spring规范定义一个删除的表单,表单中只有一个隐藏属性input设置方法为_method,value=delete
      
      3.在Javascript中编写提交逻辑
      七、错误处理机制 1.SpringBoot默认的错误处理机制

      默认效果:

        浏览器,返回一个默认的错误页面;

        浏览器发送的数据:
        如果是其他客户端访问,默认返回一个JSON数据;
      {
          "timestamp": "2022-03-06T14:32:47.196+00:00",
          "status": 404,
          "error": "Not Found",
          "path": "/dmserver/"
      }
      

      其他客户端发送的数据:

      原理:
      可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;
      给容器中添加了以下组件

        DefaultErrorAttributes:
      //帮我们在页面共享数据
          private Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
              Map errorAttributes = new linkedHashMap();
              errorAttributes.put("timestamp", new Date());
              this.addStatus(errorAttributes, webRequest);
              this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
              this.addPath(errorAttributes, webRequest);
              return errorAttributes;
          }
      
        BasicErrorController:
      @Controller
      @RequestMapping("${server.error.path:${error.path:/error}}")
      public class BasicErrorController extends AbstractErrorController {
      
      	//产生HTML类型的数据,浏览器发送请求来到这个方法处理
      	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
      	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
      		HttpStatus status = getStatus(request);
      		Map model = Collections
      				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
      		response.setStatus(status.value());
      		//去哪个页面作为错误页面;包含页面地址和页面内容
      		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
      		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
      	}
      
      	//产生JSON类型的数据,其他类型客户端发送请求到这个方法处理
      	@RequestMapping
      	public ResponseEntity> error(HttpServletRequest request) {
      		HttpStatus status = getStatus(request);
      		if (status == HttpStatus.NO_CONTENT) {
      			return new ResponseEntity<>(status);
      		}
      		Map body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
      		return new ResponseEntity<>(body, status);
      	}
      }
      
        ErrorPageCustomizer:
      	//系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)
      	@Value("${error.path:/error}")
      	private String path = "/error";
      
        DefaultErrorViewResolver:
      	@Override
      	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map model) {
      		ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
      		if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
      			modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
      		}
      		return modelAndView;
      	}
      
      	private ModelAndView resolve(String viewName, Map model) {
      		//默认SpringBoot可以去找到一个页面? error/404
      		String errorViewName = "error/" + viewName;
      		//模板引擎可以解析这个页面就用模板引擎解析
      		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
      				this.applicationContext);
      		if (provider != null) {
      			//模板引擎可用的情况下返回到errorViewName指定的视图地址
      			return new ModelAndView(errorViewName, model);
      		}
      		//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面  error/404.html
      		return resolveResource(errorViewName, model);
      	}
      

      步骤:
      一旦系统出现4XX或者5XX之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;
      1)响应页面;去哪个页面是由DefaultErrorViewResolver解析

      	protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status,
      			Map model) {
      			//所有的ErrorViewResolver得到ModelAndView视图
      		for (ErrorViewResolver resolver : this.errorViewResolvers) {
      			ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
      			if (modelAndView != null) {
      				return modelAndView;
      			}
      		}
      		return null;
      	}
      
      2.如何定制错误响应: 1. 如何定制错误的页面;
      - **有模板引擎的情况下;error/状态码;**【将错误页面命名为错误状态码.HTML放在模板引擎文件夹里面的error文件夹下】,发生此状态的错误就会来到对应的页面;
      我们可以使用4XX和5XX作为错误页面的文件名称来匹配这种类型的所有错误,精确有限(优先寻找精确的状态码.html);
      页面能获取的信息:
      	timestamp:时间戳
      	status:状态码
      	error:错误提示
      	exception:异常对象
      	message:异常消息
      	errors:JSR303数据校验的错误都在这里
      - 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
      - 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
      
      2. 如何定制错误的JSON数据;
        自定义异常处理&返回定制JSON数据
      //没有自适应效果,浏览器和客户端返回都是JSON
      @ControllerAdvice
      public class MyExceptionHandler {
      
          @ResponseBody
          @ExceptionHandler(UserNotExitsException.class)
          public Map handlerException(Exception e){
              Map map = new HashMap<>();
              map.put("code","user.not.exist");
              map.put("message",e.getMessage());
              return map;
          }
      }
      
        转发到/error进行自适应响应效果处理
          @ExceptionHandler(UserNotExitsException.class)
          public String handlerException(Exception e, HttpServletRequest request){
              Map map = new HashMap<>();
              //传入我们自己的错误状态码,否则就不会进入定制的错误流程的解析页面
              //Integer status = (Integer)this.getAttribute(requestAttributes, "javax.servlet.error.status_code");
              request.setAttribute("javax.servlet.error.status_code",500);
              map.put("code","user.not.exist");
              map.put("message",e.getMessage());
              //转发到/error
              return "forward:/error";
          }
      
      3. 将我们的定制数据携带出去

      出现错误以后,回来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorAttributes)规定的方法);

        完全编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;页面上能用的数据,或者是JSON返回能用的数据都是通过this.errorAttributes.getErrorAttributes得到;容器中的DefaultErrorAttributes.getErrorAttributes();默认进行数据处理的;自定义ErrorAttributes如下:
      @Component
      //在容器中加入我们自己定义的errorAttributes
      public class MyErrorAttributes extends DefaultErrorAttributes {
          @Override
          public Map getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
              final Map attributes = super.getErrorAttributes(webRequest, options);
              attributes.put("company","heym");
              return attributes;
          }
      }
      

      最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容。

      八、配置嵌入式servlet容器

      SpringBoot默认使用tomcat作为servlet容器;

      1.如何定制和修改Servlet容器的相关配置:
        修改和server有关的配置(ServerProperties);
      server.port=8081
      server.servlet.context-path=/crud
      server.tomcat.uri-encoding=utf-8
      
      #通用的Servlet容器配置
      server.xxx
      #tomcat的设置
      server.tomcat.xxx
      
        编写一个WebServerFactoryCustomizer:嵌入式的Servlet容器的定制器;来修改servlet容器的配置;
      	//定制Servlet容器相关的规则
          @Bean
          public WebServerFactoryCustomizer webServerFactoryCustomizer(){
              return factory -> factory.setPort(8083);
          }
      
      2.注册Servlet三大组件【Servlet、Filter、Listener】

      SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件;注册三大组件用以下方式:
      ServletRegistrationBean:

          @Bean
          public ServletRegistrationBean servletRegistrationBean(){
              final ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
              return registrationBean;
          }
      

      FilterRegistrationBean:

          @Bean
          public FilterRegistrationBean filterRegistrationBean(){
              final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
              registrationBean.setFilter(new MyFilter());
              registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
              return registrationBean;
          }
      

      ServletListenerRegistrationBean:

          @Bean
          public ServletListenerRegistrationBean listenerRegistrationBean(){
              return new ServletListenerRegistrationBean<>(new MyListener());
          }
      

      SpringBoot帮我们自动注册SpringMVC的前端控制器:DispatcherServlet:

      		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
      		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
      		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
      				WebMvcProperties webMvcProperties, ObjectProvider multipartConfig) {
      			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
      					webMvcProperties.getServlet().getPath());
      					//默认拦截:/ 所有请求,包括静态资源,但是不包括jsp请求; /*会拦截jsp
      					//可以通过server.servletPath
      			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
      			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
      			multipartConfig.ifAvailable(registration::setMultipartConfig);
      			return registration;
      		}
      
      3.替换其他的嵌入式Servlet容器


      默认支持:
      Tomcat(默认使用)

              
                  org.springframework.boot
                  spring-boot-starter-web
              
      

      Jetty:

       
           org.springframework.boot
           spring-boot-starter-web
           
               
                   spring-boot-starter-tomcat
                   org.springframework.boot
               
           
       
      
       
           spring-boot-starter-jetty
           org.springframework.boot
       
      

      Undertow:

      
          org.springframework.boot
          spring-boot-starter-web
          
              
                  spring-boot-starter-tomcat
                  org.springframework.boot
              
          
      
      
      
          spring-boot-starter-undertow
          org.springframework.boot
      
      
      4.嵌入式Servlet自动配置原理

      EmbeddedWebServerFactoryCustomizerAutoConfiguration:嵌入式容器自动配置:

      @Configuration(proxyBeanMethods = false)
      class ServletWebServerFactoryConfiguration {
      
      	@Configuration(proxyBeanMethods = false)
      	//判断当前是否引入Tomcat依赖
      	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
      	//判断容器中有没有用户自己定义的ServletWebServerFactory;嵌入式的Servlet容器工厂;作用是创建Servlet容器
      	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
      	static class EmbeddedTomcat {
      
      		@Bean
      		TomcatServletWebServerFactory tomcatServletWebServerFactory(
      				ObjectProvider connectorCustomizers,
      				ObjectProvider contextCustomizers,
      				ObjectProvider> protocolHandlerCustomizers) {
      			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
      			factory.getTomcatConnectorCustomizers()
      					.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
      			factory.getTomcatContextCustomizers()
      					.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
      			factory.getTomcatProtocolHandlerCustomizers()
      					.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
      			return factory;
      		}
      
      	}
      }
      
        ServletWebServerFactory(嵌入式Servlet容器工厂);
      @FunctionalInterface
      public interface ServletWebServerFactory {
      	WebServer getWebServer(ServletContextInitializer... initializers);
      }
      


      2. 以TomcatServletWebServerFactory为例

      	@Override
      	public WebServer getWebServer(ServletContextInitializer... initializers) {
      		if (this.disableMBeanRegistry) {
      			Registry.disableRegistry();
      		}
      		//创建一个Tomcat
      		Tomcat tomcat = new Tomcat();
      		//配置Tomcat的基本环境
      		File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
      		tomcat.setbaseDir(baseDir.getAbsolutePath());
      		for (LifecycleListener listener : this.serverLifecycleListeners) {
      			tomcat.getServer().addLifecycleListener(listener);
      		}
      		Connector connector = new Connector(this.protocol);
      		connector.setThrowOnFailure(true);
      		tomcat.getService().addConnector(connector);
      		customizeConnector(connector);
      		tomcat.setConnector(connector);
      		tomcat.getHost().setAutoDeploy(false);
      		configureEngine(tomcat.getEngine());
      		for (Connector additionalConnector : this.additionalTomcatConnectors) {
      			tomcat.getService().addConnector(additionalConnector);
      		}
      		prepareContext(tomcat.getHost(), initializers);
      		//将配置好的tomcat传入进去,返回一个web容器对象,并且启动tomcat服务器
      		return getTomcatWebServer(tomcat);
      	}
      
        我们对嵌入式容器的配置修改时怎么生效的?
        ServerProperties、WebServerFactoryCustomizer
      @Configuration(proxyBeanMethods = false)
      @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
      @ConditionalOnClass(ServletRequest.class)
      @ConditionalOnWebApplication(type = Type.SERVLET)
      @EnableConfigurationProperties(ServerProperties.class)
      @import({ 
      //导入BeanPostProcessorsRegistrar:给容器中导入一些组件
      //导入了WebServerFactoryCustomizerBeanPostProcessor;
      //后置处理器:bean初始化前后(创建完对象,还没有属性赋值)执行初始化工作
      ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
      public class ServletWebServerFactoryAutoConfiguration {
      }
      
        容器中导入了WebServerFactoryCustomizerBeanPostProcessor
      	//初始化之前
      	@Override
      	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      		//如果当前初始化的是一个WebServerFactory类型的组件
      		if (bean instanceof WebServerFactory) {
      			postProcessBeforeInitialization((WebServerFactory) bean);
      		}
      		return bean;
      	}
      	
      	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {		
      		//获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值;
      		//getCustomizers:从容器中获取所有这类型的组件
      		//定制Servlet容器,给容器中添加一个WebServerFactoryCustomizer类型的组件;
      		LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
      				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
      				.invoke((customizer) -> customizer.customize(webServerFactory));
      	}
      

      步骤:

      ServletWebServerFactoryAutoConfiguration类根据导入情况,给容器添加相应的WebServletWebServerFactory【TomcatServletWebServerFactory】;容器中某个组件要创建对象就会惊动后置处理器WebServerFactoryCustomizerBeanPostProcessor;只要是嵌入式的servlet工厂,后置处理器就工作;后置处理器,从容器中获取所有的WebServerFactoryCustomizer,调用定制器的定制方法; 5、嵌入式Servlet容器启动原理

      什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的servlet容器并启动Tomcat?

      获取嵌入式的servlet的容器工厂:

        SpringBoot应用启动运行run方法;refreshContext(context);SpringBoot刷新IOC容器【创建IOC容器并初始化容器】refresh(context);刷新刚才创建好的IOC容器;onRefresh();servlet的IOC容器重写了onRefresh方法;servlet的IOC创建嵌入式的服务器对象;createWebServer;获取web容器工厂;ServletWebServerFactory factory = getWebServerFactory();从IOC容器获取ServletWebServerFactory 组件;TomcatServletWebServerFactory创建对象,后置处理器就获取所有的定制器来先定制Servlet容器的相关配置;使用容器工厂获取嵌入式的servlet容器;this.webServer = factory.getWebServer(getSelfInitializer());嵌入式的servlet容器创建对象并启动servlet容器;
        先启动嵌入式的servlet容器,再将IOC容器下剩下没有创建的对象获取出来;
      九、使用外置的servlet容器

      嵌入式Servlet容器:应用打成可执行的jar

      优点:简单、便捷缺点:默认不支持jsp、优化定制比较复杂(使用定制器【ServerProperties、自定义ConfigurableWebServerFactory】、自己编写嵌入式Servlet容器的创建工厂ServletWebServerFactory );

      外置的Servlet容器:外面安装Tomcat----应用war包的方式打包:
      步骤:

        必须创建一个war项目(利用idea创建目录结构);将嵌入式的tomcat指定为provided
       
           org.springframework.boot
           spring-boot-starter-tomcat
           provided
       
      
        必须编写一个SpringBootServletInitializer 的子类,并调用configure方法
      public class ServletInitializer extends SpringBootServletInitializer {
      
          @Override
          protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
          	//传入SpringBoot应用主程序
              return application.sources(SpringBootWebJspApplication.class);
          }
      
      }
      
        启动服务器即可;
      1、原理

      jar包:执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器;
      war包:启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer 】,启动IOC容器;

      servlet3.0(Spring注解版):
      8.2.4 Shared libraries/ runtimes pluggability;

      2、规则
        服务启动(web应用启动)会创建当前的web应用里面每一个jar包里面ServletContainerInitializer实例;ServletContainerInitializer的实现放在meta-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名;还可以使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类;
      3、流程
        启动Tomcat;orgspringframeworkspring-web5.3.16spring-web-5.3.16.jar!meta-INFservicesjavax.servlet.ServletContainerInitializer,Spring的web模块里面有这个文件:org.springframework.web.SpringServletContainerInitializer;SpringServletContainerInitializer将@HandlesTypes({WebApplicationInitializer.class})标注的所有这个类型的类都传入到onStartup方法的Set>;为这些WebApplicationInitializer类型的类创建实例;每一个WebApplicationInitializer都调用自己的onStartup;相当于我们的SpringBootServletInitializer类会被创建对象,并执行onStartup方法;SpringBootServletInitializer实例执行onStartup的时候会创建createRootApplicationContext容器;
          protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
          	//1.创建SpringApplicationBuilder 
              SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
              builder.main(this.getClass());
              ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
              if (parent != null) {
                  this.logger.info("Root context already created (using as parent).");
                  servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
                  builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
              }
      
              builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
              builder.contextFactory((webApplicationType) -> {
                  return new AnnotationConfigServletWebServerApplicationContext();
              });
              //调用了configure,子类重写了这个方法,将SpringBoot的主程序类传入进来
              builder = this.configure(builder);
              builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
              //使用builder创建一个Spring应用
              SpringApplication application = builder.build();
              if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
                  application.addPrimarySources(Collections.singleton(this.getClass()));
              }
      
              Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
              if (this.registerErrorPageFilter) {
                  application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
              }
      
              application.setRegisterShutdownHook(false);
              //启动spring应用
              return this.run(application);
          }
      
        Spring应用创建并启动IOC容器;
      public ConfigurableApplicationContext run(String... args) {
      		long startTime = System.nanoTime();
      		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
      		ConfigurableApplicationContext context = null;
      		configureHeadlessProperty();
      		SpringApplicationRunListeners listeners = getRunListeners(args);
      		listeners.starting(bootstrapContext, this.mainApplicationClass);
      		try {
      			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      			configureIgnoreBeanInfo(environment);
      			Banner printedBanner = printBanner(environment);
      			context = createApplicationContext();
      			context.setApplicationStartup(this.applicationStartup);
      			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      			//刷新IOC容器
      			refreshContext(context);
      			afterRefresh(context, applicationArguments);
      			Duration timetakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
      			if (this.logStartupInfo) {
      				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timetakenToStartup);
      			}
      			listeners.started(context, timetakenToStartup);
      			callRunners(context, applicationArguments);
      		}
      		catch (Throwable ex) {
      			handleRunFailure(context, ex, listeners);
      			throw new IllegalStateException(ex);
      		}
      		try {
      			Duration timetakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
      			listeners.ready(context, timetakenToReady);
      		}
      		catch (Throwable ex) {
      			handleRunFailure(context, ex, null);
      			throw new IllegalStateException(ex);
      		}
      		return context;
      	}
      

      启动Servlet容器,在启动SpringBoot应用;

      转载请注明:文章转载自 www.mshxw.com
      本文地址:https://www.mshxw.com/it/759670.html
      我们一直用心在做
      关于我们 文章归档 网站地图 联系我们

      版权所有 (c)2021-2022 MSHXW.COM

      ICP备案号:晋ICP备2021003244-6号