SpringBoot总结 一、静态资源处理 1.1 静态资源访问 静态资源访问过程中,springBoot默认访问存储路径下的/static
(or /public
or /resources
or /META-INF/resources)
但是可以使用SpringBoot配置文件中application.properties 或者 application.yaml
配置如下:
静态资源默认将所有的请求/**
进行拦截,但是为了方便拦截器对其进行拦截,统一为静态资源设置前缀:
1 2 spring.mvc.static-path-pattern:/resources/** resources代表你需要映射的访问静态资源路径的前缀
设置静态资源存储的路径:
1 2 spring.web.resources.static-locations:classpath:[/resourcesSta/] 表示将静态资源存储到类路径下的:resourcesSta文件夹内,可以配置多个,数组写法
访问webjar类型
webjar
官网 webjar实际上是将其类似js等文件封装成jar包格式,将其使用maven导入之后,访问路径为:localhost:端口号/webjar/xxx/xxx即可以访问。
静态资源访问原理:
1.2 欢迎页和favicon(浏览器小图标) 1.2.1 静态页面作为欢迎页 将其欢迎页面(index.html)存储到静态资源路径下,即可进行访问,可以配置静态资源实际存储路径。但是在配置静态资源访问前缀之后,默认的index.html页面将会失效,无法访问!
1.2.2 favicon图标 将其放到静态资源路径上面即可以自动识别,但是名称必须要为favivon.xxx
,若配置静态资源访问前缀,则其也会失效!
1.3 静态资源处理源码分析 1.3.1 静态资源处理源码与配置文件映射 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 @Configuration(proxyBeanMethods = false) @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class }) @Order(0) public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer , ServletContextAware 构造方法:从容器中拿到: webProperties:资源文件属性 spring.web配置文件中取到 mvcProperties:mvc配置文件属性从 spring mvc配置文件中取到 beanFactory:spring容器工厂 messageConvertersProvider:消息类型转化器 resourceHandlerRegistrationCustomizer:资源处理器的自定义器 dispatcherServletPath:spring的servlet servletRegistrations:原生servlet、filter、listener注册器 public WebMvcAutoConfigurationAdapter (WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider, ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider, ObjectProvider<DispatcherServletPath> dispatcherServletPath, ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) { this .resourceProperties = webProperties.getResources(); this .mvcProperties = mvcProperties; this .beanFactory = beanFactory; this .messageConvertersProvider = messageConvertersProvider; this .resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this .dispatcherServletPath = dispatcherServletPath; this .servletRegistrations = servletRegistrations; this .mvcProperties.checkConfiguration(); }
1.3.2 资源映射处理的方法: 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 @Override public void addResourceHandlers (ResourceHandlerRegistry registry) { if (!this .resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled" ); return ; } addResourceHandler(registry, "/webjars/**" , "classpath:/META-INF/resources/webjars/" ); addResourceHandler(registry, this .mvcProperties.getStaticPathPattern(), (registration) -> { registration.addResourceLocations(this .resourceProperties.getStaticLocations()); if (this .servletContext != null ) { ServletContextResource resource = new ServletContextResource (this .servletContext, SERVLET_LOCATION); registration.addResourceLocations(resource); } }); } private void addResourceHandler (ResourceHandlerRegistry registry, String pattern, Consumer<ResourceHandlerRegistration> customizer) { if (registry.hasMappingForPattern(pattern)) { return ; } ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); customizer.accept(registration); registration.setCachePeriod(getSeconds(this .resourceProperties.getCache().getPeriod())); registration.setCacheControl(this .resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); registration.setUseLastModified(this .resourceProperties.getCache().isUseLastModified()); customizeResourceHandlerRegistration(registration); }
1 2 3 spring.web.resources.cache 相关的缓存配置: 配置静态资源缓存:告诉浏览器静态资源需要存储的时间 spring.web.resources.cache.period:秒
1.3.3 欢迎页的静态资源访问源码
handlerMapping:保存每一个Handler能处理哪种请求。
依旧是在WebMvcAutoConfiguration中,找到WelcomePageHandlerMapping
方法:其实际上向容器中放置了一个组件,其负责处理欢迎页的请求。传入参数中this.mvcProperties.getStaticPathPattern()
映射的就是静态资源访问路径。
1 2 3 4 5 6 7 8 9 10 11 WebMvcAutoConfiguration:中的私有方法向容器中注入一个处理欢迎页的请求映射handler @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping (ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping ( new TemplateAvailabilityProviders (applicationContext), applicationContext, getWelcomePage(), this .mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider)); welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations()); return welcomePageHandlerMapping; }
进入WelcomePageHandlerMapping中:
参数:
staticPathPattern:为自定义静态资源访问路径
welcomePage:找到的欢迎页
由welcomePageHandlerMapping
传入的参数中的welcomPage(由getWelcomePage()方法获得)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 WebMvcAutoConfiguration:中的私有方法 private Resource getWelcomePage () { for (String location : this .resourceProperties.getStaticLocations()) { Resource indexHtml = getIndexHtml(location); if (indexHtml != null ) { return indexHtml; } } ServletContext servletContext = getServletContext(); if (servletContext != null ) { return getIndexHtml(new ServletContextResource (servletContext, SERVLET_LOCATION)); } return null ; }
由getWelcomePage
方法可以发现,其遍历寻找在静态资源路径下文件,看起是否存在index.html
如果存在就传入一个不为空的welcomPage
文件。
1 2 3 4 5 6 7 8 9 10 11 12 WelcomePageHandlerMapping类的构造方法: WelcomePageHandlerMapping(TemplateAvailabilityProviders templateAvailabilityProviders, ApplicationContext applicationContext, Resource welcomePage, String staticPathPattern) { if (welcomePage != null && "/**" .equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage); setRootViewName("forward:index.html" ); } else if (welcomeTemplateExists(templateAvailabilityProviders, applicationContext)) { logger.info("Adding welcome page template: index" ); setRootViewName("index" ); } }
这里再判断欢迎页是否存在,以及其是否在/**目录下,这里当自定义静态资源路径之后,由于这里的welconPage 是由**WebMvcAutoConfiguration中的getWelcomePage()方法再静态资源路径下查找获得的 因此其并不匹配if 判断的存在与****/****** 目录下,因此会返回空白页!这也就是上面配置静态资源路径后index.html 无法出现的原因!**
当if 条件不成立之后,会返回视图名称****index 查看哪个controller能处理这个视图。
1 2 3 4 5 6 7 8 WelcomePageHandlerMapping:私有方法 setRootViewName()方法 private void setRootViewName (String viewName) { ParameterizableViewController controller = new ParameterizableViewController (); controller.setViewName(viewName); setRootHandler(controller); setOrder(2 ); }
1.3.4 favicon 浏览器默认会发送,项目路径下的/favicon.ico
静态资源,当修改静态资源访问路径之后,就无法找到这个文件,因此也会出现图标空白的情况。
二、请求参数处理 2.1 Rust 风格源码解析 2.1.1 Rust 风格原理 在SpringBoot自动配置类中,默认配置了一个OrderedHiddenHttpMethodFilter
类,其实际上继承HiddenHttpMethodFilter
。在HiddenHttpMethodFilter
这个doFilterInternal()
方法中,负责将请求方法首次进行封装,通过其中的_method
参数获取真正请求方式post、get、delete、put
,再将其request
中的请求方法进行替换,再次封装,传递给下一层进行请求被业务处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 HiddenHttpMethodFilter类中的方法: protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest requestToUse = request; if ("POST" .equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null ) { String paramValue = request.getParameter(this .methodParam); if (StringUtils.hasLength(paramValue)) { String method = paramValue.toUpperCase(Locale.ENGLISH); if (ALLOWED_METHODS.contains(method)) { requestToUse = new HttpMethodRequestWrapper (request, method); } } } filterChain.doFilter(requestToUse, response); }
但是虽然自动配置注入了分发请求的Rest风格的Filter,但是观察自动配置文件中的配置方法可以知道,在@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
配置文件中这一项spring.mvc.hiddenmethod.filter
开启之后才会启用Rest风格的请求。
1 2 3 4 5 6 @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled") public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter () { return new OrderedHiddenHttpMethodFilter (); }
开启Rust风格实际上,这个配置spring.mvc.hiddenmethod.filter
只需要在做页面开发的时候需要开启,实际上原生是支持post、get、delete、put
这些请求参数的(用postMan等第三方不涉及页面表单都可以兼容)。
2.1.2 自定义请求_method ``定制化SpringBoot
当然也可以自定义,这个参数method * ,因为实际上其,是因为容器中不存在HiddenHttpMethodFilter.class 才将其放入容器,又因为其实质上还是调用的HiddenHttpMethodFilter.class 中的doFilterInternal()方法实现请求方式在包装的,因此直接可以给容器中放一个 HiddenHttpMethodFilter 来修改其默认的参数*method
。
1 2 3 4 5 6 7 8 9 @Bean @Configuration(proxyBeanMethods = false) public class WebConfig implements WebMvcConfigurer { public HiddenHttpMethodFilter hiddenHttpMethodFilter () { HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter (); methodFilter.setMethodParam("_m" ); return methodFilter; } }
2.2 请求参数注解 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar (@PathVariable("id") Integer id, @PathVariable("username") String name, @PathVariable Map<String,String> pv, @RequestHeader("User-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam("age") Integer age, @RequestParam("inters") List<String> inters, @RequestParam Map<String,String> params, @CookieValue("_ga") String _ga, @CookieValue("_ga") Cookie cookie) @PostMapping("/save") public Map postMethod (@RequestBody String content)
集合注入为,全部参数或者全部属性到List中或者Map
@PathVariable 路径变量 全部一次性取出Map<String,String>
@RequestHeader 获取请求头参数 多个值注入Map<String,String>
@RequestParam 获取请求参数(比如表单提交的)支持集合注入,一个属性对应多个值
@CookieValue 获取Cookie
@RequestBody 获取请求体
@RequestAttribute 获取Request域内的属性参数
@MatrixVariable 矩阵变量 必须要有路径变量才能被解析
value属性:指定其取出的V是哪个KEY的
pathVar属性:表示取出哪一个路径变量后的矩阵变量 请求路径:/cars/sell
url路径:/cars/sell;low=34;brand=byd,audi,yd
每个参数之间用;
隔开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 例1 : 所有矩阵变量参数在一个路径变量的后面 请求路径:/cars/sell;low=34 ;brand=byd,audi,yd @GetMapping("/cars/{path}") public Map carsSell (@PathVariable("path") String path, @MatrixVariable("low") Integer low, @MatrixVariable("brand") List<String> brand) path = sell low = 34 brand=[byd,audi,yd] ---------------------------------------------- ---------------------------------------------- 例2 : 矩阵变量存在不同的路径变量后面 请求路径:/boss/1 ;age=20 /2 ;age=10 @GetMapping("/boss/{bossId}/{empId}") public Map boss (@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge, @MatrixVariable(value = "age",pathVar = "empId") Integer empAge) bossAge = 20 empAge = 10
开启矩阵变量需要,定制化修改SpringBoot:在WebMvcAutoConfigration下存在一个自动配置的configurePathMatch,其中使用urlPathHelper(处理请求路径)其中含有一个属性removeSemicolonContent(意思为移出分号后内容,也就是请求路径上分号后面内容全部移出。)
定制化放入一个configurePathMatch
:
Text 1 2 3 4 5 6 7 @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper urlPathHelper = new UrlPathHelper(); // 不移除;后面的内容。矩阵变量功能就可以生效 urlPathHelper.setRemoveSemicolonContent(false); configurer.setUrlPathHelper(urlPathHelper); }
2.3 DispatherServlet 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 72 73 74 75 76 77 78 79 80 81 82 protected void doDispatch (HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null ; boolean multipartRequestParsed = false ; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null ; Exception dispatchException = null ; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); mappedHandler = getHandler(processedRequest); if (mappedHandler == null ) { noHandlerFound(processedRequest, response); return ; } HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest (request, response).checkNotModified(lastModified) && isGet) { return ; } } if (!mappedHandler.applyPreHandle(processedRequest, response)) { return ; } mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return ; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException ("Handler dispatch failed" , err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { triggerAfterCompletion(processedRequest, response, mappedHandler, ex); } catch (Throwable err) { triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException ("Handler processing failed" , err)); } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null ) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else { if (multipartRequestParsed) { cleanupMultipart(processedRequest); } } } }
2.3.1 mappedHandler = getHandler(processedRequest) 1 2 3 4 5 6 7 8 9 10 11 12 @Nullable protected HandlerExecutionChain getHandler (HttpServletRequest request) throws Exception { if (this .handlerMappings != null ) { for (HandlerMapping mapping : this .handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null ) { return handler; } } } return null ; }
SpringMVC决定使用哪一个handler进行处理请求,通过这里的handlerMapping
获取所有的请求映射,使用遍历,看谁能够处理。SpringBoot自动配置了RequestMappingHandlerMapping
WelcomePageHandlerMapping
在WebMvcAutoConfiguration
执行流程:
2.4 系统参数解析原理以及自定义POJO参数解析 2.4.1 系统参数解析原理 2.4.2 POJO自定义参数解析
将请求注入到,自定义对象POJO中,级联属性可以直接封装,但是其他类似2.4.2.3这种自定义类型就需要自定义转化器Converter进行转化,才能够支持。
2.4.2.1 自动封装POJO(级联属性)方式 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 例子: bean对象 @Data public class Person { private String userName; private Integer age; private Date birth; private Pet pet; } @Data public class Pet { private String name; private Integer age; } 控制器: @PostMapping("/saveuser") public Person saveuser (Person person) { return person; } 表单请求:级联封装 >>>> 对象属性.内部对象属性 <form action="/saveuser" method="post" > 姓名: <input name="userName" value="zhangsan" /> <br/> 年龄: <input name="age" value="18" /> <br/> 生日: <input name="birth" value="2019/12/10" /> <br/> 宠物姓名:<input name="pet.name" value="阿猫" /><br/> 宠物年龄:<input name="pet.age" value="5" /> <input type="submit" value="保存" /> </form>
2.4.2.2 自动封装POJO 原理(数据绑定器binder–>>转化器Converters) 自定义类型参数封装,使用的还是通过HandlerMethodArgumentResolverComposite
找到参数解析器,最后返回参数解析器,而在自定义对象封装使用的是ServletModelAttributeMethodProcessor****参数解析器, 最后通过参数解析器的args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
方法进行解析参数。
ServletModelAttributeMethodProcessor参数解析器判断是否支持的方法: resolver.supportsParameter(parameter)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 调用ModelAttributeMEthodProcessor类中supportsParameter方法: @Override public boolean supportsParameter (MethodParameter parameter) { return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this .annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); } ------------------------------ ------------------------------ BeanUtils.isSimpleProperty:实际上就是判断是不是简单类型 public static boolean isSimpleValueType (Class<?> type) { return (Void.class != type && void .class != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type)); }
解析流程:
创建一个WebBinder数据绑定器,将其请求数据以及创建的空对象绑定到绑定器中,之后通过绑定器中包含的转换服务中的各种转换器将其数据类型进行转化,最终通过反射注入到JavaBean中
实际执行数据绑定封装关键步骤:创建数据绑定器之后执行bindRequestParameters(binder, webRequest);
进行绑定,再绑定过程中,获取原生类型,将binder转化为原生请求类型的binder,之后获取原生请求的参数,然后检查哪些需要绑定,之后通过遍历属性,一个一个获得之后通过反射器反射注入参数,设置值的过程中使用类型转化器(conventer)进行转化,最后执行提交。执行结束之后,数据成功被封装到JavaBean当中。
2.4.2.3 自定义Converter参数绑定 定制化SpringBoot
1 2 3 4 5 6 7 8 例子:自定义协议-->>pet属性使用;隔开前面名字后面年龄 <form action="/saveuser" method="post" > 姓名: <input name="userName" value="zhangsan" /> <br/> 年龄: <input name="age" value="18" /> <br/> 生日: <input name="birth" value="2019/12/10" /> <br/> 宠物: <input name="pet" value="啊猫,3" /> <input type="submit" value="保存" /> </form>
还是在WebMvcAutoConfiguration中配置:addFormatters
添加格式化器Formatters
或者类型转化器Converters
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters (FormatterRegistry registry) { registry.addConverter(new Converter <String, Pet>() { @Override public Pet convert (String source) { if (!StringUtils.isEmpty(source)){ Pet pet = new Pet (); String[] split = source.split("," ); pet.setName(split[0 ]); pet.setAge(Integer.parseInt(split[1 ])); return pet; } return null ; } }); } }
三、响应处理与内容协商 四、视图解析模板引擎 五、拦截器 六、文件上传 七、异常处理 八、原生组件Filter、Servlet、Listener注入 8.1 DispatcherServlet 底层注入方法 springBoot底层对于DispatcherServlet存在自动配置类:org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
底层对DispatcherServlet进行注入,并且绑定配置类WebMvcProperties.class(SpringMvc配置类)–>绑定配置文件前缀prefix = “spring.mvc”,然后还注入了一个文件上传解析器
底层将DispatcherServlet注入Spring容器:采用的还是DispatcherServletRegistrationConfiguration实质上采用的就是ServletRegistrationBean这种方式进行注册。
8.2 使用Servlet原生提供的API
在Spring配置类上提供Servlet原生组件的存放包注解 @ServletComponentScan(basePackages = “com.atguigu.admin”)
在原生组件的上面使用以下注解进行标注并继承对应的HttpServlet、实现对应的Filter、实现对应的ServletContextListener
@WebServlet(urlPatterns = “/my” ….. ) 会直接进行响应,不会通过DispatcherServlet进行分发,因为路径为精准匹配,有精准先精准
@WebFilter(urlPatterns={“/css/*“ ,“/images/*“ })
@WebListener listener会在Servlet容器启动时进行运行contextInitialized
,关闭时进行运行contextDestroyed
8.3 使用Spring提供的进行注册
这里最好保证为单实例,否则会出现注入新的servlet导致组件冗余
8.3.1 ServletRegistrationBean
声明一个配置类
注入一个ServletRegistrationBean
1 2 3 4 5 6 7 8 9 @Configuration(proxyBeanMethods = true) public class MyRegistConfig { @Bean public ServletRegistrationBean myServlet () { MyServlet myServlet = new MyServlet (); return new ServletRegistrationBean (myServlet,"/my" ,"/my02" ); } }
8.3.2 FilterRegistrationBean
声明一个配置类
注入一个FilterRegistrationBean
1 2 3 4 5 6 7 8 @Configuration(proxyBeanMethods = true) public class MyRegistConfig { @Bean public ServletRegistrationBean myServlet () { MyServlet myServlet = new MyServlet (); return new ServletRegistrationBean (myServlet,"/my" ,"/my02" ); } }
8.3.3 ServletListenerRegistrationBean
声明一个配置类
注入一个ServletListenerRegistrationBean
1 2 3 4 5 6 7 8 @Configuration(proxyBeanMethods = true) public class MyRegistConfig { @Bean public ServletListenerRegistrationBean myListener () { MySwervletContextListener mySwervletContextListener = new MySwervletContextListener (); return new ServletListenerRegistrationBean (mySwervletContextListener); } }
8.4 Web服务器自动配置原理以及定制化操作、 8.4.1 配置原理 8.4.2 切换Web服务器
TomcatServletWebServer
JettyServletWebServer
UndertowServletWebServer:默认不支持jsp
切换服务器方法:
引入web场景服务器,默认Web-start自动导入tomcat,因此要先排除tomcat场景
1 2 3 4 5 6 7 依赖中排除tomcat <exclusions > <exclusion > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > </exclusion > </exclusions >
引入其他 web场景服务器(版本均会自动仲裁)默认还是建议使用tomcat
1 2 3 4 5 6 7 8 9 10 11 tomcat场景启动器 <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > jetty场景启动器 <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jetty</artifactId > undertow场景启动器 <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-undertow</artifactId >
8.4.3 定制化Servlet容器
直接修改配置文件,修改server前缀即可 推荐
实现WebServerFactoryCustomizer
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.boot.web.server.WebServerFactoryCustomizer;import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;import org.springframework.stereotype.Component;@Component public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer <ConfigurableServletWebServerFactory> { @Override public void customize (ConfigurableServletWebServerFactory server) { server.setPort(9000 ); } }
自定义方法注入的ConfigurableServletWebServerFactory 会自动在容器中寻寻找已经实现的serveletServer,也就是如下图所示
直接注入一个ConfigurableServletWebServerFactory
示例:最后被返回工厂即可
九、数据库连接与访问 9.1 导入Starter场景启动器
导入场景启动器
1 2 3 4 5 6 7 8 9 10 11 12 maven自动版本仲裁,继承maven父类 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>填写Spring版本号</version> <relativePath/> <!-- lookup parent from repository --> </parent> Spring-jdbc maven启动器依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
值得注意的是,在SpringDataBase场景启动器中并未配置MySql或者其他类型的数据库驱动,因为Spring并不知道你要用哪种类型的数据库驱动,因此需要手动导入数据库驱动的依赖。
导入数据库驱动
Text 1 2 3 4 5 6 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
这里并不需要写数据库的版本号<version>5.1.49</version>
因为Spring以及提供了自动仲裁,如果需要自定义版本号,那么在maven
依赖中添加以下,覆盖Spring默认变量。
Text 1 2 3 <properties> <mysql.version>你需要使用的版本</mysql.version> </properties>
9.2 数据库自动配置原理 9.2.1 Spring数据库自动配置: SpringBoot默认配置HiKari的数据源:
SpringAutoConfig数据库自动配置包:org.springframework.boot.autoconfigure.jdbc
包含以下重点部分:
DataSourceTransactionManagerAutoConfiguration
数据库事务管理器 对应配置文件为prefix = "spring.datasource"
JdbcTemplateAutoConfiguration JDBCTemolete
小工具
绑定JdbcProperties
对应配置文件为 prefix = "spring.jdbc"
配置中导入了一个:JdbcTemplateConfiguration.class
JDBCTemplete类
JndiDataSourceAutoConfiguration
JNDI Java Name Directory Interface
JDNI实际上是一个通用资源名管理系统,不仅仅提供数据库定位,也可以给当前应用服务器所管理页面、网页、文件、连接池提供唯一标识符。名称—服务或者记录
XADataSourceAutoConfiguration
分布式事务相关的
9.2.2 数据库配置案例 1 2 3 4 5 6 spring: datasource: url: jdbc:mysql://localhost:3306 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver //mysql8.0以上版本之后驱动程序变化
9.3 使用Druid数据库连接池
官方文档:DruidGithub
9.3.1 配置Druid数据库连接池 9.3.1.1 手动配置基本Druid数据库连接池
配置Maven依赖:
Text 1 2 3 4 5 6 7 <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency>
注意SpringBoot默认配置Hikari数据源,观察配置流程可以得知,当我们手动注入数据源的时候,会默认使用我们注入的,因为`@ConditionalInMIssingBean(DataSource.class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Configuration public class MyDataSourceConfig { public DataSource dataSource () throws SQLException { DruidDataSource druidDataSource = new DruidDataSource (); return druidDataSource; }
绑定数据库配置:
可以使用Set方法向自己定义的数据源中注入属性
绑定配置文件@ConfigurationProperties("spring.datasource")
之所以绑定这个配置文件,是因为配置文件中的大部分属性名称与注入的数据源的成员变量一致,Spring会自动配置文件绑定。
9.3.1.2 使用Druid数据库连接池高级功能
配置Druid的内置监控页面官方配置方法
根据官方文档配置spring:实际上是配置了一个Servlet处理Druid数据库连接池的Web页面响应。
1 2 3 4 5 6 7 8 <servlet > <servlet-name > DruidStatView</servlet-name > <servlet-class > com.alibaba.druid.support.http.StatViewServlet</servlet-class > </servlet > <servlet-mapping > <servlet-name > DruidStatView</servlet-name > <url-pattern > /druid/*</url-pattern > </servlet-mapping >
因此只需要给容器中注入一个Servlet即可:*使用SpringBoot原生Web组件注册方式 ***ServletRegistrationBean **
1 2 3 4 5 6 7 8 9 10 11 12 @Bean public ServletRegistrationBean statViewServlet () { StatViewServlet statViewServlet = new StatViewServlet (); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean <>(statViewServlet, "/druid/*" ); registrationBean.addInitParameter("loginUsername" ,"admin" ); registrationBean.addInitParameter("loginPassword" ,"123456" ); return registrationBean; }
如果需要给官方Servlet注入初始化属性,那么就使用ServletRegistrationBean
提供的方法addInitParameter()
来注入初始化属性。
配置Druid数据库连接池监控和统计功能
官方文档介绍到spring中,只需要给数据库连接池配置一个初始化属性filter的值即可
1 2 3 4 <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > ... ... <property name ="filters" value ="stat" /> </bean >
Springboot中将其数据源属性中添加:
1 druidDataSource.setFilters("stat" );
开启Web请求的监控
官方文档 中描述,需要向容器中放入一个Filter:
1 2 3 4 5 6 7 8 9 10 11 12 <filter > <filter-name > DruidWebStatFilter</filter-name > <filter-class > com.alibaba.druid.support.http.WebStatFilter</filter-class > <init-param > <param-name > exclusions</param-name > 需要排除的静态资源 <param-value > *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*</param-value > </init-param > </filter > <filter-mapping > <filter-name > DruidWebStatFilter</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
在这里SpringBoot使用FilterRegistrationBean
进行配置:
1 2 3 4 5 6 7 8 9 10 @Bean public FilterRegistrationBean webStatFilter () { WebStatFilter webStatFilter = new WebStatFilter (); FilterRegistrationBean<WebStatFilter> filterRegistrationBean = new FilterRegistrationBean <>(webStatFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/*" )); filterRegistrationBean.addInitParameter("exclusions" ,"*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" ); return filterRegistrationBean; }
设置拦截路径setUrlPatterns()
设置初始化参数addInitParameter()
配置Sql注入防火墙
官方配置 :只需要给数据库连接池的Filter属性增加一个值即可
只开启防火墙:
1 2 3 4 <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > ... <property name ="filters" value ="wall" /> </bean >
与其他功能Filter一起使用,比如用于统计监控信息的statFilter
1 2 3 4 <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" init-method ="init" destroy-method ="close" > ... <property name ="filters" value ="wall,stat" /> </bean >
在SpringBoot中同理只需要添加一组Filter属性对应的值即可:
1 druidDataSource.setFilters("stat,wall" );
开启Sping以及Session的监控
官方配置 中,注入了一个Druid的拦截器
9.3.1.3 使用场景启动器配置 使用官方场景启动器自动配置maven依赖:
1 2 3 4 5 6 <dependency > <groupId > com.alibaba</groupId > <artifactId > druid-spring-boot-starter</artifactId > <version > 1.2.8</version > </dependency >
可以看到自动配置中,绑定了DruidStatProperties.class, DataSourceProperties.class
这两个配置类,分别绑定了spring.datasource.druid
和spring.datasource
这两个配置文件前缀,因此只需要在配置文件中修改响应的属性即可。
DruidSpringAopConfiguration.class 配置监控Spring
DruidStatViewServletConfiguration.class 开启监控页面
观察配置类可以发现:默认关闭状态监控页面Servlet
DruidWebStatFilterConfiguration.class 配置监控和统计功能
DruidFilterConfiguration.class配置开启哪些功能,比如stat和防火墙功能
配置示例:
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 spring: datasource: url: jdbc:mysql://localhost:3306/db_account username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver druid: aop-patterns: com.atguigu.admin.* filters: stat,wall,slf4j stat-view-servlet: enabled: true login-username: admin login-password: admin resetEnable: false web-stat-filter: enabled: true urlPattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filter: stat: slow-sql-millis: 1000 logSlowSql: true enabled: true wall: enabled: true config: drop-table-allow: false
常用功能配置列表
9.4 使用MyBatis 9.4.1 使用Starter配置 官方SpringBoot地址:GIthub-Mybatis
1 2 3 4 <dependency > <artifactId > mybatis-spring-boot-starter</artifactId > <name > mybatis-spring-boot-starter</name > </dependency >
9.4.2 自动配置原理
引入pom依赖之后,会增加jdbc 以及SpringBoot-Mybatis依赖文件
并且在SpringBoot启动之后会自动加载org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
两个配置类
自动配置类MybatisAutoConfiguration
自动配置部分内容:
1. 自动配置`SqlSession---打开一个SqlSession会话的工厂类`
2. 自动配置`sqlSessionTemplate----是一个SqlSession的具体实现类`
3. 额外的会自动注入`@Import(AutoConfiguredMapperScannerRegistrar.class)` 实际上观察配置源码可以发现,只要我们使用Mapper注解Mapper接口就会被自动配置:
9.4.3 Mybatis手动配置
配置MyBatis配置文件,在全局配置yaml文件中配置mybatis
1 2 3 4 5 6 mybatis: config-location: classpath:mybatis/mybatis-config.xml //mybatis全局配置文件 mapper-locations: classpath:mybatis/mapper/*.xml //mapper为之 configuration: map-underscore-to-camel-case: true
编写Mapper接口,标注@Mapper
接口注解
编写Mapper.xml文件
或者配置yaml配置文件中的属性,就相当他于更改mybatis全局配置文件中的值。也就是在yaml中配置了,那么mybatis全局配置文件就不要写,不能同时存在!!!
9.4.4 Mybatis注解整合
SpringBoot初始化项目时进行引入整合SpringBoot-mybatis-starter
1 2 3 注解编写Mapper文件: @Select("select * from city where id=#{id}") public City getById (Long id) ;
需要增加mapper接口的功能,比如增加主键自增等…
1 2 增加Option注解标签 @Options(useGeneratedKeys = true,keyProperty = "id")
9.4.4 最佳使用方法
引入mybatis-starter
配置application.xml 文件内中的mybatis配置规则
在**application.xml 指定Mapper.xml 所在位置**
编写Mapper接口,并且使用 @Mapper ** 注解进行标注,或者在SpringBoot某一个配置类上标注****@mapperScan**** 指定包扫描Mapper**
简单方法使用 @Select @Delete @Insert @Update ** 等注解直接编写,使用****@Option**** 注解增加当前Mapper功能**
复杂文件在mapper.xml中进行编写即可。
9.5 使用MyBatis Plus 官方文档:Mybatis-Plus
9.5.1 配置MyBatisPlus 引入官方Stater配置依赖:
1 2 3 4 5 <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-boot-starter</artifactId > <version > 3.5.1</version > </dependency >
9.5.2 自动配置原理
引入Starter后,会自动导入如下依赖,也就是说,使用MybatisPlus之后不需要再重复引入**mybatis jdbc 的相关启动器**
自动配置类会在Springboot加载之后,自动再容器中注入几个配置类:
1 2 3 4 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
自动配置类:MybatisPlusAutoConfiguration
自动配置绑定的配置类@EnableConfigurationProperties(MybatisPlusProperties.class)
绑定配置文件前缀:mybatis-plus
自动注入SqlSessionFactory
实际上也自动配置了Mapper-loction的位置:任意包的类路径下的所有Mapper文件夹下的任意路径都是mapper.xml的sql映射文件路径。
也会自动配置SqlSessionFactory
自动注册Mapper注解标注的mapper文件
9.5.3 使用MBP开发 9.5.4 基础开发
引入数据源配置
引入MBP启动器
开启MapperScan或者Mapper注解
编写实体类
编写对应的Mapper文件(继承基类BaseMapper 你需要操作的类)
BaseMapper提供了一些基本的增删改查的方法,****并且可以使用@TableField(exist=false) 注解标注表示当前属性在表中不存在。
注意:
实体类名称与表名不一致
9.5.4 mybatisPlus简化开发Service
IService 规定一系列的增删改查的业务方法,简化Service编写
IService顶级接口,存在一个实现类 ServiceImpl<M extends BaseMapper, T>实现了顶级方法的增删改查方法
可以支持常见的查询集合,分页数据等….
使用方法:
使用自定义Service接口继承顶级接口IService
自定义的ServiceImp继承顶级接口的实现类ServiceImpl,并且实现自定义Service接口方法
分页开发:****官方地址
需要配置分页插件才可以使用:在配置文件中注入MybatisPlusInterceptor,这个拦截器中可以执行默认的拦截器,另外存在一个**public void addInnerInterceptor(InnerInterceptor innerInterceptor) 方法,添加了一个***InnerInterceptor 打开实现可知:一个分页的拦截器 PaginationInnerInterceptor * 。
因此在使用分页插件的时候,只需要注入通用的MybatisPlusInterceptor **** 拦截器,并且调用 addInnerInterceptor(InnerInterceptor innerInterceptor)** 向其中添加分页拦截器PaginationInnerInterceptor 即可**。
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 @MapperScan("scan.your.mapper.package") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor () { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor (); interceptor.addInnerInterceptor(new PaginationInnerInterceptor ()); return interceptor; } @Bean public ConfigurationCustomizer configurationCustomizer () { return configuration -> configuration.setUseDeprecatedExecutor(false ); } }
调用顶级接口IService接口中被ServiceImpl实现的方法:default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper
传入两个参数:
1. page:分页对象——封装分页参数信息
2. `queryWrapp` :条件封装对象
示例:
1 2 3 4 Page<User> page = new Page <>(pn, 2 ); Page<User> userPage = userService.page(page, null );
Themeleaf模板引擎也支持,路径变量参数传递等功能,参照官方文档
十、单元测试Junit5 SpringBoot整合Junit以后。
编写测试方法:@Test标注(注意需要使用junit5版本的注解)
Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
10.1 配置Junit5 SpringBoot引入Junit5测试框架:
1 2 3 4 5 6 引入junit5官方启动器pom依赖: <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency >
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的测试引擎。
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖,默认不支持Junit4,3
10.2 支持Junit4 1 2 3 4 5 6 7 8 9 10 11 12 引入POM依赖: <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 >
10.3 Junit5注解 JUnit5的注解与JUnit4的注解有所变化
https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
**@Test :**表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
**@ParameterizedTest :**表示方法是参数化测试,下方会有详细介绍
**@RepeatedTest :**表示方法可重复执行,下方会有详细介绍
**@DisplayName :**为测试类或者测试方法设置展示名称
**@BeforeEach :**表示在每个单元测试之前执行
**@AfterEach :**表示在每个单元测试之后执行
**@BeforeAll :**表示在所有单元测试之前执行
**@AfterAll :**表示在所有单元测试之后执行
**@Tag :**表示单元测试类别,类似于JUnit4中的@Categories
**@Disabled :**表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
**@Timeout :**表示测试方法运行如果超过了指定时间将会返回错误
**@ExtendWith :**为测试类或测试方法提供扩展类引用
10.4 断言Assertions 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法 。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告:
10.4.1 简单断言
方法
说明
assertEquals
判断两个对象或两个原始类型是否相等
assertNotEquals
判断两个对象或两个原始类型是否不相等
assertSame
判断两个对象引用是否指向同一个对象
assertNotSame
判断两个对象引用是否指向不同的对象
assertTrue
判断给定的布尔值是否为 true
assertFalse
判断给定的布尔值是否为 false
assertNull
判断给定的对象引用是否为 null
assertNotNull
判断给定的对象引用是否不为 null
10.4.2 数组断言 通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
1 2 3 4 5 @Test @DisplayName("array assertion") public void array () { assertArrayEquals(new int []{1 , 2 }, new int [] {1 , 2 }); }
10.4.3 组合断言 assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
1 2 3 4 5 6 7 8 @Test @DisplayName("assert all") public void all () { assertAll("Math" , () -> assertEquals(2 , 1 + 1 ), () -> assertTrue(1 > 0 ) ); }
10.4.4 异常断言 JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用
1 2 3 4 5 6 7 @Test @DisplayName("异常测试") public void exceptionTest () { ArithmeticException exception = Assertions.assertThrows( ArithmeticException.class, () -> System.out.println(1 % 0 )); }
10.4.5 超时断言 Assertions.assertTimeout()为测试方法设置了超时时间
1 2 3 4 5 6 @Test @DisplayName("超时测试") public void timeoutTest () { Assertions.assertTimeout(Duration.ofMillis(1000 ), () -> Thread.sleep(500 )); }
10.4.6 快速失败 fail方法后的代码不进行执行
1 2 3 4 5 @Test @DisplayName("fail") public void shouldFail () { fail("This should fail" ); }
10.5 假设Assumptions (assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @DisplayName("前置条件") public class AssumptionsTest { private final String environment = "DEV" ; @Test @DisplayName("simple") public void simpleAssume () { assumeTrue(Objects.equals(this .environment, "DEV" )); assumeFalse(() -> Objects.equals(this .environment, "PROD" )); } @Test @DisplayName("assume then do") public void assumeThenDo () { assumingThat( Objects.equals(this .environment, "DEV" ), () -> System.out.println("In DEV" ) ); } }
10.6 嵌套测试 使用内部类和@Nested注解实现嵌套测试,内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。外部测试方法无法驱动内部的BeforeEach以及AfterEach,但是内部可以驱动外部!!
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 () { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked () { 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 () { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped () { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked () { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
10.7 参数化测试
当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider 接口,任何外部文件都可以作为它的入参。
@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 @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" ); }
十一、指标监控
SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。https://github.com/codecentric/spring-boot-admin ** spring可视化管理页面**
11.1 使用方法
引入SpringBoot Actuator启动器
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
配置文件中进行配置
1 2 3 4 5 6 management: endpoints: enabled-by-default: true web: exposure: include: '*'
ID
描述
auditevents
暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
。
beans
显示应用程序中所有Spring Bean的完整列表。
caches
暴露可用的缓存。
conditions
显示自动配置的所有条件信息,包括匹配或不匹配的原因。
configprops
显示所有@ConfigurationProperties
。
env
暴露Spring的属性ConfigurableEnvironment
flyway
显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway
组件。
health
显示应用程序运行状况信息。
httptrace
显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository
组件。
info
显示应用程序信息。
integrationgraph
显示Spring integrationgraph
。需要依赖spring-integration-core
。
loggers
显示和修改应用程序中日志的配置。
liquibase
显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase
组件。
metrics
显示当前应用程序的“指标”信息。
mappings
显示所有@RequestMapping
路径列表。
scheduledtasks
显示应用程序中的计划任务。
sessions
允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
shutdown
使应用程序正常关闭。默认禁用。
startup
显示由ApplicationStartup
收集的启动步骤数据。需要使用SpringApplication
进行配置BufferingApplicationStartup
。
threaddump
执行线程转储。
11.2 Health Endpoint 健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
Text 1 2 开启health的显示详细 endpoint.health.show-details=always
重要的几点:
health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告有任何一个应用是宕机状态,整个就是宕机状态
很多的健康检查默认已经自动配置好了,比如:数据库、redis等
可以很容易的添加自定义的健康检查机制
11.3 Metrics Endpoint 提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
通过Metrics对接多种监控系统
简化核心Metrics开发
添加自定义Metrics或者扩展已有Metrics
11.4 管理Endpoints 11.4.1 开启与禁用Endpoints
默认所有的Endpoint除过shutdown都是开启的。
需要开启或者禁用某个Endpoint。配置模式为 management.endpoint..enabled = true
1 2 3 4 5 6 7 8 9 10 11 12 13 management: endpoint: beans: enabled: true 或者禁用所有的Endpoint然后手动开启指定的Endpoint management: endpoints: enabled-by-default: false endpoint: beans: enabled: true health: enabled: true
11.4.2 暴露Endpoints 支持的暴露方式
HTTP:默认只暴露health 和info Endpoint
JMX :默认暴露所有Endpoint
除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
ID
JMX
Web
auditevents
Yes
No
beans
Yes
No
caches
Yes
No
conditions
Yes
No
configprops
Yes
No
env
Yes
No
flyway
Yes
No
health
Yes
Yes
heapdump
N/A
No
httptrace
Yes
No
info
Yes
Yes
integrationgraph
Yes
No
jolokia
N/A
No
logfile
N/A
No
loggers
Yes
No
liquibase
Yes
No
metrics
Yes
No
mappings
Yes
No
prometheus
N/A
No
scheduledtasks
Yes
No
sessions
Yes
No
shutdown
Yes
No
startup
Yes
No
threaddump
Yes
No
11.4.3 定制化EndPoints 11.4.3.1 定制化Health信息
继承抽象类AbstractHealthIndicator
实现其中的doHealthCheck()
方法
使用@Component注解注入容器
开启配置文件中,显示详情信息
1 2 3 4 management: health: enabled: true show-details: always
使用示例:
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 @Component public class MyComHealthIndicator extends AbstractHealthIndicator { @Override protected void doHealthCheck (Health.Builder builder) throws Exception { Map<String,Object> map = new HashMap <>(); if (1 == 2 ){ builder.status(Status.UP); map.put("count" ,1 ); map.put("ms" ,100 ); }else { builder.status(Status.OUT_OF_SERVICE); map.put("err" ,"连接超时" ); map.put("ms" ,3000 ); } builder.withDetail("code" ,100 ) .withDetails(map); } }
10.4.3.2 定制化Info信息 10.4.3.2.1 使用配置文件 1 2 3 4 5 info: appName: boot-admin version: 2.0 .1 mavenProjectName: @project.artifactId@ mavenProjectVersion: @project.version@
10.4.3.2.2 使用InfoContributer
访问:http://localhost:8080/actuator/info 返回详细信息
实现InfoContributer接口
使用@Component注入容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.Collections;import org.springframework.boot.actuate.info.Info;import org.springframework.boot.actuate.info.InfoContributor;import org.springframework.stereotype.Component;@Component public class ExampleInfoContributor implements InfoContributor { @Override public void contribute (Info.Builder builder) { builder.withDetail("example" , Collections.singletonMap("key" , "value" )); } }
10.4.3.2.3 定制Metrics SpringBoot支持自动适配的Metrics
JVM metrics, report utilization of:
Various memory and buffer pools
Statistics related to garbage collection
Threads utilization
Number of classes loaded/unloaded
CPU metrics
File descriptor metrics
Kafka consumer and producer metrics
Log4j2 metrics: record the number of events logged to Log4j2 at each level
Logback metrics: record the number of events logged to Logback at each level
Uptime metrics: report a gauge for uptime and a fixed gauge representing the application’s absolute start time
Tomcat metrics (server.tomcat.mbeanregistry.enabled
must be set to true
for all Tomcat metrics to be registered)
Spring Integration metrics
通过在业务逻辑方法构造函数中传入**MeterRegistry 类也就是Metrics的注册中心,在构造方法中调用注册中心,进行注册一个新的指标**
1 2 3 4 5 6 7 8 9 class MyService { Counter counter; public MyService (MeterRegistry meterRegistry) { counter = meterRegistry.counter("myservice.method.running.counter" ); } public void hello () { counter.increment(); } }
10.4.3.3 自定义监控端点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Component @Endpoint(id = "container") public class DockerEndpoint { @ReadOperation public Map getDockerInfo () { return Collections.singletonMap("info" ,"docker started..." ); } @WriteOperation private void restartDocker () { System.out.println("docker restarted...." ); } }
十二、配置文件解析 12.1 Profile功能 为了方便多环境适配,springboot简化了profile功能。
12.2 application-profile功能
默认配置文件 application.yaml;任何时候都会加载
指定环境配置文件 application-{env}.yaml
激活指定环境
配置文件激活
spring.profiles.active
命令行激活:java -jar xxx.jar –spring.profiles.active=prod –person.name=haha
修改配置文件的任意值,命令行优先
默认配置与环境配置同时生效
同名配置项,profile配置优先
12.3 @Profile条件装配功能 Text 1 2 3 4 5 6 7 @Configuration(proxyBeanMethods = false) @Profile("production") public class ProductionConfiguration { // ... }
12.4 profile分组 Text 1 2 3 4 spring.profiles.group.production[0]=proddb spring.profiles.group.production[1]=prodmq 使用:--spring.profiles.active=production 激活
12.5 配置文件应用方法
常用:Java属性文件、YAML文件、环境变量、命令行参数;
12.5.1 配置文件查找位置 (1) classpath 根路径
(2) classpath 根路径下config目录
(3) jar包当前目录
(4) jar包当前目录的config目录
(5) /config子目录的直接子目录
12.5.2 配置文件加载顺序:
当前jar包内部的application.properties和application.yml
当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
引用的外部jar包的application.properties和application.yml
引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
12.5.3 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项 十三、自定义Starter
新建 一个空的Maven项目
新建一个启动器模块—->
新建一个自动配置模块—->
Autoconfigure包中配置使用 META-INF/spring.factories 中 EnableAutoConfiguration 的值,使得项目启动加载指定的自动配置类
编写自动配置类 xxxAutoConfiguration -> xxxxProperties
@Configuration
@Conditional
@EnableConfigurationProperties
@Bean
……
启动器模块中pom引入自动配置模块