二.核心功能 配置文件 yaml 对象:键值对的集合(map,hash,set,object) 1 2 3 4 5 6 k: {k1:v1 , k2:v2 , k3:v3 }k: k1: v1 k2: v2 k3: v3
数组:顺序排列的值(array,list,queue) 1 2 3 4 5 6 k: [v1 , v2 , v3 ]k: - v1 - v2 - v3
1 2 3 4 5 k1: "abc \n def" k2: 'abc \n def'
自动提示 1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > </dependency >
Web开发 SpringMVC自动配置概览 内容协商视图解析器和BeanName视图解析器 静态资源的自动配置 自动注册 Converter,GenericConverter,Formatter
支持 HttpMessageConverters
(后面,配合内容协商理解原理) 自动注册 MessageCodesResolver
(国际化用) 静态index.html页支持 自定义Favicon
自动使用 ConfigurableWebBindingInitializer
,(DataBinder负责将请求数据绑定到JavaBean上) 静态资源访问 官方文档:Web/Servlet Web Application/The Spring Web MVC Framework/Static Content
静态资源目录/static
/public
/resources
/META-INF/resources
请求先看Controller能不能处理
不能处理的交给静态资源处理器
都找不到则404
1 2 3 spring: mvc: static-path-pattern: /resources/**
1 2 3 4 spring: web: resources: static-locations: [classpath:/haha/ , classpath:/page/ ]
欢迎页 官方文档:Web/Servlet Web Application/The Spring Web MVC Framework/Welcome Page
静态资源路径下放置index.html controller处理/index请求 static-path-pattern必须为默认的/**才能访问到默认首页,下面源码分析会看到为什么
请求映射 1 2 3 4 5 @RequestMapping(value = "/user", method = RequestMethod.DELETE) public String deleteUser () { return "delete" ; }
使用HiddenHttpMethodFilter
兼容PUT
,DELETE
等请求(源代码 下方有) 1 spring.mvc.hiddenmethod.filter = enabled
SpringBoot帮我们注入了HiddenHttpMethodFilter
,我们也可以自己注入 1 2 3 4 5 6 7 8 9 10 @Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter (); filter.setMethodParam("_m" ); return filter; } }
参数注解 1. 普通注解 1 public String hello (@RequestParam("username") String username) {}
1 2 3 @GetMapping("/car/{carId}/owner/{userId}") public Map<String, Object> getCar (@PathVariable Integer carId, @PathVariable Integer userId) {}
1 2 3 @GetMapping("/car/{carId}/owner/{userId}") public Map<String, Object> getCar (@PathVariable Map<String, String> map) {}
1 public Map<String, Object> getHeader (@RequestHeader("User-Agent") String userAgent) {}
1 public Map<String, Object> getHeader (@RequestHeader MultiValueMap<String, String> header) {}
@RequestParam
:获取请求参数(URL参数,form表单中的参数)1 2 3 public Map<String, Object> getParam (@RequestParam("age") Integer age, @RequestParam("inters") List<String> inters) {}
1 public Map<String, Object> getCookie (@CookieValue("_ga") String ga) {}
1 public Map<String, Object> getCookie (@CookieValue("_ga") Cookie gaCookie) {}
1 public Map<String, Object> postMethod (@RequestBody String content) {}
@RequestAttribute
:获取request域属性1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/goto") public String goToPage (HttpServletRequest request) { request.setAttribute("msg" , "value1" ); return "forward:/success" ; } @ResponseBody @GetMapping("/success") public String success (@RequestAttribute("msg") String msg) { return msg; }
@MatrixVariable
:获取矩阵变量(必须有url变量才能解析)1 2 3 4 5 @GetMapping("/cars/{id}") public Map<String, Object> carsSell (@MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand, @PathVariable("id") String id) {}
1 2 3 4 5 6 7 @GetMapping("/boss/{bossId}/{empId}") public Map<String, Object> boss (@MatrixVariable(value = "age", pathVar = "bossId") Integer bossAge, @PathVariable String bossId, @MatrixVariable(value = "age", pathVar = "bossId") Integer empAge, @PathVariable String empId) {
SpringBoot默认禁用了矩阵变量,需手动开启,开启方法见后文源码分析
矩阵变量需要和路径一起看,比如:/boss/1/2
/boss/1;age=30/2;age=40
前端传Json
对象,使用@RequestParam
注解 前端传Json
对象的字符串,使用@RequestBody
注解
url重写(Cookie被禁用如何使用Session)
使用矩阵变量:/abc;jsessionid=xxxx
2. Servlet API 1 public String goToPage (HttpServletRequest request) {}
MultipartRequest
HttpSession
javax.servlet.http.PushBuilder
Principal
InputStream
Reader
HttpMethod
Locale
TimeZone
ZoneId
1 public String hello (HttpSession session)
3. 复杂参数 1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/params") public String testParam (Map<String, Object> map, Model model, HttpServletRequest request) { map.put("mapKey" , "mapValue" ); model.addAttribute("modelKey" , "modelValue" ); request.setAttribute("reqKey" , "reqValue" ); return "forward:/success" ; }
Errors/BindingResult
RedirectAttributes(重定向携带数据)
ServletResponse
SessionStatus
UriComponentsBuilder
ServletUriComponentsBuilder
参数自动封装为对象
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 @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } @Data public class Pet { private String name; private String age; } @PostMapping("/saveuser") public Person saveUser (Person person) { return person; }
自定义Converter
1 2 宠物: <input name ="pet" value ="Tom,6" >
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(proxyBeanMethods = false) public class MyConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Pet>() { @Override public Pet convert (String source) { if (!source.isEmpty()) { Pet pet = new Pet (); String[] split = source.split("," ); pet.setName(split[0 ]); pet.setAge(Integer.parseInt(split[1 ])); return pet; } return null ; } }); } }; } }
可以在WebMvcConfigurer
源码中看到用法提示
1 2 3 4 5 6 default void addFormatters (FormatterRegistry registry) {}
响应处理 响应页面 响应数据 1 2 // web开发场景,已经帮我们引入了jackson spring-boot-starter-web
1 2 3 4 5 6 7 8 9 10 @Controller public class ResponseController { @ResponseBody @GetMapping("/test/person") public Person getPerson () { Person person = new Person (); return person; } }
内容协商 1 2 Accept=application/xml Accept=application/json
1 2 3 4 spring: mvc: contentnegotiation: favor-parameter: true
1 2 http://localhost:8088/test/person?format=json http://localhost:8088/test/person?format=xml
@ResponseBody
注解,代表数据要响应出去
调用RequestResponseBodyMethodProcessor
(返回值处理器)处理返回值
调用各种MessageConverter
进行数据的转换
MessageConverter
可以支持各种媒体类型数据的操作(读,写)
内容协商找到合适的MessageConverter
自定义MessageConverter
自定义MessageConverter
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 package com.example.myweb.converter;public class MyConverter implements HttpMessageConverter <Person> { @Override public boolean canRead (Class<?> clazz, MediaType mediaType) { return false ; } @Override public boolean canWrite (Class<?> clazz, MediaType mediaType) { return clazz.isAssignableFrom(Person.class); } @Override public List<MediaType> getSupportedMediaTypes () { return MediaType.parseMediaTypes("application/my-type" ); } @Override public Person read (Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { return null ; } @Override public void write (Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { String data = person.getName() + "-" + person.getAge(); OutputStream body = outputMessage.getBody(); body.write(data.getBytes()); } }
添加自定义MessageConverter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package com.example.myweb.config;@Configuration(proxyBeanMethods = false) public class MyConfig { @Bean public WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void extendMessageConverters (List<HttpMessageConverter<?>> converters) { MyConverter myConverter = new MyConverter (); converters.add(myConverter); } }; } }
(源码)请求参数与请求返回值 视图解析 thymeleaf
:现代化,服务端Java模板引擎SpringBoot要打包成Jar包,因此无法使用JSP 基本语法 语法 用途 ${…} 获取请求域、session域、对象等值 *{…} 获取上下文对象值 #{…} 获取国际化等值 @{…} 生成链接 ~{…} jsp:include
作用,引入公共页面片段
字面量
文本值:’one text’, ‘Another one!’
数字: 0, 34, 3.0, 12.3
布尔值: true, false
空值:null
变量:one,two,(变量不能有空格)
文本操作
字符串拼接:+
变量替换:|The name is ${name}|
数学运算
运算符:+, -, *, /, %
布尔运算
运算符:and, or, !, not
比较运算
比较:>, <, >=, <= (gt, lt, ge, le)
等式:== , != (eq, ne)
条件运算
(if) ? (then) (if) ? (then) : (else) (value) ?: (defaultvalue)
特殊操作
无操作:_
为某个属性赋值
th:attr=
1 2 3 4 5 6 7 8 <form action ="subscribe.html" th:attr ="action=@{/subscribe}" > <fieldset > <input type ="submit" th:attr ="value=#{subscribe.submit}" /> </fieldset > </form > <img src ="../../images/gtvglogo.png" th:attr ="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
`th:xxx=`
1 2 3 <input type ="submit" value ="Subscribe!" th:value ="#{subscribe.submit}" /> <form action ="subscribe.html" th:action ="@{/subscribe}" >
基础配置 1 spring-boot-starter-thymeleaf
1 2 3 4 5 6 @AutoConfiguration(after = { WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class }) @EnableConfigurationProperties(ThymeleafProperties.class) @ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class }) @Import({ TemplateEngineConfigurations.ReactiveTemplateEngineConfiguration.class, TemplateEngineConfigurations.DefaultTemplateEngineConfiguration.class }) public class ThymeleafAutoConfiguration { }
自动配置好的策略
所有thymeleaf
的配置值都在ThymeleafProperties
配置好了ThymeleafViewResolver
(视图解析器) 我们的页面只需放在如下路径中,后缀html
1 2 3 4 5 @ConfigurationProperties(prefix = "spring.thymeleaf") public class ThymeleafProperties { public static final String DEFAULT_PREFIX = "classpath:/templates/" ; public static final String DEFAULT_SUFFIX = ".html" ; }
基础用法 1 2 3 4 5 6 7 8 9 10 11 12 package com.example.myweb.controller;@Controller public class ViewTestController { @GetMapping("/suc") public String success (Model model) { model.addAttribute("msg" , "你好thymeleaf" ); model.addAttribute("link" , "/login" ); return "success" ; } }
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE html > <html lang ="en" xmlns:th ="http://www.thymeleaf.org" > <head > <meta charset ="UTF-8" > <title > Title</title > </head > <body > <h1 th:text ="${msg}" > </h1 > <a th:href ="${link}" > link1</a > <a th:href ="@{link}" > link2</a > </body > </html >
拦截器 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 package com.xuedongyun.myadmin.interceptor;public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object loginUser = session.getAttribute("loginUser" ); if (loginUser!=null ) { return true ; } request.setAttribute("msg" , "请先登录" ); request.getRequestDispatcher("/" ).forward(request, response); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super .postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super .afterCompletion(request, response, handler, ex); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package com.xuedongyun.myadmin.config;@Configuration public class MyConfig { @Bean WebMvcConfigurer webMvcConfigurer () { return new WebMvcConfigurer () { @Override public void addInterceptors (InterceptorRegistry registry) { LoginInterceptor loginInterceptor = new LoginInterceptor (); registry.addInterceptor(loginInterceptor) .addPathPatterns("/**" ) .excludePathPatterns("/" , "/login" , "/css/**" , "/fonts/**" , "/images/**" , "/js/**" ); } }; } }
1 2 3 spring: mvc: static-path-pattern: /static/**
文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <form role ="form" th:action ="@{/upload}" method ="post" enctype ="multipart/form-data" > <div class ="form-group" > <label for ="exampleInputEmail1" > 邮箱</label > <input type ="email" name ="email" class ="form-control" id ="exampleInputEmail1" placeholder ="Enter email" > </div > <div class ="form-group" > <label for ="exampleInputPassword1" > 密码</label > <input type ="password" name ="password" class ="form-control" id ="exampleInputPassword1" placeholder ="Password" > </div > <div class ="form-group" > <label for ="headInputFile" > 头像</label > <input type ="file" name ="headImage" id ="headInputFile" > <label for ="photoInputFile" > 照片</label > <input type ="file" name ="photos" id ="photoInputFile" multiple > </div > <button type ="submit" class ="btn btn-primary" > Submit</button > </form >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @PostMapping("/upload") public String upload (@RequestParam("email") String email, @RequestParam("password") String password, @RequestPart("headImage") MultipartFile headImage, @RequestPart("photos") MultipartFile[] photos) throws IOException { if (!headImage.isEmpty()) { File file = new File ("C:\\cache\\" + headImage.getOriginalFilename()); headImage.transferTo(file); } if (photos.length > 0 ) { for (MultipartFile photo : photos) { if (!photo.isEmpty()) { File file = new File ("C:\\cache\\" + headImage.getOriginalFilename()); photo.transferTo(file); } } } return "main" ; }
1 2 3 4 5 6 spring: servlet: multipart: max-request-size: 100MB max-file-size: 50MB
错误处理 添加自定义视图 可以自行添加error
视图替代默认,error.html
,error/404.html
,error/5xx.html
@ExceptionResolver
@ControllerAdvice
+@ExceptionResolver
处理全局异常1 2 3 4 5 6 7 8 9 10 @Slf4j @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class}) public String handleAlgorithmException (Exception exception) { log.error("异常是: {}" , exception); return "login" ; } }
@ResponseStatus
1 2 3 4 5 6 @ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "错误的具体原因") public class MyException extends RuntimeException { public MyException (String msg) { super (msg); } }
1 throw new MyException ("dddddd" )
自定义异常处理器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package com.xuedongyun.myadmin.exception;@Order(Ordered.HIGHEST_PRECEDENCE) @Component protected ModelAndView applyStatusAndReason (int statusCode, @Nullable String reason, HttpServletResponse response) throws IOException { if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); } else { String resolvedReason = (this .messageSource != null ? this .messageSource.getMessage(reason, null , reason, LocaleContextHolder.getLocale()) : reason); response.sendError(statusCode, resolvedReason); } return new ModelAndView (); }
原生组件注入 使用Serlvet API
1 2 3 4 5 6 7 8 9 10 @ServletComponentScan(basePackages = "com.xuedongyun.servlet") @SpringBootApplication public class MyAdminApplication { public static void main (String[] args) { SpringApplication.run(MyAdminApplication.class, args); } }
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("test" ); } }
Filter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Slf4j @WebFilter(urlPatterns = {"/css/*", "/images/*"}) public class MyFilter implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { log.info("Filter初始化" ); } @Override public void destroy () { log.info("Filter销毁" ); } @Override public void doFilter (ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { log.info("Filter工作" ); chain.doFilter(request, response); } }
小提示:url
匹配时,Servlet
中用*
,Spring
中用**
Listener 1 2 3 4 5 6 7 8 9 10 11 12 13 @Slf4j @WebListener public class MyListener implements ServletContextListener { @Override public void contextInitialized (ServletContextEvent sce) { log.info("监听到项目初始化完成" ); } @Override public void contextDestroyed (ServletContextEvent sce) { log.info("监听到项目销毁" ); } }
使用RegistrationBean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Configuration public class MyConfig { @Bean public ServletRegistrationBean myServlet () { MyServlet myServlet = new MyServlet (); return new ServletRegistrationBean <>(myServlet, "/my" , "/my02" ); } @Bean public FilterRegistrationBean myFilter () { MyFilter myFilter = new MyFilter (); FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean <>(myFilter); registrationBean.setUrlPatterns(Arrays.asList("/my" , "/css/*" )); return registrationBean; } @Bean public ServletListenerRegistrationBean myListener () { MyListener myListener = new MyListener (); return new ServletListenerRegistrationBean (myListener); } }
定制Web服务器 修改配置文件 ServletWebServerFactoryAutoConfiguration
绑定了属性ServerProperties
1 2 @ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) public class ServerProperties {}
1 server.undertow.accesslog.dir =/tmp
直接自定义webServerFactory
ConfigurableServletWebServerFactory
是ServletWebServerFactory
的子接口,可以很方便的设置一些属性1 2 3 4 5 6 7 8 public interface ConfigurableServletWebServerFactory extends ConfigurableWebServerFactory , ServletWebServerFactory, WebListenerRegistry { void setContextPath (String contextPath) ; void setDisplayName (String displayName) ; void setSession (Session session) ; ... }
在容器中放入自定义ConfigurableServletWebServerFactory
1 2 3 4 5 6 7 @Bean public ConfigurableServletWebServerFactory webServerFactory () { TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory (); factory.setPort(9000 ); factory.addErrorPages(new ErrorPage (HttpStatus.NOT_FOUND, "/not_found.html" )); return factory; }
使用FactoryCustomizer
在ServletWebServerFactoryAutoConfiguration
中有ServletWebServerFactoryCustomizer
(web服务器工厂类定制化器)定制化器负责把配置文件中的值和ServletWebServerFacory
进行绑定 定制化器后置的修改ServletWebServerFacory
中的值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Configuration(proxyBeanMethods = false) @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @ConditionalOnClass(ServletRequest.class) @ConditionalOnWebApplication(type = Type.SERVLET) @EnableConfigurationProperties(ServerProperties.class) @Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, ServletWebServerFactoryConfiguration.EmbeddedJetty.class, ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) public class ServletWebServerFactoryAutoConfiguration { @Bean public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer (ServerProperties serverProperties, ObjectProvider<WebListenerRegistrar> webListenerRegistrars, ObjectProvider<CookieSameSiteSupplier> cookieSameSiteSuppliers) { return new ServletWebServerFactoryCustomizer (serverProperties, webListenerRegistrars.orderedStream().collect(Collectors.toList()), cookieSameSiteSuppliers.orderedStream().collect(Collectors.toList())); } }
1 2 3 4 5 6 7 8 9 10 11 12 @Component public class MyWebServerFactoryCustomizer extends ServletWebServerFactoryCustomizer { public MyWebServerFactoryCustomizer (ServerProperties serverProperties) { super (serverProperties); } @Override public void customize (ConfigurableServletWebServerFactory factory) { factory.setPort(9000 ); } }
定制化原理 定制化的常见方式 编写自定义配置类xxxConfiguration
,然后@Bean
替换容器中组件,或增加组件(比如视图解析器)
修改配置文件
xxxCustomizer
(比如定制web
服务器时)
配置类实现WebMvcConfigurer
,来定制化web
功能
向容器中注入WebMvcRegistrations
组件,来修改SpringMVC
底层的组件(比如修改HandlerMapping
)
@EnableWebMvc
+WebMvcConfigurer
配置类加上@EnableWebMvc
将全面接管SpringMVC
静态资源,视图解析器,欢迎页…全没了 需要自己来实现自动配置类了,自行实现WebMvcConfigurer
,利用addxxx
方法… 原理分析套路 starter
xxxAutoConfiguration
导入xxx组件 绑定xxxProperties
数据访问 数据源 数据源的自动配置 1 spring-boot-starter-data-jdbc
1 2 3 HikariCP (官方数据库连接池) spring-jdbc (jdbc) spring-tx (用于处理事务)
官方场景没有导入驱动
1 2 3 4 5 6 7 spring: datasource: url: jdbc:mysql://localhost:3306/user_db username: root password: 122599 driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource // 默认就是它,不需要写
1 2 3 4 5 6 7 8 @Autowired JdbcTemplate jdbcTemplate; @Test void test () { Long count = jdbcTemplate.queryForObject("select count(*) from t_user" , Long.class); log.info(String.valueOf(count)); }
分析相关自动配置 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 @Configuration(proxyBeanMethods = false) @ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory") @AutoConfigureBefore(SqlInitializationAutoConfiguration.class) @EnableConfigurationProperties(DataSourceProperties.class) @Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.InitializationSpecificCredentialsDataSourceInitializationConfiguration.class, DataSourceInitializationConfiguration.SharedCredentialsDataSourceInitializationConfiguration.class }) public class DataSourceAutoConfiguration { @Configuration(proxyBeanMethods = false) @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class, DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { } }
使用Druid
数据源 自定义使用 1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class MyDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource") public DataSource dataSource () throws SQLException { DruidDataSource dataSource = new DruidDataSource (); dataSource.setFilters("stat, wall" ); return dataSource; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Bean public ServletRegistrationBean statViewServlet () { StatViewServlet servlet = new StatViewServlet (); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean <>(servlet, "/druid/*" ); registrationBean.addInitParameter("loginUsername" , "xdy" ); registrationBean.addInitParameter("loginPassword" , "1234" ); registrationBean.addInitParameter("resetEnable" , "true" ); return registrationBean; } @Bean public FilterRegistrationBean webStatFilter () { WebStatFilter filter = new WebStatFilter (); FilterRegistrationBean<WebStatFilter> registrationBean = new FilterRegistrationBean <>(filter); registrationBean.setUrlPatterns(Arrays.asList("/*" )); registrationBean.addInitParameter("exclusions" , "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" ); return registrationBean; }
使用starter (源码)DruidDataSourceAutoConfigure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Configuration @ConditionalOnClass(DruidDataSource.class) @AutoConfigureBefore(DataSourceAutoConfiguration.class) @EnableConfigurationProperties({DruidStatProperties.class, DataSourceProperties.class}) @Import({DruidSpringAopConfiguration.class, DruidStatViewServletConfiguration.class, DruidWebStatFilterConfiguration.class, DruidFilterConfiguration.class}) public class DruidDataSourceAutoConfigure { private static final Logger LOGGER = LoggerFactory.getLogger(DruidDataSourceAutoConfigure.class); @Bean(initMethod = "init") @ConditionalOnMissingBean public DataSource dataSource () { LOGGER.info("Init DruidDataSource" ); return new DruidDataSourceWrapper (); } }
自动配置导入了DruidSpringAopConfiguration
:监控Spring
用的DruidStatViewServletConfiguration
:监控页面DruidWebStatFilterConfiguration
:web监控DruidFilterConfiguration
:开启相关监控 配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 spring: datasource: url: jdbc:mysql://localhost:3306/user_db username: root password: 122599 driver-class-name: com.mysql.cj.jdbc.Driver druid: stat-view-servlet: enabled: true login-username: xdy login-password: 1234 reset-enable: true web-stat-filter: enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' aop-patterns: com.xuedongyun.myadmin.* filters: stat, wall
MyBatis 1 mybatis-spring-boot-starter
MyBatis的自动配置 mybatis-spring-boot-autoconfigure/MybatisAutoConfiguration
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 @org .springframework.context.annotation.Configuration@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnSingleCandidate(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) public class MybatisAutoConfiguration implements InitializingBean { public MybatisAutoConfiguration (MybatisProperties properties, ...) { this .properties = properties; ... } @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory (DataSource dataSource) throws Exception { ... } @Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate (SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this .properties.getExecutorType(); if (executorType != null ) { return new SqlSessionTemplate (sqlSessionFactory, executorType); } else { return new SqlSessionTemplate (sqlSessionFactory); } } @org .springframework.context.annotation.Configuration @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class }) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { @Override public void afterPropertiesSet () { logger.debug("xxx" ); } } }
以前需要
全局配置的文件 SqlSessionFactory
->SqlSession
->Mapper
现在
全局配置文件:yaml
配置
SqlSessionFactory
:自动配置
SqlSessionTemplate
:自动配置
Mapper
:@Mapper
接口
配置模式 1 2 3 4 5 6 mybatis: config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml configuration: map-underscore-to-camel-case: true
1 2 3 4 5 6 7 8 9 10 11 <!--全局配置文件:resources/mybatis/mybatis-config.xml--> <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration> <settings> <!--开启驼峰命名,这里只是举个例子,其实最好在yaml配置文件中写--> <setting name="mapUnderscoreToCamelCase" value="true" /> </settings> </configuration>
1 2 3 4 5 6 package com.xuedongyun.myadmin.mapper;@Mapper public interface TUserMapper { public TUser getTUser (Long userId) ; }
1 2 3 4 5 6 7 8 9 10 <?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="com.xuedongyun.myadmin.mapper.TUserMapper" > <select id="getTUser" resultType="com.xuedongyun.myadmin.bean.TUser" > select * from user_db.t_user where user_id = #{userId} </select> </mapper>
1 2 3 4 5 6 7 8 @Autowired TUserMapper tUserMapper; @Test public void test () { TUser tUser = tUserMapper.getTUser(1002L ); System.out.println("tUser = " + tUser); }
总结:
导入starter
编写mapper
接口 编写mapper.xml
文件并绑定mapper
接口 在application.yaml
中指定mapper.xml
文件的位置 (二选一)在application.yaml
中指定全局配置文件的位置 (二选一)在application.yaml
中直接进行配置(推荐) 注解模式 1 2 3 4 5 6 7 8 package com.xuedongyun.myadmin.mapper;@Mapper public interface TUserMapper { @Select("select * from user_db.t_user where user_id = #{userId}") TUser getTUser (Long userId) ; }
最佳实践
引入starter
配置application.yaml
中,指定mapper-location
位置 编写Mapper
接口并标注@Mapper
注解 简单方法直接注解方式 复杂方法编写mapper.xml
MyBatisPlus Redis 单元测试 JUnit5
基础JUnit5
JUnit Platform
:JVM
上启动测试框架的基础,不止JUnit
,其他测试引擎也能接入JUnit Jupiter
:提供了JUnit
的新的测试模型,是JUnit5
新特性的核心。包含一个测试核心,在JUnit Platform
上运行JUnit Vintage
:提供的兼容JUnit4
,JUnit3
的测试引擎(SpringBoot2.4
以上已经默认移除了,要用需要手动添加)引入依赖 1 spring-boot-starter-test
1 2 3 4 5 6 7 8 @SpringBootTest class MyAdminApplicationTests { @Test void contextLoads () { } }
Junit
测试类支持Spring
的功能
JUnit5
常用注解@Test
:表示方法是测试方法。但是与JUnit4
的@Test
不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter
提供额外测试@ParameterizedTest
:表示方法是参数化测试@RepeatedTest
:表示方法可重复执行@DisplayName
:为测试类或者测试方法设置展示名称@BeforeEach
:表示在每个单元测试之前执行@AfterEach
:表示在每个单元测试之后执行@BeforeAll
:表示在所有单元测试之前执行@AfterAll
:表示在所有单元测试之后执行@Tag
:表示单元测试类别@Disabled
:表示测试类或测试方法不执行@Timeout
:表示测试方法运行如果超过了指定时间将会返回错误@ExtendWith
:为测试类或测试方法提供扩展类引用1 2 3 4 5 6 7 8 @SpringBootTest class MyAdminApplicationTests {}
断言机制 assertEquals
:判断两个对象或两个原始类型是否相等assertNotEquals
:判断两个对象或两个原始类型是否不相等assertSame
:判断两个对象引用是否指向同一个对象assertNotSame
:判断两个对象引用是否指向不同的对象assertTrue
:判断给定的布尔值是否为 trueassertFalse
:判断给定的布尔值是否为 falseassertNull
:判断给定的对象引用是否为 nullassertNotNull
:判断给定的对象引用是否不为 nullassertArrayEquals
:判断两个数组是否相等assertAll
:组合断言,都成功才往下走1 2 3 4 Assertions.assertAll( () -> Assertions.assertEquals(a, b), () -> Assertions.assertSame(c, d) );
1 2 3 4 Assertions.assertThrows( ArithmeticException.class, () -> {int i = 1 /0 ;} );
assertTimeout
:测试方法超时将会异常fail
:手动让方法失败前置条件 不满足前置条件,只会使得测试方法的执行终止,不会失败
嵌套测试 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 @DisplayName("A stack") class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew () { new Stack <>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack () { stack = new Stack <>(); } @Test @DisplayName("is empty") void isEmpty () { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped () { Assertions.assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked () { Assertions.assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element" ; @BeforeEach void pushAnElement () { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty () { Assertions.assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped () { Assertions.assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked () { Assertions.assertEquals(anElement, stack.peek()); Assertions.assertFalse(stack.isEmpty()); } } } }
参数化测试 @ValueSource
:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型@NullSource
:表示为参数化测试提供一个null的入参@EnumSource
:表示为参数化测试提供一个枚举入参@CsvFileSource
:表示读取指定CSV文件内容作为参数化测试入参@MethodSource
:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @ParameterizedTest @ValueSource(strings = {"one", "two", "three"}) @DisplayName("参数化测试1") public void parameterizedTest1 (String string) { System.out.println(string); Assertions.assertTrue(StringUtils.isNotBlank(string)); } @ParameterizedTest @MethodSource("method") @DisplayName("方法来源参数") public void testWithExplicitLocalMethodSource (String name) { System.out.println(name); Assertions.assertNotNull(name); } static Stream<String> method () { return Stream.of("apple" , "banana" ); }
指标监控 Profile功能 配置文件 1 2 @Value("${person.name:default}") private String name;
@Value()
注解
${}
取值
:
还可以指定默认值
两个配置文件application-prod.yaml
和application-test.yaml
1 2 3 person: name: prod-xdy
1 2 3 person: name: test-xdy
在默认配置文件application.yaml
中指定环境 1 2 3 spring: profiles: active: test
即使打包完成,也可以使用命令行的形式激活配置
1 java -jar ./xxx.jar --spring.profiles.active=test
优先级:
命令行 > application-[env].yaml
> application.yaml
1 2 3 4 5 6 7 spring: profiles: active: myprod group: myprod[0]: prod myprod[1]: ppd mytest[0]: test
两个组:myprod
和mytest
同一个组优先级:后面的覆盖前面的
@Profile注解 1 2 3 4 5 6 7 8 9 10 11 @Profile("prod") @Component public Boss extends Person { } @Profile("test") @Component public Staff extends Person { }
外部化配置 官方给了14种配置方式,后面的覆盖前面的 本节只讲常用的方式 常用:java属性文件(properties文件),yaml文件,环境变量,命令行参数 SpringBoot
中也能拿到环境变量1 2 @Value("${MAVEN_HOME}") private String mavenHome;
配置文件查找位置 classpath
根路径classpath
根路径下/config
目录jar
包当前目录jar
包当前目录的/config
目录/config
目录直接的子目录优先级:下面的覆盖上面的
配置文件的加载顺序 当前jar
包中的application.properties
, application.yaml
当前jar
包中的application-[env].properties
, application-[env].yaml
引用外部jar
包的application.properties
, application.yaml
引用外部jar
包的application-[env].properties
, application-[env].yaml
自定义starter graph LR;
starter-->autoconfigure-->spring-boot-starter starter
:引入所需依赖,同时引入该场景autoconfigure
包项目1:hello-spring-boot-starter
1 2 3 4 5 6 7 8 9 10 11 12 <groupId > com.xuedongyun</groupId > <artifactId > hello-spring-boot-starter</artifactId > <version > 1.0-SNAPSHOT</version > <dependencies > <dependency > <groupId > com.xuedongyun</groupId > <artifactId > hello-spring-boot-starter-autoconfigure</artifactId > <version > 0.0.1-SNAPSHOT</version > </dependency > </dependencies >
项目2:hello-spring-boot-starter-autoconfigure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <groupId > com.xuedongyun</groupId > <artifactId > hello-spring-boot-starter-autoconfigure</artifactId > <version > 0.0.1-SNAPSHOT</version > <dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-configuration-processor</artifactId > <optional > true</optional > </dependency > </dependencies >
假设需求是创建一个HelloService
,返回前缀+名字+后缀
1 2 3 4 5 6 7 8 9 10 package com.xuedongyun.hello.bean;@ConfigurationProperties("xuedongyun.hello") public class HelloProperties { private String prefix; private String suffix; }
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.xuedongyun.hello.service;public class HelloService { @Autowired HelloProperties helloProperties; public String sayHello (String username) { return helloProperties.getPrefix() + ": " + username + ">" + helloProperties.getSuffix(); } }
HelloServiceAutoConfiguration
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.xuedongyun.hello;@Configuration @EnableConfigurationProperties(HelloProperties.class) public class HelloServiceAutoConfiguration { @Bean @ConditionalOnMissingBean(HelloService.class) public HelloService helloService () { HelloService helloService = new HelloService (); return helloService; } }
resources/META-INF/spring.factories
这样SpringBoot会自动帮我们加载AutoConfiguration
1 2 3 # Auto Configure org.springframework.boot.autoconfigure.AutoConfiguration=\ com.xuedongyun.hello.HelloServiceAutoConfiguration
使用时:
配置文件中配置属性 直接从容器中获取HelloService
组件