SpringBoot
SpringBoot 简化了 Spring 应用开发,约定大于配置,去繁从简,just run 就能创建一个独立的,产品级别的应用。
SpringBoot2 要求 java8 及以上, maven3.3 及以上。(查看方式:java -version,mvn -v)
参考文档:SpringBoot2 核心技术与响应式编程
简介
快速创建独立运行的 Spring 项目以及与主流框架集成
使用嵌入式的 Servlet 容器,应用无需打成 war 包
starters 自动依赖与版本控制
自动配置简化开发,无需配置 XML 开箱即用
运行时应用监控与云计算的天然集成等
微服务简介
架构风格(服务微化)
一个应用应该是一组小型服务,各个服务之间可以通过HTTP的方式进行互通关联
每一个功能元素最终都是一个可独立替换和独立升级的软件单元。
详细请参照:微服务详解中文版
创建项目 1. 官网创建: 官方创建 SpringBoot 项目网址:https://start.spring.io/
将下载下来的项目压缩包解压使用 IDEA 打开,我们就初始化成功了一个 spring boot 项目。
2. 手动创建: 参考地址:Getting Started (spring.io)
使用 IDEA 创建一个普通 maven 项目
在 pom.xml 中导入 SpringBoot 依赖
1 2 3 4 5 6 7 8 9 10 11 12 <parent > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-parent</artifactId > <version > 2.7.2</version > </parent > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > </dependencies >
编写主程序,启动 Spring Boot 应用:
新建 MainApplication 类作为主程序,类上使用 SpringBootApplication
注解表明它是 SpringBoot 的主程序。在类中创建 main 方法,并使用 SpringApplication.run
方法来启动 SpringBoot 项目。
1 2 3 4 5 6 7 8 @SpringBootApplication public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class, args); } }
编写业务:
新建 controller 包,新建 HelloController 类(注意:SpringBoot 主程序与 controller包同级)
1 2 3 4 5 6 7 8 @Controller public class HelloController { @ResponseBody @RequestMapping("/hello") public String hello () { return "hello world" ; } }
运行主程序(MainApplication)的 main 方法启动 spring boot 应用,在浏览器访问 http://localhost:8080/hello 可以看到浏览器中返回了 hello world 的字样。
3. IDEA 创建:
选择初始化 spring 项目:
项目配置:
选择项目依赖(这里选择 spring boot 的 web 模块依赖)
点击 finish 创建项目:
自动配置 依赖管理:
SpringBoot 中我们需要继承一个父项目 spring-boot-starter-parent
正是它来管理我们项目中的依赖,它也有一个父项目 spring-boot-dependencies
此项目中在 properties
内几乎声明了所有开发中常用依赖的所有版本号,这也被称之为 自动版本仲裁机制 。
如需自定义依赖版本号需要在我们项目的 pom.xml 添加 <properties></properties>
配置,在父项目中找到你需要修改依赖的 key 重写配置即可。
SpringBoot 提供以很多 starter, spring-boot-starter-*
* 代表了一个具体的场景。SpringBoot 支持场景 Developing with Spring Boot
SpringBoot 支持自定义 starter 官方建议取名 *-spring-boot-starter
,所以我们看见的此类 starter 一般都是第三方为我们提供的 starter。
所有的 starter 场景都有一个底层依赖:
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > <version > 2.7.2</version > <scope > compile</scope > </dependency >
初探自动配置: 以 web 场景为例:
为我们引入并配置好了 Tomcat:
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <version > 2.7.2</version > <scope > compile</scope > </dependency >
自动配置好了 SpringMVC 常用组件,以及 web 常用功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 @SpringBootApplication public class MainApplication { public static void main (String[] args) { ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args); String[] beanDefinitionNames = run.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); } } }
主程序(@SpringBootApplication)下的包及其子包默认被扫描,无需我们再手动配置包扫描。
1 2 @SpringBootApplication(scanBasePackages = "com.ruoxijun")
@ComponentScan
也能修改包扫描路径,但不支持与 SpringBootApplication 注解在同一类上。通过 @SpringBootApplication
源码可知:
1 2 3 4 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
因此我们可以使用这三个注解来代替 SpringBootApplication 注解,一样可以实现 SpringBoot 功能。
各种配置拥有默认值,且我们配置文件(application.properties)的配置最终都会绑定在某一个类上,这个类对象存储在 IOC 容器中。
所有的自动配置项按需加载,我们引入了那个场景对应场景的自动配置才会开启。在 spring-boot-starter 包中引入了一个 spring-boot-autoconfigure 包 SpringBoot 所有的自动配置功能都在其中。
容器功能: 1. @Configuration: 使用:
使用 @Configuration 配置类注册组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class MyConfig { @Bean public User user () { return new User (); } @Bean("tom") public Pet pet () { return new Pet (); } }
1 2 3 4 5 6 7 ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);MyConfig myConfig = run.getBean("myConfig" , MyConfig.class); User user = run.getBean("user" , User.class); User user1 = myConfig.user(); Pet tom = run.getBean(Pet.class);
@Configuration(proxyBeanMethods = false)
:可以获取到单实例组件(Lite 模式)
Full 模式:
标注有 @Configuration
或 @Configuration(proxyBeanMethods = true)
的类被称为Full模式的配置类。
生成 CGLIB 子类,单例组件之间有依赖关系时使用,方便管理。每次都会在容器中查找是否有此组件(没有时创建并加入容器),效率较低。
Lite 模式:
类上有 @Component
、 @ComponentScan
、 @Import
、 @ImportResource
、 @Configuration(proxyBeanMethods = false)
注解或类上没有任何注解,但是类中存在 @Bean
方法。
运行时不用生成 CGLIB 子类,提高运行性能,降低启动时间,可以作为普通类使用。不方便管理组件之间的依赖关系。
2. @Import:
可以作用在任意组件类的上方导入,值为任意类型组件数组,给容器中自动创建出对应类型的组件,默认组件的名字是全类名。
1 @Import({User.class, Pet.class})
3. @Conditional: @Conditional
条件装配注解,当满足条件时装配组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration @ConditionalOnMissingBean(name = "tom") public class MyConfig { @ConditionalOnBean(name = "tom") @Bean public User user () { return new User (); } @Bean("tom") public Pet pet () { return new Pet (); } }
在主程序中使用 run.containsBean("tom");
检验是否存在某组件。
4. @ImportResource: @ImportResource
允许我们导入 spring 的 xml 配置文件。
1 2 @ImportResource("classpath:beans.xml")
5. 配置绑定: 读取 properties 文件中的内容,并且把它封装到 JavaBean 中。
1. 原生 java 方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 public class getProperties { public static void main (String[] args) throws FileNotFoundException, IOException { Properties pps = new Properties (); pps.load(new FileInputStream ("a.properties" )); Enumeration enum1 = pps.propertyNames(); while (enum1.hasMoreElements()) { String strKey = (String) enum1.nextElement(); String strValue = pps.getProperty(strKey); System.out.println(strKey + "=" + strValue); } } }
2. 类已是组件:
IDEA 中使用 ConfigurationProperties 编辑器可能会提示 Spring Boot Configuration Annotation Processor not configured
未配置 Spring 引导配置注释处理器,引入如下依赖即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <excludes > <exclude > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </exclude > </excludes > </configuration > </plugin > </plugins > </build >
组件类上:
1 2 3 4 5 6 7 @Component @ConfigurationProperties(prefix = "mycar") public class Car { private String name; private int age; }
配置属性:
1 2 mycar.name = MyCar mycar.age = 18
组件中还支持 @Value(${属性})
方式注入
3. 在配置类上开启:
为类绑定前缀
1 2 @ConfigurationProperties(prefix = "mycar") public class Car {}
在配置类声明开启:
1 2 3 4 @Configuration @EnableConfigurationProperties(Car.class) public class MyConfig {}
自动配置原理: @SpringBootApplication
注解有以下 3 个主要注解构成:
1 2 3 4 5 6 7 8 9 10 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan( // 指定扫描包 excludeFilters = {@Filter( type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class} ), @Filter( type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class} )})
1. @SpringBootConfiguration: 主要构成如下:
@SpringBootConfiguration
表示 @SpringBootApplication
标记的类是一个配置类。
2. @EnableAutoConfiguration: @EnableAutoConfiguration
由以下两个注解构成:
1 2 @AutoConfigurationPackage @Import({AutoConfigurationImportSelector.class})
@AutoConfigurationPackage:
自动配置包指定了默认的包规则,查看源码 @Import({Registrar.class})
可以发现它导入了一个 Registrar
组件,而查看这个组件源码可以发现它有一个方法会给容器注入一系列组件:
1 2 3 4 5 6 7 8 9 public void registerBeanDefinitions ( AnnotationMetadata metadata, // 注解原信息(注解标记位置、属性值...) BeanDefinitionRegistry registry) { AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages .PackageImports(metadata)).getPackageNames() .toArray(new String [0 ])); }
由此可知 @AutoConfigurationPackage
作用是利用 Registrar
将 主程序(@SpringBootApplication) 包以及子包内的组件导入容器中。
AutoConfigurationImportSelector.class
查看 AutoConfigurationImportSelector
它会批量给容器导入一些组件:
1 2 3 4 5 1. 利用 getAutoConfigurationEntry(annotationMetadata) 给容器中批量导入组件2. 调用 getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入容器中的配置类3. 利用工厂 SpringFactoriesLoader.loadFactoryNames 最终调用 Map<String, List<String>> loadSpringFactories (ClassLoader classLoader) 方法得到所有配置类4. 加载文件 classLoader.getResources("META-INF/spring.factories" ) 默认扫描项目中所有 META-INF/spring.factories 位置文件核心包 spring-boot-autoconfigure.jar 的 META-INF/spring.factories 文件中配置了项目启动就需要全部加载的不同场景的配置类
3. 修改默认配置:
1 2 3 4 5 6 7 8 9 @Bean @ConditionalOnBean({MultipartResolver.class}) @ConditionalOnMissingBean(name = {"multipartResolver"}) public MultipartResolver multipartResolver (MultipartResolver resolver) { return resolver; }
1 2 3 4 5 6 7 @Bean @ConditionalOnMissingBean public CharacterEncodingFilter characterEncodingFilter () { }
由上可知 SpringBoot 默认会在底层配好所有的组件,但是如果用户自己配置了以用户的优先。因此我们想修改默认配置或定制化配置方法如下:
在自己的配置文件中 @bean
替换需要修改的组件。
通过查看配置类绑定了配置文件的哪些属性,在 properties 中修改配置值。
4. 总结:
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration.class
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。 xxxxProperties.class 和配置文件进行了绑定。
生效的配置类就会给容器中装配很多相应场景的组件,并帮我们配置好组件相应功能。
配置文件 配置属性文档 Common Application Properties (spring.io)
常用配置: 在 resource 文件夹中新建 application.properties
或者 application.yml
配置文件。
1 2 3 4 5 6 7 8 9 server.port =8081 server.servlet.context-path =/bootTest spring.datasource.driver-class-name =com.mysql.cj.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.username =root spring.datasource.password =88888888
properties
转 yml
:在线yaml与properties互转
yml 配置方式:
绑定配置项:
1 2 3 4 5 6 7 8 9 10 11 12 @Component @ConfigurationProperties(prefix = "user") public class User { private String name; private int age; private Car car; private String[] arr; private List<String> list; private Set<String> set; private Map<String, Object> map; }
yml 配置方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 user: name: ruoxi age: ${random.int} car: name: rouxiCar age: 1 arr: - 1 - 2 list: [ 3 , 4 ] set: [ 5 , 6 ] map: a: { a: map } b: { b: map }
启动图标: 在线生成字符图标工具:
http://www.network-science.de/ascii/ http://patorjk.com/software/taag/
推荐 :
Spring Boot banner在线生成工具,制作下载banner.txt,修改替换banner.txt文字实现自定义,个性化启动banner-bootschool.net
我们只需在 resource 文件夹下新建一个 banner.txt 文件,在该文本文件中粘贴你想要的SpringBoot启动时显示的字符图标就好了。
关闭图标:
1 2 3 SpringApplication app = new SpringApplication (MainApplication.class);app.setBannerMode(Banner.Mode.OFF); app.run(args);
热部署:
idea需要的设置:
file->settings->build->Build project automatically 勾选
file->settings -> Advanced Settings -> Allow auto-make to start even if developed application is currently running 勾选 (这是idea2020.2设置的位置其它版本请上网自查)
1. pom.xml 配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > <optional > true</optional > </dependency > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > <configuration > <fork > true</fork > </configuration > </plugin > </plugins > </build >
2. 主配置文件配置: 1 2 3 4 5 6 7 8 9 spring: devtools: restart: enabled: true additional-paths: src/main/java poll-interval: 3000 quiet-period: 1000
热部署快捷键 Ctrl+F9
打包: jar 包: 添加插件将 spring boot 应用打包为可执行 jar 包并运行,pom.xml 中添加如下代码
1 2 3 4 5 6 7 8 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
在打包的 jar 包位置使用 java -jar 项目的jar文件名
命令即可运行。
如不能成功请尝试关闭 cmd 快速编辑模式(右击 cmd 窗口点击属性)
war 包: 将文件打包为war包,在pom.xml中配置:
1 2 <packaging > war</packaging >
因为 SpringBoot 中默认内置 tomcat 服务器,我们需要将它内置的 tomcat 在打包时忽略掉因此在 pom.xml 的 dependencies 配置中添加:
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency >
最后在 SpringBoot 启动类中继承 SpringBootServletInitializer
类重写 configure
方法,之后打包即可:
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication public class MainApplication extends SpringBootServletInitializer { public static void main (String[] args) { SpringApplication.run(MainApplication.class,args); } @Override protected SpringApplicationBuilder configure (SpringApplicationBuilder builder) { return builder.sources(MainApplication.class); } }
Profile: 环境配置:
application.properties
(yaml 同理)表示默认环境配置,SpringBoot 还支持我们开启和配置不同环境的配置文件。
自定义环境配置文件 application-环境名称.yaml
,常用配置环境名称有 dev 开发环境、 test 测试环境、 prod 生产环境。
默认配置文件与环境配置文件同时生效,且同名配置环境配置优先。
在 application.properties
默认配置文件中指定激活某环境配置:
1 spring.profiles.active =dev # 激活并使用 application-dev 中的配置
1 2 3 4 5 spring.profiles.active = production spring.profiles.group.production[0] =proddb spring.profiles.group.production[1] =prodmq
1 2 3 4 5 (1 ) classpath 根路径 (2 ) classpath 根路径下 config 目录 (3 ) jar 包当前目录 (4 ) jar 包当前目录的 config 目录 (5 ) /config 子目录的直接子目录
自定义 starter 原理解析 (https://www.yuque.com/atguigu/springboot/tmvr0e)
web 开发 静态资源: 静态资源使用: 在 SpringBoot 中规定 resources 下的 /META-INF/resources
、 /resources
、 /static
、 /public
(同名文件按此优先级)等文件夹都可以存放静态资源我们只需按规定建立文件夹即可。
在配置文件中指定 resources 文件夹中的哪些文件夹为静态资源文件夹:
1 2 3 4 5 spring: web: resources: static-locations: classpath:/public/,classpath:/static/,classpath:/staticFile
为静态资源设置访问前缀:
1 2 3 spring: mvc: static-path-pattern: /static/**
SpringBoot 中默认静态资源文件夹下的 index.html
为首页(不推荐),直接访问项目根路径将访问到此页面(未设置静态资源访问前缀的情况下)。
设置网页图标只需在静态资源文件夹中添加名为 favicon.ico
的图片即可。
自定义静态资源处理规则: 如我们需要访问指定位置的文件时可以如下配置:
1 2 3 4 5 6 7 8 9 10 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { registry.addResourceHandler("/file/**" ) .addResourceLocations("file:E:\\myfile\\" ); } }
静态资源配置原理: 1. WebMvcAutoConfiguration: SpringBoot 启动时自动加载 xxxAutoConfiguration 类,其中 SpringMvc 功能的自动配置类 WebMvcAutoConfiguration
加载成功且生效:
1 2 3 4 5 6 7 8 9 10 @AutoConfiguration( after = {DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class} ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) public class WebMvcAutoConfiguration { ... }
2. WebMvcAutoConfigurationAdapter: 在 WebMvcAutoConfiguration 有一个配置类 WebMvcAutoConfigurationAdapter
:
1 2 3 4 5 @Configuration( proxyBeanMethods = false ) @Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class}) @EnableConfigurationProperties({WebMvcProperties.class, WebProperties.class}) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer , ServletContextAware {...}
这个类绑定了两个配置属性: WebMvcProperties.class = spring.mvc,WebProperties.class = spring.web
且这个配置类只有一个有参构造器,当配置类只有一个有参构造器时所有的参数都会自动充容器里面找 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public WebMvcAutoConfigurationAdapter ( WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this .resourceProperties = webProperties.getResources(); this .mvcProperties = mvcProperties; this .beanFactory = beanFactory; this .messageConvertersProvider = messageConvertersProvider; this .resourceHandlerRegistrationCustomizer = (WebMvcAutoConfiguration.ResourceHandlerRegistrationCustomizer)resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this .dispatcherServletPath = dispatcherServletPath; this .servletRegistrations = servletRegistrations; this .mvcProperties.checkConfiguration(); }
3. 静态资源处理规则: 在 WebMvcAutoConfigurationAdapter 配置类中有一个 addResourceHandlers
方法,它就是静态资源处理的默认规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void addResourceHandlers (ResourceHandlerRegistry registry) { if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); } else { this .addResourceHandler(registry, "/webjars/**" , "classpath:/META-INF/resources/webjars/" ); this .addResourceHandler(registry, this .mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this .resourceProperties.getStaticLocations()); if (this .servletContext != null ) { ServletContextResource resource = new ServletContextResource (this .servletContext, "/" ); registration.addResourceLocations(new Resource []{resource}); } }); } }
4. 欢迎页处理规则: 在 WebMvcAutoConfiguration 类中有一个 EnableWebMvcConfiguration
配置类:
1 2 @EnableConfigurationProperties({WebProperties.class}) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {...}
其中向容器注册了一个组件 WelcomePageHandlerMapping
:
1 2 3 4 5 6 7 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping (new TemplateAvailabilityProviders (applicationContext), applicationContext, this .getWelcomePage(), this .mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(this .getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(this .getCorsConfigurations()); return welcomePageHandlerMapping; }
WelcomePageHandlerMapping 构造函数中:
1 2 3 4 5 6 7 8 9 10 11 12 WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) { if (welcomePage != null && "/**" .equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage); this .setRootViewName("forward:index.html" ); } else if (this .welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index" ); this .setRootViewName("index" ); } }
5. favicon.ico: 浏览器会默认请求项目下 /favicon.ico
作为标签页图标,并保存在 session 域中。因此在配置文件中设置了静态资源访问前缀,那么 /favicon.ico 就会获取不到相对应的图标了。
表单与 RestFul: 1. 开启方式: 一般浏览器 表单 不能发送 get、post 以外的其它请求(其它请求都被 get 请求代替,),SpringMvc 提供了 HiddenHttpMethodFilter
我们只需为 post 请求添加一个 _method
参数,参数值就是我们想使用的请求方式。
SpringBoot 允许我们在配置中开启这项功能:
1 spring.mvc.hiddenmethod.filter.enabled: true
2. 原理解析: 在 WebMvcAutoConfiguration 中:
1 2 3 4 5 @Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty( prefix = "spring.mvc.hiddenmethod.filter", name = {"enabled"} ) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter () { return new OrderedHiddenHttpMethodFilter (); }
在 HiddenHttpMethodFilter 过滤器中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private String methodParam = "_method" ; public void setMethodParam (String methodParam) { Assert.hasText(methodParam, "'methodParam' must not be empty" ); this .methodParam = methodParam; } protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST" .equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception" ) == null ) { String paramValue = request.getParameter(this .methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HiddenHttpMethodFilter .HttpMethodRequestWrapper(request, method); } } } filterChain.doFilter((ServletRequest)requestToUse, response); }
3. 自定义 HiddenHttpMethodFilter: 参照 WebMvcAutoConfiguration 自定义 HiddenHttpMethodFilter:
1 2 3 4 5 6 7 8 9 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter (); hiddenHttpMethodFilter.setMethodParam("_m" ); return hiddenHttpMethodFilter; } }
表单现在只需添加一个 -m
参数,参数值为 PUT、DELETE、PATCH 其中一个,它就会去访问对应的请求方法了。
请求映射原理: SpringMvc 中通过 DispatcherServlet
做请求分发,SpringBoot 同理也一样。DispatcherServlet 是 Servlet 因此它肯定有 doGet 等方法。
DispatcherServlet 继承关系(idea 中 Ctrl+F12 查看):
1 DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet
FrameworkServlet 中重写了 doGet 等方法(idea 中 Ctrl+H 查看),并且可以发现 doGet 等方法都调用了 processRequest 方法它又调用了 doService 方法处理请求:
1 2 3 4 5 6 7 8 9 10 11 12 protected final void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this .processRequest(request, response); } protected final void processRequest (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { this .doService(request, response); } } protected abstract void doService (HttpServletRequest request, HttpServletResponse response) throws Exception;
DispatcherServlet 中 doService 最终调用 doDispatch
方法派发请求:
1 2 3 4 5 6 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { mappedHandler = this .getHandler(processedRequest); }
在 DispatcherServlet 中有一个 List<HandlerMapping> handlerMappings
参数它存储了许多请求的 映射处理器 HandlerMapping, getHandler
方法中它会遍历这些映射处理器,并获取到能够处理当前请求的 handler:
1 2 3 4 5 6 7 8 9 10 11 12 13 protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { if (this .handlerMappings != null ) { Iterator var2 = this .handlerMappings.iterator(); while (var2.hasNext()) { HandlerMapping mapping = (HandlerMapping)var2.next(); HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null ) { return handler; } } } return null ; }
handlerMappings 中有一个 RequestMappingHandlerMapping
它保存了所有 @RequestMapping 相关和handler 的映射规则。还有 WelcomePageHandlerMapping
它就是欢迎页面 /index.html
的映射处理器,它们都是 WebMvcAutoConfiguration 中帮我们配置好的。
遍历时查询 HandlerMapping 中是否有对应处理请求的 handler,如果当前 HandlerMapping 中没有则继续在下一个 HandlerMapping 中寻找。
同理我们可以自定义映射处理器 HandlerMapping。
自定义 MVC:
SpringBoot 默认是关闭矩阵变量的 @MatrixVariable
,在 WebMvcAutoConfiguration 中 WebMvcAutoConfigurationAdapter 内有一个 configurePathMatch
方法它是来配置路径映射的我们需要修改一下路径路径匹配规则,而这个方法是通过 UrlPathHelper
对路径进行解析此类中有一个 removeSemicolonContent
默认为 true
表示移除 URL 路径分号的内容。
想开启矩阵变量功能,我们需要自定义 MVC SpringBoot 提供了三种方式:
@Configuration + 实现 WebMvcConfigurer
接口(推荐使用):
1 2 3 4 5 6 7 8 9 @Configuration(proxyBeanMethods = false) public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } }
@Bean 注册 WebMvcConfigurer 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void configurePathMatch (PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper (); urlPathHelper.setRemoveSemicolonContent(false ); configurer.setUrlPathHelper(urlPathHelper); } }; } }
@Configuration + @EnableWebMvc
+ 继承 WebMvcConfigurer 全面接管 SpringMvc,慎用所有的规则需要手动重写配置。
@EnableWebMvc 会 @Import({DelegatingWebMvcConfiguration.class})
引入组件 DelegatingWebMvcConfiguration
它继承 WebMvcConfigurationSupport
,而在 WebMvcAutoConfiguration
上表示 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
则存在该类时 WebMvc 的自动配置将不生效。
参数处理原理:
在 Controller 方法中参数会按照类型或注解帮我们自动注入值,它是如何实现的?
1. 注解参数:
在 DispatcherServlet
的 doDispatch
方法中它通过 getHandler
找到能够处理当前请求的 HandlerMapping 从中拿到具体处理该请求的 Handler 它记录了具体的 Controller 方法。
getHandler
之后还会为当前的 Handler 找到 适配器 HandlerAdapter:
1 2 HandlerAdapter ha = this .getHandlerAdapter(mappedHandler.getHandler());
它会遍历适配器集合找到能够处理当前 Handler 的适配器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 private List<HandlerAdapter> handlerAdapters; protected HandlerAdapter getHandlerAdapter (Object handler) throws ServletException { if (this .handlerAdapters != null ) { Iterator var2 = this .handlerAdapters.iterator(); while (var2.hasNext()) { HandlerAdapter adapter = (HandlerAdapter)var2.next(); if (adapter.supports(handler)) { return adapter; } } } throw new ServletException ("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler" ); }
在适配器集合中有一个 RequestMappingHandlerAdapter
它就是主要来处理 @RequestMaping 等方法的适配器。
找到适配器后执行目标方法:
1 2 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
在 RequestMappingHandlerAdapter
通过 invokeHandlerMethod
来执行我们的目标方法(Controller 方法)。
为将执行的方法设置 参数解析器 与 返回值处理器 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ServletInvocableHandlerMethod invocableMethod = this .createInvocableHandlerMethod(handlerMethod);if (this .argumentResolvers != null ) { invocableMethod.setHandlerMethodArgumentResolvers(this .argumentResolvers); } if (this .returnValueHandlers != null ) { invocableMethod.setHandlerMethodReturnValueHandlers(this .returnValueHandlers); } invocableMethod.invokeAndHandle(webRequest, mavContainer, new Object [0 ]);
ServletInvocableHandlerMethod
中 invokeAndHandle
方法内:
1 2 Object returnValue = this .invokeForRequest(webRequest, mavContainer, providedArgs);
invokeForRequest
会利用 InvocableHandlerMethod
的 getMethodArgumentValues
方法中确定每一个参数的具体值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object [parameters.length]; for (int i = 0 ; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this .parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null ) { continue ; } if (!this .resolvers.supportsParameter(parameter)) { throw new IllegalStateException (formatArgumentError(parameter, "No suitable resolver" )); } try { args[i] = this .resolvers.resolveArgument(parameter, mavContainer, request, this .dataBinderFactory); } catch (Exception ex) { if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; }
2. Servlet API 参数: Servlet 参数类型的解析器 ServletRequestMethodArgumentResolver
中:
1 2 3 4 5 6 7 8 9 10 public boolean supportsParameter (MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || pushBuilder != null && pushBuilder.isAssignableFrom(paramType) || Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations() || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType; }
3. 复杂参数: Map、Model、ServletResponse 等类型参数。
以参数 Map 类型为例,在 InvocableHandlerMethod
的 getMethodArgumentValues
方法中:
获取到 Map 类型的参数解析器为 MapMethodProcessor
。
解析参数是利用 ModelAndViewContainer mavContainer
的 mavContainer.getModel()
返回 BindingAwareModelMap
它是 Model 也是 Map 类型,最终参数值封装到它里面。
ModelAndViewContainer
用来保存请求需要的所有数据,视图数据模型数据等等。
Model 类型参数解析器为 ModelMethodProcessor
过程同理一样调用了 mavContainer.getModel()
。
4. 自定义对象参数: 自定义 pojo 参数类型的解析器 ServletModelAttributeMethodProcessor
它继承 ModelAttributeMethodProcessor
。
由之前可知 resolvers.supportsParameter(parameter)
来判断某解析器是否能解析该类型参数:
1 2 3 4 5 public boolean supportsParameter (MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this .annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); }
同列解析参数 resolvers.resolveArgument(...)
,再ModelAttributeMethodProcessor
的 resolveArgument
方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public final Object resolveArgument (MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null ) { mavContainer.setBinding(name, ann.binding()); } Object attribute = null ; BindingResult bindingResult = null ; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } catch (BindException ex) { if (isBindExceptionRequired(parameter)) { throw ex; } if (parameter.getParameterType() == Optional.class) { attribute = Optional.empty(); } else { attribute = ex.getTarget(); } bindingResult = ex.getBindingResult(); } } if (bindingResult == null ) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null ) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException (binder.getBindingResult()); } } if (!parameter.getParameterType().isInstance(attribute)) { attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); } bindingResult = binder.getBindingResult(); } Map<String, Object> bindingResultModel = bindingResult.getModel(); mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); return attribute; }
WebDataBinder
: web 数据绑定器,将请求参数的值绑定到指定的 JavaBean 中,它利用 Container
转换器,将请求数据转换成指定的数据类型再次封装到 JavaBean 中。
GenericConversionService
:在设置每一个值的时候,找它里面的所有 Converter
那个可以将这个数据类型(request带来参数的字符串)转换到指定的类型。
自定义 Converter: Converter 接口 @FunctionalInterface public interface Converter<S, T>
包含 FunctionalInterface 注解,它只能针对 form 表单提交数据有效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Car>() { @Override public Car convert (String source) { if (StringUtils.hasText(source)){ Car car = new Car (); car.setName(source); return car; } return null ; } }); } }; } }
例如发起请求:/setUser?name=userName&car=myCar
与 /setCar?car=111
1 2 3 4 5 6 7 8 @RequestMapping("setUser") public User setUser (User user) { return user; } @RequestMapping("setCar") public Car setUser (@RequestParam("car") Car car) { return car; }
数据响应原理: 1. 响应 JSON:
由上可知 ServletInvocableHandlerMethod
的 invokeAndHandle
中会拿到 Controller 的返回值,之后它还会对返回值做处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public void invokeAndHandle (ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { Object returnValue = this .invokeForRequest(webRequest, mavContainer, providedArgs); this .setResponseStatus(webRequest); if (returnValue == null ) { if (this .isRequestNotModified(webRequest) || this .getResponseStatus() != null || mavContainer.isRequestHandled()) { this .disableContentCachingIfNecessary(webRequest); mavContainer.setRequestHandled(true ); return ; } } else if (StringUtils.hasText(this .getResponseStatusReason())) { mavContainer.setRequestHandled(true ); return ; } try { this .returnValueHandlers.handleReturnValue(returnValue, this .getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception var6) { } }
HandlerMethodReturnValueHandlerComposite
中:
1 2 3 4 5 6 7 8 public void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType); if (handler == null ) { throw new IllegalArgumentException ("Unknown return value type: " + returnType.getParameterType().getName()); } handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); }
返回值处理器接口 HandlerMethodReturnValueHandler
:
1 2 3 4 5 6 7 public interface HandlerMethodReturnValueHandler { boolean supportsReturnType (MethodParameter returnType) ; void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ModelAndView Model View ResponseEntity ResponseBodyEmitter StreamingResponseBody HttpEntity HttpHeaders Callable DeferredResult ListenableFuture CompletionStage WebAsyncTask @ModelAttribute @ResponseBody --> RequestResponseBodyMethodProcessor
RequestResponseBodyMethodProcessor
处理 @ResponseBody
标注的方法的返回值:
RequestResponseBodyMethodProcessor 在 handleReturnValue 调用 writeWithMessageConverters
方法使用消息转换器(MessageConverters)进行写出操作:
内容协商:
Http 协议中规定请求头中有 Accept 属性它告诉服务器,客户端能接收的响应类型是什么。
1 2 Accept: text/html, application/xhtml+xml, application/xml;q=0.9 , image/webp, image/apng, *
客户端能接收的类型 用 MediaType
媒体类型 对象表示,与 服务器支持生成的类型 循环匹配筛选出能够支持类型的 MediaType
。若客户端无法解析服务端返回的内容,即媒体类型未匹配,那么响应 406。
SpringMvc 遍历容器中所有 HttpMessageConverter
消息转换器,找到支持相应类型的转换器:
主要作用:看是否支持将返回值 Class 类型的对象,转化为 MediaType
类型的数据
1 2 3 4 5 6 7 8 9 10 public interface HttpMessageConverter <T> { boolean canRead (Class<?> clazz, @Nullable MediaType mediaType) ; boolean canWrite (Class<?> clazz, @Nullable MediaType mediaType) ; List<MediaType> getSupportedMediaTypes () ; default List<MediaType> getSupportedMediaTypes (Class<?> clazz) { return (canRead(clazz, null ) || canWrite(clazz, null ) ? getSupportedMediaTypes() : Collections.emptyList()); } T read (Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; void write (T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
利用 MappingJackson2HttpMessageConverter
中 write
方法将对象转为 JSON (利用 Jackson)再写出去。
2. 内容协商原理:
判断当前请求头中是否已经有确定的媒体类型 MediaType
,有就使用确定好的媒体类型,没有则向下执行。
获取客户端支持接收的内容类型(默认通过请求头 Accept 字段) acceptableTypes = this.getAcceptableMediaTypes(request)
。
ContentNegotiationManager 内容协商管理器 默认使用基于请求头的策略
HeaderContentNegotiationStrategy 确定客户端可以接收的内容类型
获取服务器支持生成的媒体类型,再与客户端能接收的类型进行比对找到匹配的媒体类型。
遍历循环容器中所有的 HttpMessageConverter
,拿到 所有支持转换 当前返回值 Class 类型的 HttpMessageConverter
。
客户端想要的类型和服务器支持的类型进行循环匹配。
最终使用匹配到的 Converter
进行转换。
3. 开启浏览器参数方式内容协商功能:
开启此配置后服务器不再默认通过请求头的 Accept 字段来获取客户端支持接收的内容类型,而是通过读取请求参数中的 format 值来确定客户端接收的内容类型。
1 2 3 4 spring: mvc: contentnegotiation: favor-parameter: true
请求时添加 format 参数指定接收的数据类型即可
/user?format=json
接收 JSON 类型数据
自定义 HttpMessageConverter:
自定义 HttpMessageConverter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { converters.add(new HttpMessageConverter <User>() { @Override public boolean canRead (Class<?> clazz, MediaType mediaType) { return false ; } @Override public boolean canWrite (Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(User.class); } @Override public List<MediaType> getSupportedMediaTypes () { return MediaType.parseMediaTypes("application/x-user" ); } @Override public User read (Class<? extends User> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null ; } @Override public void write (User user, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String data = user.getName()+ " -> " +user.getAge(); OutputStream body = outputMessage.getBody(); body.write(data.getBytes(StandardCharsets.UTF_8)); } }); } }; }
当标注 @ResponseBody 的方法返回值类型为 User 且媒体类型为我们自定义的 application/x-user 时将使用此自定义 HttpMessageConverter
写出响应数据。
如果还想使用请求中添加 format 参数来指定客户端接收响应数据的类型,还需要在上 WebMvcConfigurer
中重写 configureContentNegotiation
方法(原来的策略全部失效):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override public void configureContentNegotiation (ContentNegotiationConfigurer configurer) { Map<String, MediaType> mediaTypes = new HashMap <>(); mediaTypes.put("json" , MediaType.APPLICATION_JSON); mediaTypes.put("xml" , MediaType.APPLICATION_XML); mediaTypes.put("user" , MediaType.parseMediaType("application/x-user" )); ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy (mediaTypes); configurer.strategies(Arrays.asList(strategy)); }
1 2 3 4 5 6 spring: mvc: contentnegotiation: favor-parameter: true media-types: { user: application/x-user }
这样我们只需在请求中添加 format=user 则表示客户端需要接收的类型为 application/x-user 类型。
视图解析 Thymeleaf 模板:
现代化、服务端 Java 模板引擎
Thymeleaf 官网:https://www.thymeleaf.org/
Thymeleaf 使用与语法
1. 引入 Starter: 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-thymeleaf</artifactId > </dependency >
2. Thymeleaf 使用:
SpringBoot 默认帮我们配置好了 Thymeleaf:
1 2 3 4 5 @AutoConfiguration( after = {WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class} ) @EnableConfigurationProperties({ThymeleafProperties.class}) @ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class}) @Import({ReactiveTemplateEngineConfiguration.class, DefaultTemplateEngineConfiguration.class}) public class ThymeleafAutoConfiguration {...}
默认在 resources/templates/
文件夹内的 .html
页面文件将会被 Thymeleaf 模板引擎解析。
1 2 3 4 5 6 @ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { public static final String DEFAULT_PREFIX = "classpath:/templates/" ; public static final String DEFAULT_SUFFIX = ".html" ; ... }
Thymeleaf 模版页面中的 html 标签上需要声明 Thymeleaf 的命名空间:
1 <html lang ="en" xmlns:th ="http://www.thymeleaf.org" >
视图解析原理:
SpringMvc 处理返回值时,当返回值是一个字符串时会被 ViewNameMethodReturnValueHandler
处理器处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ViewNameMethodReturnValueHandler implements HandlerMethodReturnValueHandler { public boolean supportsReturnType (MethodParameter returnType) { Class<?> paramType = returnType.getParameterType(); return Void.TYPE == paramType || CharSequence.class.isAssignableFrom(paramType); } public void handleReturnValue (@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue instanceof CharSequence) { String viewName = returnValue.toString(); mavContainer.setViewName(viewName); if (this .isRedirectViewName(viewName)) { mavContainer.setRedirectModelScenario(true ); } } else if (returnValue != null ) { throw new UnsupportedOperationException ("Unexpected return type: " + returnType.getParameterType().getName() + " in method: " + returnType.getMethod()); } } protected boolean isRedirectViewName (String viewName) { return viewName.startsWith("redirect:" ) || PatternMatchUtils.simpleMatch(this .redirectPatterns, viewName); } }
Controller 方法的参数是一个 自定义类型对象 时会默认 自动将它存放在 ModelAndViewContainer 中。
任何目标方法执行完成以后都会返回 ModelAndView (数据与视图)。
当你的 ModelAndView 没有设置 viewName 时是给你使用默认的 viewName(值为当前 Controller 方法的请求路径,如 RquestMapping(“login”) 那么视图就是 login)。
由 DispatcherServlet
的 processDispatchResult
方法处理派发结果(处理页面该如何响应)。
它会执行 render(mv, request, response)
渲染页面操作,遍历所有 ViewResolver
找到对应视图解析器得到相应的 View 对象(定义了页面渲染逻辑,如重定向是 RedirectView ),最后调用 View 的 render 方法进行页面渲染工作。
返回值以 forward 开头:InternalResourceView 中 request 原生转发请求
返回值以 redirect 开头:RedirectView 中 request 原生重定向请求
普通字符串:new ThymeleafView()
拦截器 HandlerInterceptor:
拦截器 HandlerInterceptor
接口:
1 2 3 4 5 6 7 8 9 10 public interface HandlerInterceptor { default boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true ; } default void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {} default void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {} }
使用拦截器:
实现 HandlerInterceptor
:
1 2 3 4 5 6 7 8 9 10 11 public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("user" ); if (user != null ) return true ; request.setAttribute("msg" , "请登录" ); request.getRequestDispatcher("/success" ).forward(request, response); return false ; } }
配置拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor ()) .addPathPatterns("/**" ) .excludePathPatterns("/" ,"/login" , "/css/**" ,"/html/**" ,"/images/**" ); } }
拦截器原理:
根据当前请求,找到 mappedHandler 为 HandlerExecutionChain
它拿到了可以处理请求的 Handler 和相关的拦截器。
之后拿到了相关的适配器(HandlerAdapter),最后使用适配器来执行目标方法。但在执行目标方法之前还有一个前置步骤 applyPreHandle
它会来执行我们拦截器的 preHandle
方法:
1 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; }
applyPreHandle
会先 正序 挨个执行拦截器的 preHandle
方法,如果当前拦截器返回 true 这执行下一个。
如果拦截器 preHandle
返回 false 会执行 triggerAfterCompletion
方法,它会 逆序 执行 已经触发了的拦截器 的 afterCompletion
方法,并且 applyPreHandle
返回 false。
因此如果任何一个拦截器返回 false ,则 applyPreHandle
返回 false 将直接 return 不能执行目标方法。
如果成功执行了目标方法,之后还会 倒序 执行所有拦截器的 postHandle
方法:
1 mappedHandler.applyPostHandle(processedRequest, response, mv);
并且注意 前任何步骤报错 都将执行 triggerAfterCompletion
方法。
最后在页面渲完成以后,也会触发 triggerAfterCompletion
方法。
文件上传: 文件上传:
表单设置 method="post"
与 enctype="multipart/form-data"
:
1 2 3 4 <form action="/upload" method="post" enctype="multipart/form-data" > file: <input type="file" name="file" id="file" > <br/> <button type="submit" > 提交 </button> </form>
接收文件:
1 2 3 4 5 6 7 8 9 @PostMapping("upload") public String upload ( @RequestPart("file") MultipartFile file ) throws IOException { if (!file.isEmpty()){ String fileName = file.getOriginalFilename(); File saveFile = new File ("C:\\Users" , UUID.randomUUID() + fileName); file.transferTo(saveFile); } return "success" ; }
@RequestParam
也能接收 MultipartFile 类型,一般接收基本类型和 String。
@RequestPart
一般接收 multipart/form-data 表单数据,适用于复杂的请求域像 JSON,XML。
相关配置(MultipartAutoConfiguration.class):
1 2 3 4 # 上传单个文件大小上限 spring.servlet.multipart.max-file-size=10MB # 整个请求的大小上限 spring.servlet.multipart.max-request-size=100MB
原理解析:
SpringBoot 对文件上传的自动配置封装在了 MultipartAutoConfiguration
中,它自动帮我们配置了 StandardServletMultipartResolver
文件上传解析器。
在 DispatcherServlet
的 doDispatch
中获取 mappedHandler
处理器之前会先解析请求是否是上传文件请求(根据请求类型是否是 multipart/
来判断)。
如果是文件上传请求,文件上传解析器会将 原生请求(request) 封装为 MultipartHttpServletRequest
继续向下执行。
最终参数解析器将请求中的文件内容封装成 MultipartFile
。
错误处理: 默认机制:
默认情况下 SpringBoot 提供 /error
处理所有错误映射,如果是浏览器会响应一个 Whitelabel 的 HTML 视图,其它客户端会生产 JSON 响应。
在任何静态资源文件夹中 error/
目录下以 4xx 或 5xx 开头页面会在发生相应错误时被自动解析并返回。
自定义异常处理:
@ControllerAdvice
+ @ExceptionHandler
处理全局异常:
1 2 3 4 5 6 7 8 @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler({ NullPointerException.class, ArithmeticException.class }) public String handlerException ( Exception e ) { return "" ; } }
@ResponseStatus
自定义异常:
1 2 3 @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "请求错误") public class UserException extends RuntimeException {}
HandlerExceptionResolver
自定义异常解析器:
因为我们自定义异常解析器的组件创建会比它默认的解析器后创建,而一般异常都被默认的解析器处理了轮不到我们自定义的解析器,因此我们需要提升自定义解析器创建的优先级。
1 2 3 4 5 6 7 8 9 10 11 @Order(value = Ordered.HIGHEST_PRECEDENCE) @Component public class CustomerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { return new ModelAndView ("404" ); } }
异常处理原理:
ErrorMvcAutoConfiguration
自动配置了异常处理规则,给容器中存放了一些组件 errorAttributes( DefaultErrorAttributes.class
) 组件。
1 DefaultErrorAttributes implements ErrorAttributes , HandlerExceptionResolver
ErrorAttributes
定义了错误页面中包含的数据。
basicErrorController( BasicErrorController
) 组件,默认处理 /error
请求。
1 2 3 @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController
其中返回 HTML 页面的请求方法会返回 ModelAndView("error", model)
,通过 BeanNameViewResolver
视图解析器,按照 视图名作为组件的 ID 去容器中寻找 ID 为 error 的 View 组件 。(还有一个返回 JSON 的请求方法)
conventionErrorViewResolver( DefaultErrorViewResolver
)组件,发生错误它会以 Http 状态码 作为视图名(404、4XX、5XX)。
异常处理流程:
doDispatch 中执行目标方法期间发生任何异常都被 catch 捕获并将异常对象保存到 Object dispatchException
中。
在执行 processDispatchResult 视图解析时将处理异常,由 processHandlerException
方法处理 handler 发生的异常并返回 ModelAndView。它会遍历所有的 HandlerExceptionResolver
处理器异常解析器看谁能够处理当前异常。
默认是没有能够处理异常的解析器其中 DefaultErrorAttributes
只会将错误信息保存到请求域中,其它的解析器只在特定的情况发挥作用(如有 @ExceptionHandler 注解等)。
如果没有任何解析器能够处理当前异常最终将发送 /error
请求,并被 BasicErrorController
中的方法处理。它将遍历所有的 ErrorViewResolver 错误视图解析器其中 DefaultErrorViewResolver
将返回错误页面视图。
web 原生组件: 注意原生组件不会触发 SpringBoot 的拦截器等功能。
@ServletComponentScan:
在主类中使用 @ServletComponentScan
指定 servlet、Filter、Listener 组件存放的包位置:
默认将扫描并注入此注解所在包以及所有子类包中含有 @WebServlet
、 @WebFilter
、 @WebListener
注解的原生组件,basePackages 属性指定扫描的包位置。
1 2 3 4 @ServletComponentScan(basePackages = "top.ruoxijun.servlet") @SpringBootApplication public class MainApplication {...
在 @ServletComponentScan
注解能扫描的包内创建原生 servlet 即可:
1 2 3 4 5 6 7 @WebServlet(urlPatterns = "/my") public class MyServlet extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("this is MyServlet.class" ); } }
同理 Filter :
1 2 3 4 5 6 7 8 9 10 @WebFilter(urlPatterns = "/css/*") public class MyFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException {} @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {} @Override public void destroy () { Filter.super .destroy(); } }
Listener:
1 2 3 4 5 6 7 @WebListener public class MyListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) {} @Override public void contextDestroyed (ServletContextEvent sce) {} }
xxxRegistrationBean: 推荐使用在配置类中向 SpringBoot 注入 ServletRegistrationBean
、 FilterRegistrationBean
、 ServletListenerRegistrationBean
类型组件的方式来分别添加 servlet、Filter、Listener:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Configuration public class MyRegistrationBean { @Bean public ServletRegistrationBean myServlet () { MyServlet myServlet = new MyServlet (); return new ServletRegistrationBean (myServlet, "/my" ); } @Bean public FilterRegistrationBean myFilter () { MyFilter myFilter = new MyFilter (); FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean <>(myFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/css/*" )); return filterRegistrationBean; } @Bean public ServletListenerRegistrationBean myListener () { MyListener myListener = new MyListener (); return new ServletListenerRegistrationBean (myListener); } }
DispatcherServlet 实现原理:
在 DispatcherServletAutoConfiguration
中给容器配置并注入了 dispatcherServlet
组件,且属性绑定在 WebMvcProperties
中对应的配置项为 spring.mvc
。
其中另一个配置类中给容器配置注入了 DispatcherServletRegistrationBean
组件,它继承至 ServletRegistrationBean<DispatcherServlet>
,且配置器映射路径为 /
(对应配置: spring.mvc.servlet.path
)。
context-path 配置项目上下文访问的前缀,path 配置的是 dispatcherServlet 拦截的路径。
多个 servlet 处理同一层路径采用精确优先原则(如有 /my/
与 /my/2
两个 servlet ,/my/1将进入第1个)。
由于 SpringBoot 的 web 都基于 dispatcherServlet 的 /
实现,因此我们自定义注入的原生 web 组件是直接执行,不会通过 SpringBoot 的相关功能(如拦截器)。
嵌入式 Servlet 容器: 原理:
在 spring-boot-starter-web
包中默认引入 Tomcat 的包。
配置类 ServletWebServerFactoryAutoConfiguration
它导入了 ServletWebServerFactoryConfiguration
组件其中它会根据添加动态判断系统导入了那个 web 服务器的包,注入相应的服务器工厂组件( TomcatServletWebServerFactory
、 JettyServletWebServerFactory
、 UndertowServletWebServerFactory
)。
ServletWebServerApplicationContext
它会在容器启动时寻找 ServletWebServerFactory
(servlet web 服务器工厂),利用服务器工厂创建除服务器并启动。
切换或去除嵌入式服务器:
在 pom.xml 中引入 web 包时排除 Tomcat 依赖的包:
1 2 3 4 5 6 7 8 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > <exclusions > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > </exclusions > </dependency >
引入你需要的服务器包,SpringBoot 已配置好版本号不需要填写版本号直接引入即可。
数据校验
1 2 3 4 5 6 7 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > <version > 2.5.4</version > </dependency >
1 2 3 4 5 @Validated public class Person { @Email(message="邮箱格式错误") private String name; }
注解
验证的数据类型
说明
@AssertFalse
Boolean,boolean
值必须是false
@AssertTrue
Boolean,boolean
值必须是true
@NotNull
任意类型
值不能是null
@Null
任意类型
值必须是null
@Min
BigDecimal、BigInteger、byte、short、int、long、double 以及任何Number或CharSequence子类型
大于等于@Min指定的值
@Max
同上
小于等于@Max指定的值
@DecimalMin
同上
大于等于@DecimalMin指定的值(超高精度)
@DecimalMax
同上
小于等于@DecimalMax指定的值(超高精度)
@Digits
同上
限制整数位数和小数位数上限
@Size
字符串、Collection、Map、数组等
长度在指定区间之内,如字符串长度、集合大小等
@Past
如 java.util.Date, java.util.Calendar 等日期类型
值必须比当前时间早
@Future
同上
值必须比当前时间晚
@NotBlank
CharSequence及其子类
值不为空,在比较时会去除字符串的首位空格
@Length
CharSequence及其子类
字符串长度在指定区间内
@NotEmpty
CharSequence及其子类、Collection、Map、数组
值不为null且长度不为空(字符串长度不为0,集合大小不为0)
@Range
BigDecimal、BigInteger、CharSequence、byte、short、int、long 以及原子类型和包装类型
值在指定区间内
@Email
CharSequence及其子类
值必须是邮件格式
@Pattern
CharSequence及其子类
值需要与指定的正则表达式匹配
@Valid
任何非原子类型
用于验证对象属性
Swagger
Swagger官网:https://swagger.io/
RestFul API 文档在线生成工具
可直接运行,支持在线测试
1. swagger 2 版:
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger2</artifactId > <version > 2.9.2</version > </dependency > <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-swagger-ui</artifactId > <version > 2.9.2</version > </dependency >
请产考下方swagger3 配置 ,swagger2 启动依赖为 @EnableSwagger2
,文档类型为DocumentationType.SWAGGER_2
其它使用无太大变化。
浏览器访问地址为: 项目地址/swagger-ui.html
2. swagger 3 版:
1 2 3 4 5 6 <dependency > <groupId > io.springfox</groupId > <artifactId > springfox-boot-starter</artifactId > <version > 3.0.0</version > </dependency >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @Configuration @EnableOpenApi public class SwaggerConfig { @Bean public Docket docket () { return new Docket (DocumentationType.OAS_30) .apiInfo(apiInfo()) .enable(true ) .groupName("分组1" ) .select() .apis(RequestHandlerSelectors.basePackage("ruoxijun.cn" )) .paths(PathSelectors.ant("/find**" )) .build(); } private ApiInfo apiInfo () { Contact contact = new Contact ( "ruoxijun" , "http://ruoxijun.cn/" , "1460662245@qq.com" ); return new ApiInfo ( "项目 Api 标题" , "项目 Api 描述" , "1.0" , "http://ruoxijun.cn/" , contact, "Apache 2.0" , "http://www.apache.org/licenses/LICENSE-2.0" , new ArrayList ()); } @Bean public Docket docket2 () { return new Docket (DocumentationType.SWAGGER_2) .groupName("分组2" ); } }
浏览器访问地址为: 项目地址/swagger-ui/index.html
3.常用注解:
conllection 类常用注解:
1 2 3 4 5 6 7 8 9 10 11 12 @Api(tags = "conllection 类说明") @ApiOperation(value = "请求方法作用",notes = "请求方法备注说明") @ApiImplicitParams({ // 方法参数集,required 表示参数是否必须 @ApiImplicitParam(name = "参数名",value = "参数描述",required = true), @ApiImplicitParam(name = "v",value = "value值",required = true) }) @ApiResponses({ // 方法响应 code 描述,response 抛出的异常类 @ApiResponse(code = 200,message = "返回数据成功"), @ApiResponse(code = 400,message = "返回数据失败",response = ParamsException.class) })
实体类常用注解:
1 2 @ApiModel("用户实体类") @ApiModelProperty("用户id")
跨域 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/**" ) .allowedOriginPatterns("*" ) .allowCredentials(true ) .allowedMethods("GET" , "POST" , "DELETE" , "PUT" ) .allowedHeaders("*" ) .maxAge(3600 ); } }
数据库开发 JDBC: 1. 添加依赖: 1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency >
在 spring-boot-starter-jdbc 中引入了 HikariDataSource 数据源(数据库连接池)、jdbc、spring 事务包,因此我们需要操作什么数据库导入相应驱动即可。
SpringBoot 默认数据库驱动版本一般与本机数据库版本不一致,建议修改版本配置与本机一致:
1 2 3 <properties > <mysql.version > 8.0.20</mysql.version > </properties >
2. 数据库配置: 自动配置分析:
DataSourceAutoConfiguration
数据源自动配置
数据源相关属性都与 DataSourceProperties
绑定,对应配置 spring.datasource
的相关属性。
数据库连接池默认的配置在我们没有配置 DataSource
时才会自动配置,且默认配置使用 HikariDataSource
连接池。
DataSourceTransactionManagerAutoConfiguration
事务管理器自动配置
数据库相关配置: 1 2 3 4 5 6 7 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_crud?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 88888888
3. 测试使用: 1 2 3 4 5 6 7 8 9 10 11 @Slf4j @SpringBootTest public class SpringBoot01Test { @Autowired JdbcTemplate jdbcTemplate; @Test public void dataSourceTest () { List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from book" ); log.info("查询结果 {} " , maps); } }
Druid:
项目官方地址:https://github.com/alibaba/druid
官方介绍:Druid 是 Java 语言中最好的数据库连接池,Druid 能够提供强大的监控和扩展功能。
1. 使用 druid: 1. 引入依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.14</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency >
2. 配置 druid: 1 2 3 4 5 6 7 8 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot-vue-manage?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 88888888 filters: stat,wall
要使用 druid 数据源,由前可知需要注入 druid 的 DataSource
来使默认的数据源失效(也支持配置文件中 spring.dataSource.type
属性配置数据源)。
想要开启 druid 监控与统计数据的页面需要配置 StatViewServlet
。
页面开启后 DruidDataSource
中配置 filters
属性开启各类监控,值 stat 开启 SQL 监控,值 wall 开启 SQL 防火墙。
配置 WebStatFilter
可开启 web 监控,并设置统计和需要排除的请求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Configuration public class DruidConfiguration { @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource () { DruidDataSource dataSource = new DruidDataSource (); return dataSource; } @Bean public ServletRegistrationBean statViewServlet () { StatViewServlet viewServlet = new StatViewServlet (); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean <>(viewServlet, "/druid/*" ); registrationBean.addInitParameter("loginUsername" , "admin" ); registrationBean.addInitParameter("loginPassword" , "admin" ); return registrationBean; } @Bean public FilterRegistrationBean webStatFilter () { WebStatFilter webStatFilter = new WebStatFilter (); FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean <>(webStatFilter); registrationBean.setUrlPatterns(Arrays.asList("/*" )); registrationBean.addInitParameter("exclusions" , "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" ); return registrationBean; } }
在浏览器中访问 项目地址/druid 或 项目地址/druid/login.html 即可查看监控页面。
2. druid starter: 1. 引入 druid 的 starter: 1 2 3 4 5 6 <!-- https: <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2 .14 </version> </dependency>
除了引入 druid 包手动配置以外,druid 提供了 druid-spring-boot-starter 它引入了 druid、slf4j、以及一个自动配置 druid 的包,其中设置了大量默认配置项,也可以在配置文件中自定义配置值。
2. druid starter 配置: druid/druid-spring-boot-starter 使用方法与详细配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot-vue-manage?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 88888888 druid: stat-view-servlet: enabled: true login-username: admin login-password: admin web-stat-filter: enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filters: stat, wall aop-patterns: top.ruoxijun.bean.*
Mybatis: 1. 引入依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.2.2</version > </dependency > <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper-spring-boot-starter</artifactId > <version > 1.4.5</version > </dependency >
mybatis-spring-boot-starter 中已经引入 jdbc starter 场景。
2. 添加配置: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 88888888 mybatis: mapper-locations: classpath:mybatis/mapper/*.xml configuration: map-underscore-to-camel-case: true type-aliases-package: top.ruoxijun.entity pagehelper: helper-dialect: mysql
3. 基础使用:
创建 mapper 接口:
1 2 @Mapper public interface UserMapper { List<User> findAllUser () ; }
每一个 mapper 类都要加一个 @Mapper
注解表明它是一个 mapper 接口,也可以选择在启动类中利用 @MapperScan("top.ruoxijun.mapper")
直接扫描整个 mapper 文件夹。
1 2 3 4 5 @SpringBootApplication @MapperScan("top.ruoxijun.mapper") public class MainApplication { public static void main (String[] args) { SpringApplication.run(MainApplication.class,args); } }
使用扫描可能出现在使用 @Autowired
注解自动装配 mapper 对象时在 idea 编辑器中报红显示找不到对象,但是放心运行时是没有问题的。可以不理也可用以下方法解决:
在 resources 文件夹下新建 mapper-locations 配置中的文件夹,然后建立 mapper 接口的对应 mapper.xml 即可。
1 2 3 4 5 6 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="对应的 Mapper 文件位置" > </mapper >
Mybatis Plus: 1. 引入依赖: MyBatis-Plus 官网地址 (baomidou.com)
1 2 3 4 5 6 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.5.2</version > </dependency >
它已经引入了 mybatis 与 jdbc 等场景。
配置与 MybatisPlusProperties
绑定( mybatis-plus
)。
配置 mapper.xml 文件位置属性 mapperLocations
( mybatis-plus.mapper-locations
) 默认值 classpath*:/mapper/**/*.xml
表示任意工程路径下所有 mapper 文件夹的下 xml 都是 mapper.xml (即不仅是当前项目 classpath 还包括 jar 包该路径下)。
2. 简单实例:
在 MainApplication
类上使用 @MapperScan("top.ruoxijun.mapper")
指定扫描的 mapper 接口包(也可使用 @Mapper 注解方式)。
编写 mapper 接口并继承 BaseMapper<User>
泛型是你要操作的表对应的基类:
1 public interface UserMapper extends BaseMapper <User> {}
实现 Service 业务类:
1 2 3 4 5 6 7 public interface UserService extends IService <User> { }@Service public class UserServiceImpl extends ServiceImpl <UserMapper, User> implements UserService { }
实现 Controller 请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestController public class UserController { @Autowired UserService userService; @RequestMapping({"user"}) public Map<String, Object> user ( @RequestParam(value = "pN", defaultValue = "1") Integer pageNum ) { HashMap<String, Object> map = new HashMap <>(); List<User> users = userService.list(); Page<User> userPage = new Page <>(pageNum, 2 ); Page<User> page = userService.page(userPage); map.put("users" , users); map.put("page" , page); return map; } }
分页查询还需要使用分页插件(其它插件使用方式同理):
1 2 3 4 5 6 7 8 9 10 11 @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor (); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } }
Redis:
引入 Redis 场景:
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency > <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > </dependency >
默认引入了 Lettuce 操作 Redis,使用 jedis 需要我们手动引入并且在 client-type
配置项中申明想使用的客户端。
Redis 配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 spring: data: redis: host: 192.168 .0 .166 port: 6379 database: 0 username: password: 123456 connect-timeout: 10s lettuce: pool: max-active: 8 max-wait: 200s max-idle: 8 min-idle: 0
事务控制
SpringBoot 对事务提供了实现并自动配置我们只需在需要添加事务的方法上添加如下注释即可:
1 @Transactional(propagation = Propagation.REQUIRED)
单元测试 简介与使用: 1. 引入依赖: 1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
2. Junit5: SpringBoot 2.2.0 版本开始引入 JUnit5 作为单元测试默认库,由三个不同子项目的几个不同模块组成JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
。
JUnit Platform : Junit Platform 是在 JVM 上启动测试框架的基础,不仅支持 Junit 自制的测试引擎,其他测试引擎也都可以接入。
JUnit Jupiter : JUnit Jupiter 提供了 JUnit5 的新的编程模型,是 JUnit5 新特性的核心。内部包含了一个测试引擎 ,用于在 Junit Platform 上运行。
JUnit Vintage : 由于 JUint 已经发展多年,为了照顾老的项目,JUnit Vintage 提供了兼容JUnit4.x,Junit3.x 的测试引擎。
SpringBoot2.4 以上版本移除了默认对 JUnit Vintage 的依赖,如果需要兼容 junit4 需要自行引入:
1 2 3 4 5 6 7 8 9 10 11 12 <dependency > <groupId > org.junit.vintage</groupId > <artifactId > junit-vintage-engine</artifactId > <scope > test</scope > <exclusions > <exclusion > <groupId > org.hamcrest</groupId > <artifactId > hamcrest-core</artifactId > </exclusion > </exclusions > </dependency >
3. 使用方式:
分别使用 @SpringBootTest
与 @Test
(org.junit.jupiter .api.Test)注解标记测试类与测试方法:
1 2 3 4 5 @SpringBootTest public class SpringBootTest { @Test public void test () { } }
被 @SpringBootTest
标记的类支持 @Autowired
、 @Transactional
(标注的测试方法,测试完成后自动回滚) 等 SpringBoot 注解。
Junit5 常用注解: JUnit 5 官方文档 - 常用注解
@ParameterizedTest : 表示方法是参数化测试
@RepeatedTest : 表示方法可重复执行
@DisplayName : 为测试类或者测试方法设置展示名称
@BeforeEach : 表示方法在每个单元测试之前执行, @AfterEach
(之后)同理
@BeforeAll : 表示 静态方法 在所有单元测试之前执行, @AfterAll
同理
@Tag : 表示单元测试类别,类似于 JUnit4 中的 @Categories
@Disabled : 表示测试类或测试方法不执行,类似于 JUnit4 中的 @Ignore
@Timeout : 表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith : 为测试类或测试方法提供扩展类引用
断言机制: 1. 简单断言:
测试方法中前面的断言失败则后面的代码不会执行。
断言方法一般都是 Assertions
中的静态方法,简单使用方式:
1 2 3 4 5 6 7 8 9 10 import static org.junit.jupiter.api.Assertions.assertEquals; @Test void simple () { assertEquals(1 , 1 , "值不相等" ); Object o = new Object (); assertSame(o, o); }
方法
说明
assertEquals
两个对象值或两个原始类型值是否相等
assertNotEquals
两个对象或两个原始类型是否不相等
assertSame
两个对象引用是否指向同一个对象
assertNotSame
两个对象引用是否指向不同的对象
assertTrue
给定的布尔值是否为 true
assertFalse
给定的布尔值是否为 false
assertNull
给定的对象引用是否为 null
assertNotNull
给定的对象引用是否不为 null
assertArrayEquals
两个对象或原始类型的数组是否相等
2. 组合断言:
有需要多个断言全部满足条件时可使用 assertAll
:
1 2 3 4 5 6 @Test void assertAllTest () { assertAll("组合断言" , ()-> assertTrue(true ), ()-> assertNotEquals(1 , 2 )); }
3. 异常断言:
业务逻辑在某情况下一定出现异常时使用 assertThrows
:
1 2 3 4 5 6 @Test void assertThrowsTest () { assertThrows(ArithmeticException.class, ()-> { int i = 1 / 0 ; }, "异常成功触发" ); }
4. 超时断言: 1 2 3 4 5 @Test public void timeoutTest () { Assertions.assertTimeout(Duration.ofMillis(1000 ), () -> Thread.sleep(500 )); }
5. 快速失败: 1 2 3 4 @Test public void shouldFail () { fail("This should fail" ); }
前置条件: assumptions(假设)当不满足某前置条件时测试将不会继续执行但不会报错:
1 2 3 4 5 6 @Test void assumeTest () { assumeTrue(true , "不是 true" ); assumingThat(true , ()-> System.out.println("是 true" )); }
嵌套测试: 在 Java 内部类上标注 @Nested
注解可实现嵌套测试(可无限嵌套),且内部单元测试会激活外部的 @BeforeEach
等(BeforeAll、After…)方法(反之不行):
1 2 3 4 5 6 7 class JunitTest { @Nested class NestedTest { } }
参数化测试:
@ValueSource
: 为参数化测试指定入参来源,支持八大基础类以及 String 类型, Class 类型
@NullSource
: 表示为参数化测试提供一个 null 的入参
@EnumSource
: 表示为参数化测试提供一个枚举入参
@CsvFileSource
:表示读取指定 CSV 文件内容作为参数化测试入参
@MethodSource
:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)
使用 @ParameterizedTest
注解标注方法表示这是一个参数化测试的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ParameterizedTest @ValueSource(strings = {"1", "2"}) void valueSourceTest (String s) { assertNotNull(s); } @ParameterizedTest @MethodSource("method") public void testWithExplicitLocalMethodSource (String s) { assertNotNull(s); } static Stream<String> method () { return Stream.of("1" , "2" ); }
指标监控 SpringBoot Actuator:
依赖导入:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-actuator</artifactId > </dependency >
访问路径:
localhost:8080/actuator/ 在此路径下还可以访问更详细的监控端点 /actuator/**
,默认 JMX 暴露所有端点而 web(http) 只默认暴露 health (/actuator/health) 和 info 端点。
配置:
常用监控端点(Endpoint):Health 监控状况、Metrics 运行时指标、Loggers 日志记录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 management: endpoints: enabled-by-default: false web: exposure: include: '*' endpoint: health: enabled: true show-details: always metrics: enabled: true loggers: enabled: true
定制 Endpoint (Health、Metrics、Info、Endpoint 等)信息略。
可视化:spring-boot-admin:https://github.com/codecentric/spring-boot-admin
启动过程 启动原理:
在主程序中 SpringApplication.run(MainApplication.class, args)
方法创建了一个 SpringApplication
对象:
保存信息,判断当前应用类型。
找到所有的 bootstrapRegistryInitializers 初始启动引导器(在 spring.factories 找)。
找到所有的 ApplicationContextInitializer 初始化器(在 spring.factories 找)。
找到所有的 ApplicationListener 应用监听器(在 spring.factories 找)。
执行 SpringApplication
对象的 run
方法:
记录应用启动时间,创建引导上下文(DefaultBootstrapContext),它会挨个执行之前获取的 初始启动引导器 的 initialize 方法完成引导启动器上下文环境设置。
让当前应用进入 headless(java.awt.headless) 模式。
获取所有 SpringApplicationRunListeners 运行监听器(在 spring.factories 找),遍历所有的运行监听器并调用它的 starting
方法(监听器执行 应用开始启动 事件)。
保存命令行参数 ApplicationArguments 。
准备环境 ConfigurableEnvironment:
当前上下文有环境信息就获取否则就新创建一个基础环境信息。
配置环境信息对象,读取所有配置源的配置属性值,绑定环境信息。
遍历所有的运行监听器并调用它的 environmentPrepared
方法(监听器执行 环境准备完成 事件)。
创建 IOC 容器 createApplicationContext()
(根据项目类型创建容器)。
prepareContext 准备 IOC 容器基本信息:
applyInitializers 应用初始化器,遍历所有 初始化器 对 IOC 容器进行初始化。
遍历所有的运行监听器并调用它的 contextPrepared
方法(监听器执行 IOC 容器准备完成 事件)。
遍历所有的运行监听器并调用它的 contextLoaded
方法(监听器执行 IOC 容器已加载 事件)。
refreshContext 刷新容器,在容器中创建所有需要创建的组件。
afterRefresh 执行容器刷新后处理。
遍历所有的运行监听器并调用它的 started
方法(监听器执行 IOC 容器已启动 事件)。
callRunners 调用所有的 runners:
获取容器中的 ApplicationRunner 与 CommandLineRunner 并且按照 @Order
进行排序。
遍历所有 runners 执行 run 方法。
如果以上有异常发生将调用运行监听器的 failed 方法(监听器执行 项目启动失败 事件)。
无异常将调用运行监听器的 ready 方法(实际执行 running 方法,监听器执行 项目已完全启动 事件),其中如果发生异常依然执行 failed 方法。
最后返回整个 IOC 容器。
自定义监听组件:
需要在 resources 中 META-INF/spring.factories
内配置的组件:
直接可通过 Spring 注入容器中的组件(如添加 @Component
注解等方式):
ApplicationRunner
CommandLineRunner
整合 Jsp
产考文献:Spring Boot 中使用之 JSP 篇
其它问题:spring boot 设置启动时初始化 DispatcherServlet 出错
1. 所需依赖: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependency > <groupId > org.apache.tomcat.embed</groupId > <artifactId > tomcat-embed-jasper</artifactId > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > </dependency > <dependency > <groupId > javax.servlet.jsp</groupId > <artifactId > javax.servlet.jsp-api</artifactId > <version > 2.3.3</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > jstl</artifactId > </dependency >
2. application 配置: 1 2 spring.mvc.view.prefix : /WEB-INF/jsp/ # jsp存放文件夹(注意不包含webapp文件夹) spring.mvc.view.suffix : .jsp # 视图文件后缀
这是 properties 文件配置 yml 配置文件同理转化即可。
3. idea 设置和问题: 1. 资源过滤问题: 如果遇到 jsp 文件不在编译后的项目中可在 pom.xml
的 build
标签中添加如下资源过滤配置。(可以根据需要更改配置)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > true</filtering > </resource > <resource > <directory > src/main/resources</directory > <includes > <include > **/*.*</include > </includes > <filtering > true</filtering > </resource > <resource > <directory > src/main/webapp</directory > <targetPath > META-INF/resources</targetPath > <includes > <include > **/*.*</include > </includes > </resource > </resources >
2. 将webapp在idea中设置为web资源文件夹:
3. 项目找不到 jsp 文件:
视图前缀配置中有人容易加上 webapp
文件夹前缀,也是造成jsp页面访问不到的原因。
SpringBoot 任务 异步任务
在主程序类上使用 @EnableAsync
注解开启异步任务。
在需要异步执行的业务方法上添加 @Async
注解。
1 2 3 4 5 6 7 @Service public class TestService { @Async public void asyncTest () throws InterruptedException { Thread.sleep(3000 ); } }
该方法执行时自动异步执行,不会阻碍下方的代码执行。
1 2 3 4 5 @RequestMapping(value = "asyncTest", method = RequestMethod.GET) public String asyncTest () throws InterruptedException { testService.asyncTest(); return "asyncTest" ; }
邮件任务
导入依赖:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-mail</artifactId > </dependency >
以 QQ 邮箱为例,在账户中开启 POP3/SMTP 服务并获取 授权码 。
邮箱配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 spring: mail: host: smtp.163.com port: 25 username: mail password: password protocol: smtp default-encoding: UTF-8 properties: mail: smtp: auth: true starttls: enable: true required: true ssl: enable: true socketFactory: port: 25 class: javax.net.ssl.SSLSocketFactory
发送邮件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Value("${spring.mail.username}") private String from;@Autowired JavaMailSenderImpl mailSender; @GetMapping("sendMail") public String sendMail (@RequestParam String msg) { SimpleMailMessage sendMail = new SimpleMailMessage (); sendMail.setFrom(from); sendMail.setTo("2994160002@qq.com" ); sendMail.setSubject("邮件主题" ); sendMail.setText("邮件内容" ); mailSender.send(sendMail); return msg + " - 发送成功" ; }
复杂邮件:
1 2 3 4 5 6 7 8 9 MimeMessage mimeMessage = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper (mimeMessage, true );helper.setFrom(from); helper.setTo("2994160002@qq.com" ); helper.setSubject("邮件主题" ); helper.setText("<h1>开启 HTML 解析</h1>" , true ); helper.addAttachment("附件名称" , new File ("D:\\桌面文件\\IDEA-java主题配色.jar" )); mailSender.send(mimeMessage);
定时任务
在主程序类上使用 @EnableScheduling
注解标注开启定时任务功能。
在需要开启定时任务的业务方法上使用 @Scheduled
注解标注,并使用 cron 表达式指定执行时间。
在线Cron表达式生成器 (qqe2.com)
1 2 3 4 5 6 @Scheduled(cron = "0/5 * * * * *") public void schedulingTest () { SimpleDateFormat date = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); System.out.println(date.format(new Date ()) + " 每五秒执行" ); }
WebSocket 添加依赖: 1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-websocket</artifactId > </dependency >
Server(服务端):
配置类中注入 ServerEndpointExporter
启用 websocket 支持:
1 2 3 4 5 6 7 @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter () { return new ServerEndpointExporter (); } }
编写 websocket 服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Slf4j @Service @ServerEndpoint("/websocket/{id}") public class WebSocketServer { private static final Map<String, Session> sessionMap = new HashMap (); @OnOpen public void onOpen (@PathParam("id") String id, Session session) { log.info("onOpen: {} , 当前连接数: {}" , id, sessionMap.size()); sessionMap.put(id, session); sendMsgAll("欢迎: " + id +", 当前人数: " + sessionMap.size()); } @OnMessage public void OnMessage (@PathParam("id") String id, String message) { log.info("OnMessage: {} -> {}" , id, message); sendMsgAllNotMe(id, id+ ":" + message); } @OnClose public void OnClose (@PathParam("id") String id) { log.info("OnClose: {} , 当前连接数: {}" , id, sessionMap.size()); sessionMap.remove(id); sendMsgAll("退出: " + id +", 当前人数: " + sessionMap.size()); } @OnError public void OnError (@PathParam("id") String id, Throwable error) { log.info("OnError: {} , 当前连接数: {}" , id, sessionMap.size()); if (sessionMap.containsKey(id)){ sessionMap.remove(id); } sendMsgAll("出错: " + id +", 当前人数: " + sessionMap.size()); } public static void sendMsg (String id, String message) { Session session = sessionMap.get(id); session.getAsyncRemote().sendText(message); } public static void sendMsgAll (String message) { for (Session session : sessionMap.values()){ session.getAsyncRemote().sendText(message); } } public static void sendMsgAllNotMe (String id, String message) { for (String i : sessionMap.keySet()){ if (!i.equals(id)){ Session session = sessionMap.get(i); session.getAsyncRemote().sendText(message); } } } }
Client(客户端): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 @Data @Slf4j @Component @ClientEndpoint public class CqClient { private static Session s; @Value("${client.uri}") private String uri; public boolean connect () { if (s == null || !s.isOpen()){ try { s = ContainerProvider.getWebSocketContainer() .connectToServer(this , new URI (this .uri)); } catch (Exception e) { e.printStackTrace(); return false ; } } return true ; } @OnOpen public void OnOpen (Session session) { log.info("OnOpen 连接成功" ); } @OnMessage public void OnMessage (String message) { log.info("接收消息 ---> " + message); } @OnClose public void OnClose (Session session) { log.info("OnClose 连接关闭" ); } @OnError public void OnError (Session session, Throwable throwable) { log.info("OnError 连接出错" ); } public static void sendMsg (String msg) throws JsonProcessingException { s.getAsyncRemote().sendText(msg); } }
日志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private static final Logger logger = LoggerFactory.getLogger(MyClass.class);logger.debug("This is a debug message" ); logger.info("This is an info message" ); logger.warn("This is a warning message" ); logger.error("This is an error message" ); logger.debug("User {} logged in successfully" , username); if (logger.isDebugEnabled()) { String expensiveDebugInfo = generateExpensiveDebugInfo(); logger.debug("Expensive debug info: {}" , expensiveDebugInfo); } try { } catch (Exception e) { logger.error("An error occurred: {}" , e.getMessage(), e); }
在 Spring Boot 项目中只需将 logback-spring.xml
文件放置在 src/main/resources
目录下,Spring Boot 就会在启动时自动加载并应用这个日志配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 <?xml version="1.0" encoding="UTF-8" ?> <configuration > <include resource ="org/springframework/boot/logging/logback/base.xml" /> <property name ="LOG_HOME" value ="./logs" /> <property name ="APP_NAME" value ="application" /> <property name ="MAX_HISTORY" value ="30" /> <property name ="MAX_FILE_SIZE" value ="10MB" /> <property name ="CONSOLE_PATTERN" value ="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <property name ="FILE_PATTERN" value ="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <appender name ="Console" class ="ch.qos.logback.core.ConsoleAppender" > <encoder > <pattern > ${CONSOLE_PATTERN}</pattern > </encoder > </appender > <appender name ="RollingFile" class ="ch.qos.logback.core.rolling.RollingFileAppender" > <file > ${LOG_HOME}/${APP_NAME}-0-current.log</file > <encoder > <pattern > ${FILE_PATTERN}</pattern > </encoder > <rollingPolicy class ="ch.qos.logback.core.rolling.TimeBasedRollingPolicy" > <fileNamePattern > ${LOG_HOME}/${APP_NAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern > <timeBasedFileNamingAndTriggeringPolicy class ="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP" > <maxFileSize > ${MAX_FILE_SIZE}</maxFileSize > </timeBasedFileNamingAndTriggeringPolicy > <maxHistory > ${MAX_HISTORY}</maxHistory > </rollingPolicy > </appender > <root level ="INFO" > <appender-ref ref ="Console" /> <appender-ref ref ="RollingFile" /> </root > <logger name ="com.example" level ="DEBUG" additivity ="false" > <appender-ref ref ="Console" /> <appender-ref ref ="RollingFile" /> </logger > </configuration >