Spring Boot provides auto-configuration for Spring MVC that works well with most applications。(大多场景我们都无需自定义配置)
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of ContentNegotiatingViewResolver
and BeanNameViewResolver
beans.
内容协商视图解析器和BeanName视图解析器
Support for serving static resources, including support for WebJars (covered later in this document)).
静态资源(包括webjars)
Automatic registration of Converter
, GenericConverter
, and Formatter
beans.
自动注册 Converter,GenericConverter,Formatter
Support for HttpMessageConverters
(covered later in this document).
支持 HttpMessageConverters
(后来我们配合内容协商理解原理)
Automatic registration of MessageCodesResolver
(covered later in this document).
自动注册 MessageCodesResolver
(国际化用)
Static index.html
support.
静态index.html 页支持
Custom Favicon
support (covered later in this document).
自定义 Favicon
Automatic use of a ConfigurableWebBindingInitializer
bean (covered later in this document).
自动使用 ConfigurableWebBindingInitializer`,(DataBinder负责将请求数据绑定到JavaBean上)
If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own
@Configuration
class of typeWebMvcConfigurer
but without@EnableWebMvc
.不用@EnableWebMvc注解。使用@Configuration+WebMvcConfigurer自定义规则
If you want to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
, orExceptionHandlerExceptionResolver
, and still keep the Spring Boot MVC customizations, you can declare a bean of typeWebMvcRegistrations
and use it to provide custom instances of those components.声明WebMvcRegistrations改变默认底层组件
If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
, or alternatively add your own@Configuration
-annotatedDelegatingWebMvcConfiguration
as described in the Javadoc of@EnableWebMvc
.使用@EnableWebMvc+@Configuration+DelegatingWebMvcConfiguration 全面接管SpringMVC
只要静态资源放在类路径下: called /static
(or /public
or /resources
or /META-INF/resources
)
访问 : 当前项目根路径(类路径)/ + 静态资源名
例如:http://localhost:8080/resources/1.jpg
原理: 静态映射/**,即只要写资源名就可以了。
请求进来,先去找Controller看能不能处理。不能处理的所有请求都交给静态资源处理器。静态资源也找不到则响应404页面
spring:
mvc:
# 1.改变默认的静态资源路径,即请求静态资源必须加/res的路径前缀;示例:http://localhost:8080/res/5.jpg
static-path-pattern: /res/**
web:
resources:
# 2.修改静态资源的存放路径;设置以后只有该路径下的静态资源才能访问,默认配置全部失效;可采用数组设置多个。
static-locations: [ classpath:/static_resources/ ]
# static-locations: classpath:/static_resources/
当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找
自动映射 /webjars/**
https://www.webjars.org/
js,css等资源将其打包成jar包,通过maven引入
<dependency>
<groupId>org.webjarsgroupId>
<artifactId>jqueryartifactId>
<version>3.6.0version>
dependency>
请求路径为:http://localhost:8080/webjars/jquery/3.6.0/jquery.js
静态资源路径下index.html
可以配置静态资源路径
但是不可以配置静态资源的访问前缀static-path-pattern。否则导致 index.html不能被默认访问
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致welcome page功能失效
resources:
static-locations: [classpath:/haha/]
controller能处理/index
favicon.ico 放在静态资源目录下即可。
spring:
# mvc:
# static-path-pattern: /res/** 这个会导致 Favicon 功能失效
SpringBoot启动默认加载xxxAutoConfiguration 类(自动配置类)
加载SpringMVC功能的自动配置类WebMvcAutoConfiguration,生效
补充:WebMvcAutoConfigurationAdapter有一个有参构造器
有参构造器所有参数的值都会从容器中确定
//ResourceProperties resourceProperties; 获取和spring.resources绑定的所有的值的对象
//WebMvcProperties mvcProperties 获取和spring.mvc绑定的所有的值的对象
//ListableBeanFactory beanFactory Spring的beanFactory,spring的容器
//HttpMessageConverters 找到所有的HttpMessageConverters
//ResourceHandlerRegistrationCustomizer 找到 资源处理器的自定义器。=========
//DispatcherServletPath
//ServletRegistrationBean 给应用注册Servlet、Filter....
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
this.servletRegistrations = servletRegistrations;
}
WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers资源处理的默认规则,因为这是配置类,容器启动时调用这些方法
public void addResourceHandlers(ResourceHandlerRegistry registry) {
//resourceProperties:相当于拿到配置文件下,spring.resources下的所有配置信息
//add-mappings:开启静态资源映射规则,默认是true。如果设置为false就不会进行静态资源的配置
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});
}
});
}
}
spring:
# mvc:
# static-path-pattern: /res/**
resources:
add-mappings: false 禁用所有静态资源规则
Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
核心Filter;HiddenHttpMethodFilter
用法: 表单method=post,隐藏域 _method=put
SpringBoot中手动开启;SpringBoot中默认是不开启REST风格的,需要在配置文件中开启
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
示例:
@RequestMapping(value = "/user", method = RequestMethod.GET)
@ResponseBody
public String getUser() {
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody
public String saveUser() {
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
@ResponseBody
public String putUser() {
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
@ResponseBody
public String deleteUser() {
return "DELETE-张三";
}
REST原理(表单提交要使用REST的时候)
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);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
SpringMVC功能分析都从 org.springframework.web.servlet.DispatcherServlet --> doDispatch()
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 找到当前请求使用哪个Handler(Controller的方法)处理
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
//HandlerMapping:处理器映射。/xxx->>xxxx
HandlerMapping mapping = (HandlerMapping)var2.next();
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。
SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
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;
}
@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody
@RequestMapping(value = "car/{id}/owner/{username}", method = RequestMethod.GET)
@ResponseBody
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> requestHeader,
@RequestParam("age") Integer age,
@RequestParam("inters") List<String> Inters,
@RequestParam Map<String, String> params,
@RequestBody String content) {
Map<String, Object> map = new HashMap<>();
map.put("id", id);
map.put("name", name);
map.put("pv", pv);
map.put("userAgent", userAgent);
map.put("requestHeader", requestHeader);
map.put("age", age);
map.put("Inters", Inters);
map.put("params", params);
map.put("content", content);
return map;
}
@PostMapping(value = "save")
@ResponseBody
public Map postMethod(@RequestBody String context) {
Map<String, Object> map = new HashMap<>();
map.put("context", context);
return map;
}
WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId
略,参考SpringMVC
Map、**Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、**Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder
可以自动类型转换与格式化,可以级联封装。
jackson.jar+@ResponseBody
pom.xml中引入spring-boot-starter-web
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
spring-boot-starter-web引入spring-boot-starter-json
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jsonartifactId>
<version>2.4.10version>
<scope>compilescope>
dependency>
spring-boot-starter-json中引入jackson作为底层JSON处理工具
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.11.4version>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-jdk8artifactId>
<version>2.11.4version>
<scope>compilescope>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-jsr310artifactId>
<version>2.11.4version>
<scope>compilescope>
dependency>
示例:
@RequestMapping("/test/person")
@ResponseBody
public Person getPerson() {
Person person = new Person();
person.setAge(11);
person.setUserName("zhangsan");
person.setBrith(new Date());
return person;
}
引入xml依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
dependency>
postman分别测试返回json和xml
视图解析:Spring Boot在处理请求后跳转到某个页面的过程。
Spring Boot默认打包方式是一个jar包,JSP不支持在压缩包编译,因此SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染。
render(mv, request, response); 进行页面渲染逻辑
根据方法的String返回值得到 View 对象【定义了页面的渲染逻辑】
所有的视图解析器尝试是否能根据当前返回值得到View对象
得到了 redirect:/main.html --> Thymeleaf new RedirectView()
ContentNegotiationViewResolver 里面包含了下面所有的视图解析器,内部还是利用下面所有视图解析器得到视图对象。
view.render(mv.getModelInternal(), request, response); 视图对象调用自定义的render进行页面渲染工作
RedirectView 如何渲染【重定向到一个页面】
获取目标url地址
response.sendRedirect(encodedURL);
视图解析:
Spring Boot引入的spring-boot-starter-web默认配置了视图解析器ThymeleafViewResolve,前后缀为ThymeleafProperties里的默认值,无需用户配置。
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
private boolean checkTemplate = true;
private boolean checkTemplateLocation = true;
private String prefix = "classpath:/templates/";
private String suffix = ".html";
private String mode = "HTML";
……
}
Thymeleaf is a modern server-side Java template engine for both web and standalone environments, capable of processing HTML, XML, JavaScript, CSS and even plain text.
现代化、服务端Java模板引擎
表达式
表达式名字 | 语法 | 用途 |
---|---|---|
变量取值 | ${…} | 获取请求域、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)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
设置单个值
<form action="subscribe.html" th:attr="action=@{/subscribe}">
<fieldset>
<input type="text" name="email" />
<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
fieldset>
form>
设置多个值
<img src="../../images/gtvglogo.png" th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />
以上两个的代替写法 th:xxxx
<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">
所有h5兼容的标签写法
https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#setting-value-to-specific-attributes
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onionstd>
<td th:text="${prod.price}">2.41td>
<td th:text="${prod.inStock}? #{true} : #{false}">yestd>
tr>
<tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'">
<td th:text="${prod.name}">Onionstd>
<td th:text="${prod.price}">2.41td>
<td th:text="${prod.inStock}? #{true} : #{false}">yestd>
tr>
<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">viewa>
<div th:switch="${user.role}">
<p th:case="'admin'">User is an administratorp>
<p th:case="#{roles.manager}">User is a managerp>
<p th:case="*">User is some other thingp>
div>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({ThymeleafProperties.class})
@ConditionalOnClass({TemplateMode.class, SpringTemplateEngine.class})
@AutoConfigureAfter({WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class})
public class ThymeleafAutoConfiguration {
……
}
自动配好的策略
所有thymeleaf的配置值都在 ThymeleafProperties
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING;
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
配置好了 SpringTemplateEngine(模板引擎)
Thymeleaf的视图解析器ThymeleafViewResolver已经自动配好了,前后缀就是在ThymeleafProperties里的默认值。无需用户配置。
我们只需要直接开发页面
@RequestMapping("/success")
public ModelAndView success() {
ModelAndView modelAndView = new ModelAndView("success");
modelAndView.addObject("msg", "Stonebridge");
return modelAndView;
}
DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1 th:text="${msg}">h1>
<h2>
<a th:href="${link}">去百度a> <br/>
h2>
body>
html>
server:
servlet:
context-path: /world
ContextPath must start with ‘/’ and not end with ‘/’
thymeleaf、web-starter、devtools、lombok
自动配置好,我们只需要把所有静态资源放到 static 文件夹下
th:action="@{/login}"
th:insert/replace/include
防止用户登录后重新刷新页面导致重新提交表单到@PostMapping("/login"),因此登录后跳转到@GetMapping("/main.html"),防止重复刷新。
/**
* 来登录页
* @return
*/
@GetMapping(value = {"/","/login"})
public String loginPage(){
return "login";
}
@PostMapping("/login")
public String main(User user, HttpSession session, Model model){ //RedirectAttributes
if(StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
//把登陆成功的用户保存起来
session.setAttribute("loginUser",user);
//登录成功重定向到main.html; 重定向防止表单重复提交
return "redirect:/main.html";
}else {
model.addAttribute("msg","账号密码错误");
//回到登录页面
return "login";
}
}
/**
* 去main页面
* @return
*/
@GetMapping("/main.html")
public String mainPage(HttpSession session,Model model){
log.info("当前方法是:{}","mainPage");
//是否登录。 拦截器,过滤器
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
return "main";
}else {
//回到登录页面
model.addAttribute("msg","请重新登录");
return "login";
}
}
@GetMapping("/dynamic_table")
public String dynamic_table(Model model){
//表格内容的遍历
List<User> users = Arrays.asList(new User("zhangsan", "123456"),
new User("lisi", "123444"),
new User("haha", "aaaaa"),
new User("hehe ", "aaddd"));
model.addAttribute("users",users);
return "table/dynamic_table";
}
<table class="display table table-bordered" id="hidden-table-info">
<thead>
<tr>
<th>#th>
<th>用户名th>
<th>密码th>
tr>
thead>
<tbody>
<tr class="gradeX" th:each="user,stats:${users}">
<td th:text="${stats.count}">Tridenttd>
<td th:text="${user.userName}">Internettd>
<td >[[${user.password}]]td>
tr>
tbody>
table>
在preHandle中被拦截后不要使用重定向到初始化页面,因为携带信息无法传递到页面。应该采用转发的方式。
/**
* 登录检查
* 1、配置好拦截器要拦截哪些请求
* 2、把这些配置放在容器中
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
/**
* 目标方法执行之前
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}", requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if (loginUser != null) {
//放行
return true;
}
//拦截住。未登录。跳转到登录页
request.setAttribute("msg", "请先登录");
//被拦截后不要使用重定向到登录页面。因为msg信息无法传递到页面。应该采用转发的方式。
// re.sendRedirect("/");
request.getRequestDispatcher("login").forward(request, response);
return false;
}
/**
* 目标方法执行完成以后
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle执行{}", modelAndView);
}
/**
* 页面渲染以后
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion执行异常{}", ex);
}
}
指定拦截规则【如果是拦截所有,静态资源也会被拦截】
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**"); //放行的请求
}
}
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data” SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
<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="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
div>
//单文件
<div class="form-group">
<label for="exampleInputFile">头像label>
<input type="file" name="headerImg" id="exampleInputFile">
div>
//多文件
<div class="form-group">
<label for="exampleInputFile">生活照label>
<input type="file" name="photos" multiple>
div>
<button type="submit" class="btn btn-primary">提交button>
form>
/**
* MultipartFile 自动封装上传过来的文件
* @param email
* @param username
* @param headerImg
* @param photos
* @return
*/
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
log.info("上传的信息:email={},username={},headerImg={},photos={}",
email,username,headerImg.getSize(),photos.length);
if(!headerImg.isEmpty()){
//保存到文件服务器,OSS服务器
String originalFilename = headerImg.getOriginalFilename();
headerImg.transferTo(new File("H:\\cache\\"+originalFilename));
}
if(photos.length > 0){
for (MultipartFile photo : photos) {
if(!photo.isEmpty()){
String originalFilename = photo.getOriginalFilename();
photo.transferTo(new File("H:\\cache\\"+originalFilename));
}
}
}
return "main";
}
文件上传的配置都是在MultipartAutoConfiguration完成自动配置的,其相关的属性都是都在MultipartProperties类进行配置。例如文件的上传大小配置了默认值,与文件上传相关的所有属性都是以spring.servlet.multipart为前缀的。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.servlet.multipart",name = {"enabled"},matchIfMissing = true)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties({MultipartProperties.class})
public class MultipartAutoConfiguration {
……
}
例如:
spring:
servlet:
multipart:
max-file-size: 2MB
max-request-size: 100MB
使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws
IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/images/1552.jpg");
//创建输入流s
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要<下载方式>以及<下载文件>的名字,只有filename的值可以自定义。
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
//bytes:响应体
//headers:响应头
//statusCode:状态码
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
默认情况下,Spring Boot提供/error处理所有错误的映射
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据。
要对其进行自定义,添加View
解析为error
要完全替换默认行为,可以实现 ErrorController
并注册该类型的Bean定义,或添加ErrorAttributes类型的组件
以使用现有机制但替换其内容。
error/下的4xx,5xx页面会被自动解析;
4xx错误
<section class="error-wrapper text-center">
<h1><img alt="" src="images/404-error.png">h1>
<h2 th:text="${status}">page not foundh2>
<h3 th:text="${message}">We Couldn’t Find This Pageh3>
//跳转到首页
<a class="back-btn" th:href="@{/main.html}"> Back To Homea>
section>
5xx错误
<section class="error-wrapper text-center">
<h1><img alt="" src="images/500-error.png">h1>
<h2>OOOPS!!!h2>
//异常信息
<h3 th:text="${message}">Something went wrong.h3>
//错误详情
<p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#">contact our supporta> if the problem persists.p>
//状态码
<a class="back-btn" href="index.html" th:text="${status}"> Back To Homea>
section>
ErrorMvcAutoConfiguration自动配置异常处理规则
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({Servlet.class, DispatcherServlet.class})
@AutoConfigureBefore({WebMvcAutoConfiguration.class})
@EnableConfigurationProperties({ServerProperties.class, WebMvcProperties.class})
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties;
@Bean
@ConditionalOnMissingBean(value = {ErrorAttributes.class},search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
@Bean
@ConditionalOnMissingBean(value = {ErrorController.class},search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
}
异常有关的属性配置来自ServerProperties和WebMvcProperties。
在容器中添加了id为errorAttributes的DefaultErrorAttributes组件
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
}
在容器中添加了id为basicErrorController的BasicErrorController组件
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
@RequestMapping(
produces = {"text/html"}
)
@RequestMapping(produces = {"text/html"})
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
}
这个Controller中,如果配置了server.error.path就使用该值,没有陪的默认值是error.path,如果也没有配就匹配/error路径。如果是浏览器就显示error页面(页面响应 new ModelAndView(“error”, model);),非浏览器就返回错误数据。
容器中有组件 View->id是error;(响应默认错误页)
在com.stonebridge.boot05webadmin.servlet包下定义Servlet、Filter、Listener
Servlet
效果:直接响应,没有经过Spring的拦截器
@WebServlet(urlPatterns = "/myServlet")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("66666666");
}
}
Filter
@Slf4j
@WebFilter(urlPatterns = {"/myServlet"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("MyFilter初始化完成");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("MyFilter过滤");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("MyFilter销毁");
}
}
Listener
@Slf4j
@WebListener
public class MySwervletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
log.info("MySwervletContextListener监听项目初始化完成");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
log.info("MySwervletContextListener监听项目销毁");
}
}
在Spring Boot的主启动类中使用@ServletComponentScan扫描Servlet、Filter、Listener组件所在的包
@ServletComponentScan(basePackages = "com.stonebridge.boot05webadmin")//指定原生Servlet组件都放在那里
@SpringBootApplication
public class Boot05WebAdminApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05WebAdminApplication.class, args);
}
}
启动效果
说明:
加入MyServlet后此时有两个Servlet处理请求
MyServlet -->处理:/my
DispatcherServlet -->处理:/
容器中自动配置了 DispatcherServlet 属性绑定到 WebMvcProperties;对应的配置文件配置项是 spring.mvc。
通过 ServletRegistrationBean
默认映射的是 / 路径。
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {
@Bean
public ServletRegistrationBean myServlet() {
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet, "/my", "/my02");
}
@Bean
public FilterRegistrationBean myFilter() {
MyFilter myFilter = new MyFilter();
// return new FilterRegistrationBean(myFilter,myServlet());
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener() {
MySwervletContextListener mySwervletContextListener = new MySwervletContextListener();
return new ServletListenerRegistrationBean(mySwervletContextListener);
}
}
Spring Boot默认的数据源是HikariDataSource
数据库开发的场景会导入很多的相关数据库的自动配置类(XXXAutoConfiguration),例如数据源(dataSource),以及相关配置属性绑定的properties类。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jdbcartifactId>
dependency>
分析依赖发现缺少数据库驱动
为什么导入JDBC场景,官方不导入驱动?
因为官方不知道我们接下要操作什么数据库,如果要操作数据库,导入对应的依赖即可。
数据库版本和驱动版本对应
默认版本(版本仲裁):
<mysql.version>8.0.26mysql.version>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
<exclusions>
<exclusion>
<groupId>com.google.protobufgroupId>
<artifactId>protobuf-javaartifactId>
exclusion>
exclusions>
dependency>
想要修改版本
1、直接依赖引入具体版本(maven的就近依赖原则)
2、重新声明版本(maven的属性的就近优先原则)
<properties>
<java.version>1.8java.version>
<mysql.version>5.1.49mysql.version>
properties>
自动配置的类
DataSourceAutoConfiguration : 数据源的自动配置类
数据源相关的配置:spring.datasource。与数据源相关的属性都都在DataSourceProperties配置映射。
容器中没有io.r2dbc.spi.ConnectionFactory(响应式编程)就启动该配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})
@ConditionalOnMissingBean(type = {"io.r2dbc.spi.ConnectionFactory"})
@EnableConfigurationProperties({DataSourceProperties.class})
@Import({DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class})
public class DataSourceAutoConfiguration {
public DataSourceAutoConfiguration() {
}
}
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
private ClassLoader classLoader;
private String name;
private boolean generateUniqueName = true;
private Class<? extends DataSource> type;
private String driverClassName;
private String url;
private String username;
private String password;
private String jndiName;
private DataSourceInitializationMode initializationMode;
……
}
数据库连接池的配置,当容器中没有DataSource组件才自动配置的,即用户未配置DataSource。底层默认数据源HikariDataSource
@Configuration(proxyBeanMethods = false)
@Conditional({DataSourceAutoConfiguration.PooledDataSourceCondition.class})
@ConditionalOnMissingBean({DataSource.class, XADataSource.class})
@Import({Hikari.class, Tomcat.class, Dbcp2.class, OracleUcp.class, Generic.class, DataSourceJmxConfiguration.class})
protected static class PooledDataSourceConfiguration {
protected PooledDataSourceConfiguration() {
}
}
默认在容器中注册hikari数据源
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({HikariDataSource.class})
@ConditionalOnMissingBean({DataSource.class})
@ConditionalOnProperty(name = {"spring.datasource.type"},havingValue = "com.zaxxer.hikari.HikariDataSource",matchIfMissing = true)
static class Hikari {
Hikari() {}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
HikariDataSource dataSource(DataSourceProperties properties) {
HikariDataSource dataSource = (HikariDataSource)DataSourceConfiguration.createDataSource(properties, HikariDataSource.class);
if (StringUtils.hasText(properties.getName())) {
dataSource.setPoolName(properties.getName());
}
return dataSource;
}
}
将数据源属性的配置DataSourceProperties传递给createDataSource方法。通过spring.datasource.type设置数据源的类型。
DataSourceTransactionManagerAutoConfiguration: 数据源事务管理器的自动配置
JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud
可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
@Configuration( proxyBeanMethods = false)
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
@EnableConfigurationProperties({JdbcProperties.class})
@Import({JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class})
public class JdbcTemplateAutoConfiguration {
public JdbcTemplateAutoConfiguration() {
}
}
@Bean@Primary JdbcTemplate;容器中有这个组件
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean({JdbcOperations.class})
class JdbcTemplateConfiguration {
JdbcTemplateConfiguration() {
}
@Bean
@Primary
JdbcTemplate jdbcTemplate(DataSource dataSource, JdbcProperties properties) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Template template = properties.getTemplate();
jdbcTemplate.setFetchSize(template.getFetchSize());
jdbcTemplate.setMaxRows(template.getMaxRows());
if (template.getQueryTimeout() != null) {
jdbcTemplate.setQueryTimeout((int)template.getQueryTimeout().getSeconds());
}
return jdbcTemplate;
}
}
JndiDataSourceAutoConfiguration: jndi的自动配置
XADataSourceAutoConfiguration: 分布式事务相关的
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 默认的数据库类型时hikari
type: com.zaxxer.hikari.HikariDataSource
# 驱动
driver-class-name: com.mysql.jdbc.Driver
@SpringBootTest
class JdbcDemoApplicationTests {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
}
}
整合第三方技术的两种方式
https://github.com/alibaba/druid
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.17version>
dependency>
Spring的原生方法创建数据源
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="20" />
<property name="initialSize" value="1" />
<property name="maxWait" value="60000" />
<property name="minIdle" value="1" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="testWhileIdle" value="true" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="poolPreparedStatements" value="true" />
<property name="maxOpenPreparedStatements" value="20" />
bean>
Spring Boot配置数据源
默认的自动配置是判断容器中没有数据源才会自动配置,此时配置了druid数据源,就不会配置默认的HikariDataSource数据源
@Configuration
public class MyDataSourceConfig {
//默认的自动配置是判断容器中没有数据源才会自动配置(@ConditionalOnMissingBean(DataSource.class))
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
}
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 驱动
driver-class-name: com.mysql.jdbc.Driver
@Slf4j
@Controller
public class MainController {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@RequestMapping(value = "testDb")
@ResponseBody
public List<Map<String, Object>> testDb() {
log.info("数据源类型:" + dataSource.getClass());
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
return list;
}
}
@Configuration
public class MyDataSourceConfig {
//默认的自动配置是判断容器中没有数据源才会自动配置(@ConditionalOnMissingBean(DataSource.class))
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
//加入监控功能
dataSource.setFilters("stat,wall");
return dataSource;
}
/**
* 配置 druid的监控页功能
*
* @return
*/
@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;
}
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@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;
}
}
配置druid的监控页功能(statViewServlet())就可以查看所有的监控页面。
使用配置形式
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 默认的数据库类型时hikari
type: com.zaxxer.hikari.HikariDataSource
# 驱动
driver-class-name: com.mysql.jdbc.Driver
filters: stat,wall
max-active: 12
打开监控统计功能
//加入监控统计功能
dataSource.setFilters("stat");
WebStatFilter用于采集web-jdbc关联监控的数据
/**
* WebStatFilter 用于采集web-jdbc关联监控的数据。
*/
@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;
}
dataSource.setFilters("stat,wall");
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.17version>
dependency>
//1.标注为配置类
@Configuration
//2.系统引入DruidDataSource包
@ConditionalOnClass({DruidDataSource.class})
//3.在DataSourceAutoConfiguration自动配置之前配置,因为DataSourceAutoConfiguration中如果没有配置数据源就自动配置Hikari数据源
@AutoConfigureBefore({DataSourceAutoConfiguration.class})
//4.Druid的所有的属性配置在DruidStatProperties(spring.datasource.druid)和DataSourceProperties(spring.datasource)
@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);
public DruidDataSourceAutoConfigure() {
}
@Bean(initMethod = "init")
@ConditionalOnMissingBean
public DataSource dataSource() {
LOGGER.info("Init DruidDataSource");
return new DruidDataSourceWrapper();
}
}
扩展配置项 spring.datasource.druid。与DruidStatProperties映射
DruidSpringAopConfiguration.class, 监控SpringBean的;配置项:spring.datasource.druid.aop-patterns
DruidStatViewServletConfiguration.class, 监控页的配置:spring.datasource.druid.stat-view-servlet;默认开启
例如:allow、deny、loginUsername、loginPassword、resetEnable
DruidWebStatFilterConfiguration.class, web监控配置;spring.datasource.druid.web-stat-filter;默认开启
功能同:WebStatFilter 用于采集web-jdbc关联监控的数据。
DruidFilterConfiguration.class}) 所有Druid自己filter的配置
private static final String FILTER_STAT_PREFIX = "spring.datasource.druid.filter.stat";
private static final String FILTER_CONFIG_PREFIX = "spring.datasource.druid.filter.config";
private static final String FILTER_ENCODING_PREFIX = "spring.datasource.druid.filter.encoding";
private static final String FILTER_SLF4J_PREFIX = "spring.datasource.druid.filter.slf4j";
private static final String FILTER_LOG4J_PREFIX = "spring.datasource.druid.filter.log4j";
private static final String FILTER_LOG4J2_PREFIX = "spring.datasource.druid.filter.log4j2";
private static final String FILTER_COMMONS_LOG_PREFIX = "spring.datasource.druid.filter.commons-log";
private static final String FILTER_WALL_PREFIX = "spring.datasource.druid.filter.wall";
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
# 数据库驱动类型
driver-class-name: com.mysql.jdbc.Driver
# 与druid有关的全部配置
druid:
aop-patterns: com.atguigu.admin.* #监控SpringBean
filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙)
stat-view-servlet: # 配置监控页功能
enabled: true # 开启配置监控页功能
login-username: admin # 设置监控页面的登录账户
login-password: password # 设置监控页面的登录密码
resetEnable: false # 是否有重置按钮
# 以及allow、deny、
web-stat-filter: # 监控web
enabled: true # 开启监控web的功能
urlPattern: # 监控哪些属性
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' # 排除哪些请求
filter: # 对filter进行详细配置
stat: # 对上面filters里面的stat的详细配置
slow-sql-millis: 1000 # 设置慢查询的时间限制
logSlowSql: true # 是否记录慢查询
enabled: true # 开启stat
wall:
enabled: true # 开启wall防火墙
config: # 开启wall防火墙
drop-table-allow: false
@Slf4j
@Controller
public class MainController {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
DataSource dataSource;
@RequestMapping(value = "testDb")
@ResponseBody
public List<Map<String, Object>> testDb() {
System.out.println("数据源类型:" + dataSource.getClass());
List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM user");
System.out.println(list.size());
for (int i = 0; i < list.size(); i++) {
Map<String, Object> map = list.get(i);
System.out.println(map.get("username"));
}
return list;
}
}
配置的禁止重置,重置时不会成功
SpringBoot配置示例
https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
配置项列表https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8
https://github.com/mybatis
starter
SpringBoot官方的Starter:spring-boot-starter-*
*第三方的: -spring-boot-starter
引入mybatis
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
以前单独使用mybatis。需要全局配置文件,根据全局配置文件需要配置SqlSessionFactory,根据SqlSessionFactory获得SqlSession。通过SqlSession找到Mapper接口操作数据库。
// 1.标记为配置类
@Configuration
// 2.导了mybatis的starter后就有SqlSessionFactory、SqlSessionFactoryBean
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 3.项目中有且只有一个数据源DataSource
@ConditionalOnSingleCandidate(DataSource.class)
// 4.mybatis配置项的绑定项MybatisProperties
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
//5.自动配置好了SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
……
}
//6.自动配置了 SqlSessionTemplate 组合了SqlSession
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
// 7.找到标注了@Mapper的接口。只要我们写的操作MyBatis的接口标准了 @Mapper 就会被自动扫描进来
@Configuration
@Import({MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar.class})
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public MapperScannerRegistrarNotFoundConfiguration() {
}
public void afterPropertiesSet() {
MybatisAutoConfiguration.logger.debug("Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}
@ConfigurationProperties(prefix = "mybatis")
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
private Properties configurationProperties;
}
可以application.yaml修改配置文件中mybatis的所有属性
@Data
public class User {
private Integer id;
private String name;
private String age;
private String email;
}
编写mapper接口,必须@Mapper注解或者使用在主启动类加扫描注解。
@Mapper
public interface UserMapper {
public User getUser(Integer id);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.druiddemo.mapper.UserMapper">
<select id="getUser" resultType="com.stonebridge.druiddemo.bean.User">
SELECT *
FROM user
WHERE id = #{id}
select>
mapper>
在Spring Boot的application.yaml中配置MyBatis属性
通过config-location配置mybatis的全局配置文件的位置(不推荐)
Mybatis的全局配置文件可以使用mybatis.configuration属性进行配置(推荐)
例如:通过配置map-underscore-to-camel-case为true是开启驼峰映射
在mybatis.mapper-locations属性指向mapper映射文件所在的位置
mybatis:
#config-location: classpath:mybatis/mybatis-config.xml # 指定全局配置文件
mapper-locations: classpath:mybatis/mapper/*.xml
configuration:
map-underscore-to-camel-case: true # 开启驼峰映射
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User getUserById(Integer id) {
return userMapper.getUser(id);
}
}
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
UserService userService;
@Test
void testGetUser() {
User user = userService.getUserById(1);
System.out.println(user);
}
}
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
@Data
public class City {
private Long id;
private String name;
private String state;
private String country;
}
编写Mapper接口并标注@Mapper注解
@Mapper
public interface CityMapper {
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
City findById(long id);
}
@Service
public class CityService {
@Autowired
CityMapper cityMapper;
public City getById(Long id) {
return cityMapper.findById(id);
}
}
@Slf4j
@Controller
public class MainController {
@Autowired
CityService cityService;
@ResponseBody
@RequestMapping("city")
public City getById(@RequestParam("id") Long id) {
return cityService.getById(id);
}
}
在Mapper接口中定义注解模式的方法和配置模式的方法。编写Mapper接口并标注@Mapper注解
@Mapper
public interface CityMapper {
void insert(City city);
// @Insert("INSERT INTO city (name, state, country) VALUES(#{name}, #{state}, #{country})")
// @Options(useGeneratedKeys = true, keyProperty = "id")
// void insert(City city);
@Select("SELECT id, name, state, country FROM city WHERE id = #{id}")
City findById(long id);
}
因为有配置模式,需要添加mapper.xml文件。将sql以及配置添加其中
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.mybatistest.mapper.CityMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO city (`name`, `state`, `country`)
VALUES (#{name}, #{state}, #{country})
insert>
mapper>
.在Spring Boot的总配置文件中指定mapper.xml文件的位置
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
或在MySQL数据库端配置时区解决此问题
驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
原因:
升级后的mysql驱动类,Driver位置由com.mysql.jdbc.Driver 变为com.mysql.cj.jdbc.Driver
解决方式:
将数据配置文件里spring.datasource.driver-class-name=com.mysql.jdbc.Driver修改为如下
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatis plus 官网
建议安装MybatisX 插件
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
自动配置
MybatisPlusAutoConfiguration配置类,该配置类的所有相关的属性都绑定于MybatisPlusProperties。mybatis-plus:xxx 就是对mybatis-plus的定制
Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MybatisPlusAutoConfiguration implements InitializingBean {
}
@ConfigurationProperties(prefix = "mybatis-plus")
public class MybatisPlusProperties {
}
SqlSessionFactory 自动配置好。底层是容器中默认的数据源。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
……
}
优点:
Spring Boot官方项目中没有Mybatis Plus的启动器,只能创建项目都自行引入;
可以引入Druid的数据源
<dependencies>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.17version>
dependency>
dependencies>
创建JavaBean与数据库表对应,使用@TableId注解标注主键
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
使用@Mapper注解将其扫描进Spring容器,只要实现BaseMapper接口,就会生成很多默认方法。
@Mapper
public interface UserMapper extends BaseMapper<User> {
User getUser(Integer id);
}
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.stonebridge.mapper.UserMapper">
<select id="getUser" resultType="com.stonebridge.domain.User">
SELECT *
FROM tb_user
WHERE id = #{id}
select>
mapper>
spring:
datasource:
url: jdbc:mysql://localhost:3306/mp?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
mybatis-plus:
mapper-locations: classpath:mappers/*.xml
configuration:
map-underscore-to-camel-case: true
创建service层,调用mapper层方法
@Service
public class UserService {
UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User getUserById(Integer id) {
return userMapper.getUser(id);
}
public List<User> selectList() {
return userMapper.selectList(null);
}
}
测试mapper中自定义的方法
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
private UserService userService;
@Test
void testGetUser() {
User user = userService.getUserById(1);
System.out.println(user);
}
}
测试MyBatis Plus中的方法
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
private UserService userService;
@Test
void testSelectList() {
List<User> list = userService.selectList();
for (User user : list) {
System.out.println(user);
}
}
}
事务管理器的对象:事务管理器TransactionManager接口,接口有很多实现类,构成一个技术体系。
例如:使用jdbc或mybatis访问数据库,使用的事务管理器:org.springframework.jdbc.datasource.DataSourceTransactionManager
声明式事务:在xml配置文件或者使用注解说明事务控制的内容
事务控制:隔离级别、传播行为、超时时间、回滚和不回滚异常、隔离级别、传播行为
事务处理方式:
@SpringBootApplication
@EnableTransactionManagement
public class SpringbootSsmApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootSsmApplication.class, args);
}
}
在mapper.xml中定义
<mapper namespace="com.stonebridge.mapper.UserMapper">
<update id="updateUserById">
UPDATE tb_user SET name =#{name},user_name=#{user_name}
WHERE id = #{id}
update>
mapper>
在mapper接口定义方法
@Mapper
public interface UserMapper extends BaseMapper<User> {
void updateUserById(@Param("id") Integer id, @Param("name") String name, @Param("user_name") String user_name);
}
在service方法中使用该方法
@Service
public class UserService {
UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Transactional
public void updateUser(Integer id) {
userMapper.updateUserById(id, "trump", "Trump");
}
}
测试
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
private UserService userService;
@Test
void testUpdateUser() {
userService.updateUser(6);
}
}
执行正常执行更新
在service层增加异常
@Transactional
public void updateUser(Integer id) {
userMapper.updateUserById(id, "trump", "Trump");
System.out.println(1/0);
}
@Test
void testUpdateUser() {
userService.updateUser(5);
}
此时将回滚事务,数据不会被提交。
在service方法中调用方法,@Transactional开启事务管理
@Transactional
public void saveUser() {
User user = new User();
user.setUserName("Stonebridge");
user.setPassword("123456");
user.setName("stonebride");
user.setAge(18);
user.setEmail("[email protected]");
Integer row = userMapper.insert(user);
System.out.println(1 / 0);
System.out.println("影响条数:" + row);
}
测试
@SpringBootTest
class SpringbootSsmApplicationTests {
@Autowired
private UserService userService;
@Test
void testSaveUser() {
userService.saveUser();
}
}
此时将回滚事务,数据不会被提交。
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
Spring Boot中Redis的场景启动器默认选中lettuce作为底层连接工厂,该客户端基于netty
自动配置:
RedisAutoConfiguration 自动配置类。
RedisProperties 属性类 --> spring.redis.xxx是对redis的配置
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
……
}
连接工厂是准备好的。LettuceConnectionConfiguration(默认)、JedisConnectionConfiguration
引入LettuceConnectionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "lettuce", matchIfMissing = true)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
LettuceConnectionConfiguration(RedisProperties properties,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
super(properties, sentinelConfigurationProvider, clusterConfigurationProvider);
}
}
当已经引入了RedisClient依赖时,spring.redis.client-type无论是否设置lettuce都会加载LettuceConnectionConfiguration连接工具
引入JedisConnectionConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ GenericObjectPool.class, JedisConnection.class, Jedis.class })
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnProperty(name = "spring.redis.client-type", havingValue = "jedis", matchIfMissing = true)
class JedisConnectionConfiguration extends RedisConnectionConfiguration {
}
当已经引入了GenericObjectPool、JedisConnection、Jedis依赖时,spring.redis.client-type无论是否设置lettuce都会加载LettuceConnectionConfiguration连接工具
自动注入了RedisTemplate<Object, Object> : xxxTemplate;k:v可以是Object的
自动注入了StringRedisTemplate;k:v都是String
底层只要我们使用 StringRedisTemplate、RedisTemplate就可以操作redis
redis环境搭建
1、阿里云按量付费redis。经典网络
2、申请redis的公网连接地址
3、修改白名单 允许0.0.0.0/0 访问
引入redis的场景启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
在yaml中配置链接属性
spring:
redis:
#url: redis://stonebridge:[email protected]:6379
host: r-bp18q0hwvo1fxi5vhbpd.redis.rds.aliyuncs.com
port: 6379
password: stonebridge:Stonebridge123456
#client-type: jedis
client-type: lettuce
lettuce:
pool:
max-active: 8
min-idle: 5
测试
@Slf4j
@SpringBootTest
class MybatisTestApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis() {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello", "world");
String key = operations.get("hello");
System.out.println(key);
//class org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
System.out.println(redisConnectionFactory.getClass());
}
}
引入jedis依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
修改client-type: jedis
声明底层连接工厂是jedis。jedis和Lettuce都是给容器添加RedisConnectionFactory。
spring:
redis:
#url: redis://stonebridge:[email protected]:6379
host: 192.168.174.132
port: 6379
password:
client-type: jedis
测试
@Slf4j
@SpringBootTest
class MybatisTestApplicationTests {
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis() {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello", "world");
String key = operations.get("hello");
System.out.println(key);
System.out.println(redisConnectionFactory.getClass());
}
}
Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库
作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成。
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
注意:
SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)
JUnit 5’s Vintage Engine Removed from spring-boot-starter-test,如果需要继续兼容junit4需要自行引入vintage
<dependency>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.hamcrestgroupId>
<artifactId>hamcrest-coreartifactId>
exclusion>
exclusions>
dependency>
引入test的启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
测试代码
@SpringBootTest
class Boot05WebAdminApplicationTests {
@Test
void contextLoads() {
}
}
示例
需要组件自动注入使用@Autowired自动注入即可
其中@Test为org.junit.jupiter.api.Test;
@Slf4j
@SpringBootTest
class MybatisTestApplicationTests {
@Autowired
UserMapper userMapper;
@Autowired
StringRedisTemplate redisTemplate;
@Autowired
RedisConnectionFactory redisConnectionFactory;
@Test
void testRedis() {
ValueOperations<String, String> operations = redisTemplate.opsForValue();
operations.set("hello", "world");
String key = operations.get("hello");
System.out.println(key);
System.out.println(redisConnectionFactory.getClass());
}
@Test
public void TestMybatisPlus() {
User user = userMapper.selectById(4);
log.info("查询到的User信息:{}", user);
//查询到的User信息:User(id=4, name=Sandy, age=21, [email protected], username=null, password=null)
}
}
注意点
SpringBoot整合Junit以后。
@SpringBootTest + @RunWith(SpringTest.class)
Unit5的注解与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 *为测试类或测试方法提供扩展类引用。功能类似于Junit4的@Runwith。如果测试时需要启动Spring Boot的测试驱动进行测试,即需要获取Spring容器中的组件,在Junit5中就需要使用@SpringBootTest,@SpringBootTest底层就是通过。@ExtendWith实现的。
@SpringBootTest:如果在测试类使用该注解,该类下的所有测试方法启动就会加载Spring的整个容器,Spring容器中的组件也可以被使用。
/**
* @BootstrapWith(SpringBootTestContextBootstrapper.class)
* @ExtendWith(SpringExtension.class)
*/
@SpringBootTest
@DisplayName("junit5功能测试类")
public class Junit5Test {
@Autowired
JdbcTemplate jdbcTemplate;
@Test
@DisplayName("测试DisplayName的方法")
void testDisplayName() {
System.out.println("测试DisplayName注解");
//org.springframework.jdbc.core.JdbcTemplate@36f6e521
System.out.println(jdbcTemplate);
}
@Test
@RepeatedTest(5)
void test3() {
System.out.println(5);
}
@BeforeEach
void testBeforeEach() {
System.out.println("测试就要开始了……");
}
@AfterEach
void testAfterEach() {
System.out.println("测试就要开始了……");
}
// @BeforeAll需要加为static修改
@BeforeAll
static void testBeforeAll() {
System.out.println("所有测试就要开始了...");
}
// @AfterAll需要加为static修改
@AfterAll
static void testAfterAll() {
System.out.println("所有测试以及结束了...");
}
/**
* 规定方法超时时间。超出时间测试出异常
*
* @throws InterruptedException
*/
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
@Test
void testTimeout() throws InterruptedException {
Thread.sleep(501);
}
}
断言就是断定某些事情一定发生,如果没有发生就是可以断定出了问题。
断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions的静态方法。JUnit 5 内置的断言可以分成如下几个类别:
检查业务逻辑返回的数据是否合理。
所有的测试运行结束以后,会有一个详细的测试报告;
用来对单个值进行简单的验证。如:
方法 | 说明 |
---|---|
assertEquals | 判断两个对象或两个原始类型是否相等 |
assertNotEquals | 判断两个对象或两个原始类型是否不相等 |
assertSame | 判断两个对象引用是否指向同一个对象 |
assertNotSame | 判断两个对象引用是否指向不同的对象 |
assertTrue | 判断给定的布尔值是否为 true |
assertFalse | 判断给定的布尔值是否为 false |
assertNull | 判断给定的对象引用是否为 null |
assertNotNull | 判断给定的对象引用是否不为 null |
//@SpringBootTest
public class Junit5Assertions {
/**
* 断言:前面断言失败,后面的代码都不会执行
*/
@Test
@DisplayName("测试简单断言")
void testSimpleAssertions() {
int result = cal(3, 5);
//相等
Assertions.assertEquals(8, result, "业务逻辑计算失败");
Object obj1 = new Object();
Object obj2 = new Object();
Assertions.assertSame(obj1, obj2, "两个对象不一样");
}
int cal(int i, int j) {
return i + j;
}
}
通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等
@Test
@DisplayName("array assertion")
void array() {
Assertions.assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不相等");
}
assertAll方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言
@Test
@DisplayName("组合断言")
void all() {
/**
* 所有断言全部需要成功
*/
Assertions.assertAll("test",
() -> Assertions.assertTrue(true && true, "结果不为true"),
() -> Assertions.assertEquals(1, 2, "结果不是1"));
System.out.println("=====");
}
在JUnit4时期,想要测试方法的异常情况时,需要用**@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows()** ,配合函数式编程就可以进行使用。
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
Assertions.assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间
@DisplayName("异常断言")
@Test
void testException() {
//断定业务逻辑一定出现异常
Assertions.assertThrows(ArithmeticException.class, () -> {
int i = 10 / 2;
}, "业务逻辑居然正常运行?");
}
通过 fail 方法直接使得测试失败
@DisplayName("快速失败")
@Test
void testFail() {
//xxxxx
if (1 == 2) {
Assertions.fail("测试失败");
}
}
JUnit 5 中的前置条件(assumptions【假设】)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
Assumptions.assumeTrue(false,"结果不是true");
System.out.println("111111");
}
/**
* 测试前置条件
*/
@DisplayName("测试前置条件")
@Test
void testassumptions(){
Assumptions.assumeFalse(false,"结果不是true");
System.out.println("111111");
}
assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止。assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象。只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止。
JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制。
@DisplayName("嵌套测试")
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());
}
}
}
}
未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
https://docs.spring.io/spring-boot/docs/2.4.10/reference/html/production-ready-features.html#production-ready
引入场景
访问 http://localhost:8080/actuator/**
暴露所有监控信息为HTTP
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
测试
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/configprops
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/metrics/jvm.gc.pause
http://localhost:8080/actuator/endpointName/detailPath(重点)
actuator后面的叫做Endpoint(监控端点)
https://github.com/codecentric/spring-boot-admin
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 |
执行线程转储。 |
如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:
ID | 描述 |
---|---|
heapdump |
返回hprof 堆转储文件。 |
jolokia |
通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core 。 |
logfile |
返回日志文件的内容(如果已设置logging.file.name 或logging.file.path 属性)。支持使用HTTPRange 标头来检索部分日志文件的内容。 |
prometheus |
以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus 。 |
最常用的Endpoint
健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。
重要的几点:
health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
很多的健康检查默认已经自动配置好了,比如:数据库、redis等
可以很容易的添加自定义的健康检查机制
示例:
# management 是所有actuator
# management.endpoint.端点名.xxx 对某个端点具体配置
# 例如: management.endpoint.health.show-details 开启健康信息的详细配置
management:
endpoints:
enabled-by-default: true #暴露所有端点信息
web:
exposure:
include: '*' #以web方式暴露
endpoint:
health:
show-details: always
http://localhost:8080/actuator/health
// 20210912132411
// http://localhost:8080/actuator/health
{
"status": "UP",
"components": {
"db": {
"status": "UP",
"details": {
"database": "MySQL",
"validationQuery": "isValid()"
}
},
"diskSpace": {
"status": "UP",
"details": {
"total": 253827739648,
"free": 66736623616,
"threshold": 10485760,
"exists": true
}
},
"ping": {
"status": "UP"
},
"redis": {
"status": "UP",
"details": {
"version": "6.2.5"
}
}
}
}
提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;
通过Metrics对接多种监控系统
简化核心Metrics开发
添加自定义Metrics或者扩展已有Metrics
// 20210912132244
// http://localhost:8080/actuator/metrics
{
"names": [
"http.server.requests",
"jvm.buffer.count",
"jvm.buffer.memory.used",
"jvm.buffer.total.capacity",
"jvm.classes.loaded",
"jvm.classes.unloaded",
"jvm.gc.live.data.size",
"jvm.gc.max.data.size",
"jvm.gc.memory.allocated",
"jvm.gc.memory.promoted",
"jvm.memory.committed",
"jvm.memory.max",
"jvm.memory.used",
"jvm.threads.daemon",
"jvm.threads.live",
"jvm.threads.peak",
"jvm.threads.states",
"logback.events",
"process.cpu.usage",
"process.start.time",
"process.uptime",
"system.cpu.count",
"system.cpu.usage",
"tomcat.sessions.active.current",
"tomcat.sessions.active.max",
"tomcat.sessions.alive.max",
"tomcat.sessions.created",
"tomcat.sessions.expired",
"tomcat.sessions.rejected"
]
}
默认所有的Endpoint除过shutdown都是开启的。
需要开启或者禁用某个Endpoint。配置模式为 management.endpoint.
management:
endpoint:
beans:
enabled: true
或者禁用所有的Endpoint然后手动开启指定的Endpoint
management:
endpoints:
enabled-by-default: false
endpoint:
beans:
enabled: true
health:
enabled: true
支持的暴露方式
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 |
如果当前项目需要引入更多信息,需要自己手动添加组件,其组件名称就是MyHealthIndicator截去
@Component
public class MyComHealthIndicator extends AbstractHealthIndicator {
/**
* 真实的检查方法
*
* @param builder
* @throws Exception
*/
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
//mongodb。 获取连接进行测试
Map<String, Object> map = new HashMap<>();
// 检查完成
if (1 == 2) {
// builder.up(); //健康
builder.status(Status.UP);
map.put("count", 1);
map.put("ms", 100);
} else {
// builder.down();
builder.status(Status.OUT_OF_SERVICE);
map.put("err", "连接超时");
map.put("ms", 3000);
}
builder.withDetail("code", 100)
.withDetails(map);
}
}
开启监控
management:
health:
enabled: true
show-details: always #总是显示详细信息。可显示每个模块的状态信息
返回结果:
"myCom": {
"status": "OUT_OF_SERVICE",
"details": {
"code": 100,
"err": "连接超时",
"ms": 3000
}
}
常用两种方式
info:
appName: boot-admin
version: 2.0.1
mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
mavenProjectVersion: @project.version@