我们说引入spring-boot-starter-web场景启动器后,就会给自动配置web,那到底默认配置了什么呢?
我们以WebMvcAutoConfiguration为例进行讲解
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurerAdapter.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
//主要就是看这个内部静态类,spring-mvc.xml需要配置的内容基本都写在这里面的,具体自己进去看
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
@Override
//静态资源配置
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
//欢迎页配置
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ResourceProperties resourceProperties) {
}
}
//页面图标配置
@Configuration
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
public static class FaviconConfiguration {
}
}
静态资源加载位置按优先级从高到低依次为:classpath:/META-INF/resources/, classpath:/resources/, classpath:/static/, classpath:/public/;只要放在这里面的静态资源,都可以通过http://xxx:xxx/abc.png(前提没有@RequestMapping("abc"));可以通过在spring.resources=xxx,xxx修改静态资源的默认位置
如果在静态资源下创建index.html页面即为欢迎页,创建favicon.ico即为页面图标;关于什么是欢迎页、页面图标就不用我多说了吧!
更多springmvc的自动配置参考官网:https://docs.spring.io/spring-boot/docs/1.5.x/reference/htmlsingle/#boot-features-spring-mvc-auto-configuration
如何修改SpringBoot的默认配置
1)、SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(例如ViewResolver)将用户配置的和自己默认的组合起来;
2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置,例如增加拦截器
<!-- 这是spring-mvc.xml 的写法 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/person.do"/>
<bean class="com.example.demo.DemoInterceptor"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/admin/**" />
<mvc:exclude-mapping path="/admin/login.do"/>
<bean class="com.example.demo.LoginInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
package com.example.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration //这里等价于spring-mvc.xml
public class WebConfig {
/*
* 把DemoInterceptor注册到容器中后,添加拦截器就可以不用new DemoInterceptor()
* registry.addInterceptor(demoInterceptor()).addPathPatterns("/**").excludePathPatterns("/person");
@Bean //这里等价于<bean class="com.example.demo.DemoInterceptor"></bean>
public DemoInterceptor demoInterceptor() {
return new DemoInterceptor();
}
*/
//也可以注册一个WebMvcConfigurerAdapter的bean,该类是抽象类,直接在这里匿名实现
//该类implements WebMvcConfigurer,这就是我们说的xxxConfigurer;我们注册的这个bean会和自动配置的WebMvcConfigurer一起生效,形成互补
//因为WebMvcConfigurer需要实现的方法太多了,所以用了WebMvcConfigurerAdapter
@Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter webMvcConfigurerAdapter = new WebMvcConfigurerAdapter() {
@Override //重写添加拦截器
public void addInterceptors(InterceptorRegistry registry) {
//这里我是new DemoInterceptor(),也可以把DemoInterceptor注册一个bean,就不用new了
registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/**").excludePathPatterns("/person");
//可以添加多个拦截器
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/admin/**").excludePathPatterns("/admin/login");
}
};
return webMvcConfigurerAdapter;
}
}
或者这样配置也是可以的
@Configuration
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
}
全面接管SpringMVC
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
@EnableWebMvc //添加此注解全面接管springmvc配置
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
}
为什么加上@EnableWebMvc自动配置就失效了;
//进入@EnableWebMvc 在进入@Import(DelegatingWebMvcConfiguration.class)
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
回过头来看看自动配置生效条件
//当WebMvcConfigurationSupport不存在时,自动配置才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {
}
建议不要全面接管springmvc,尽量使用自动配置+自己的配置,形成互补
3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置,这里我们以EmbeddedServletContainerCustomizer 为例来修改servlet容器的端口号
@Configuration
public class MainConfig {
@Bean //EmbeddedServletContainerCustomizer是一个接口
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);
}
};
}
}
在application.properties 中配置的server.port=8080 实质也是实现的EmbeddedServletContainerCustomizer,详见org.springframework.boot.autoconfigure.web.ServerProperties
SpringBoot默认的错误处理机制
当我们发生错误时,springboot会默认给我们显示一个空白页或json输出
这是因为ErrorMvcAutoConfiguration为我们做了自动配置
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(ResourceProperties.class)
public class ErrorMvcAutoConfiguration {
//主要看下面几个组件的逻辑
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
}
@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
}
@Bean
public ErrorPageCustomizer errorPageCustomizer() {
}
@Configuration
static class DefaultErrorViewResolverConfiguration {
@Bean
@ConditionalOnBean(DispatcherServlet.class)
@ConditionalOnMissingBean
public DefaultErrorViewResolver conventionErrorViewResolver() {
}
}
}
如何定制错误页面?
我们可以修改server.error.path属性,默认为/error;如果静态资源文件路径或模板引擎路径下午有error/状态码.html(例如404.html,精确匹配优先)或4xx.html(模糊匹配,5xx.html),就会使用对应的html进行输出显示,否则就会使用默认输出
如何定制json输出?
@ControllerAdvice
public class MyExceptionHandler {
@ResponseBody
@ExceptionHandler(UserNotExistException.class)
public Map<String,Object> handleException(Exception e){
Map<String,Object> map = new HashMap<>();
map.put("code","user.notexist");
map.put("message",e.getMessage());
return map;
}
}
如何自适应是该输出页面还是json?
利用springboot的server.error.path=/error以及ErrorMvcAutoConfiguration自动配置原理实现
@ControllerAdvice
public class MyExceptionHandler {
@ExceptionHandler(UserNotExistException.class)
public String handleException(Exception e,HttpServletRequest request){
//设置错误响应码,不设置这句话的话,就不会跳转到500.html或5xx.html,否则仍然是默认页输出
request.setAttribute("javax.servlet.error.status_code",500);
request.setAttribute("customInfo","自定义信息")
return "forward:/error";
}
}
//另外还要给容器中加入我们自己定义的ErrorAttributes,否则仅会输出默认值
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
//request.setAttribute("customInfo","自定义信息")的值
requestAttributes.getAttributes("customInfo",0) //第二个参数是从哪个scope中取,0表示request,具体可以点进方法看
map.put("more","追加更多错误信息"); //还可以在这里追加更多信息
return map;
}
}
但是这种方式有个问题,虽然追加了我自定的内容,但是我不想要timestamp、status、error等这些默认信息该怎么办?
请参考我这篇文章:https://blog.csdn.net/q42368773/article/details/104036753
注册Servlet三大组件【Servlet、Filter、Listener】
//使用xxxRegistrationBean注册三大组件
@Configuration
public class MainConfig {
@Bean
public ServletRegistrationBean myServlet(){
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
}
注意:如果想使用@WebServlet、@WebFilter、@WebListener配置三大组件,则需要将该类加入到容器中,否则不会生效。或者直接在启动类上加入@ServletComponentScan
@WebFilter("/*")
@Component //将组件加入到容器中
public class MyFilter implements Filter {
}
@SpringBootApplication
@ServletComponentScan //或者在启动类中加入该注解
public class DemoApplication {
}
如何使用外置Tomcat容器启动SpringBoot应用程序
<!-- 项目设置为war包 -->
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 重写spring-boot-starter-tomcat的scope属性 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
//创建一个类(名字可以随便取)继承SpringBootServletInitializer
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(DemoApplication.class); //参数为springboot的启动类
}
}
最后使用外置的Tomcat启动程序,此时使用springboot启动类启动无效;如果需要使用jsp,则需要添加jstl的依赖和设置视图解析器
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
spring.mvc.view.prefix=/WEB-INF/
spring.mvc.view.suffix=.jsp
如果只是想使用JSP,不使用外置Tomcat,则可以不用创建ServletInitializer ;但是项目必须为war包,打包后启动命令:java -jar spring-boot-demo.war 运行程序