Spring Boot


SpringBoot

创建一个javaweb

1、创建Maven工程,导入springboot父依赖,以及web环境

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring-boot-study</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--导入spring'boot-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>


    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <!--导入spring'boot web环境-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

2、创建主程序入口

package com.ch3cooh.boot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 这是一个springboot程序 这里 启动入口
 */
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

3、创建Controller

package com.ch3cooh.boot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SpringController {
    @RequestMapping("/hello")
    @ResponseBody
    public String HelloSpringBoot(){
        return "Hello Springboot 你好 SpringBoot";
    }
}

4、访问项目

http://localhost:8080/hello

配置文件

#这里是配置springboot的文件

#配置web容器端口
server.port=8080    
#配置项目访问根路径
server.servlet.context-path=/springboot

了解自动配置原理

依赖管理

<!--spring-boot-starter-parent的父项目 spring-boot-dependencies-->
<!--spring-boot-dependencies中几乎声明了所有开发中常用的依赖的版本号  自动版本冲裁机制-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <!--查看spring-boot-dependencies里面规定的当前以来的版本用的key-->
    <!--这里可以修改mysql版本号-->
    <mysql.version>5.1.47</mysql.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <!--导入mysql  已经指定了版本 为8.0.25 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

场景启动器

见到很多 spring-boot-starter-*:*就是某种场景
只要引入starter吗,这个场景的所有常规需要的依赖都会自动注入
springboot提供的场景:https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.build-systems.starters
*-spring-boot-starter第三方为我们提供的环境
所有场景启动器最底层的依赖↓
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.2</version>
    <scope>compile</scope>
</dependency>

自动配置

自动配好SpringMVC

    引入了SpringMVC常用组件,
    自动配好SpringMVC常用组件

springboot 自动配制好了Web常见功能,如字符编码,

配置了默认包结构

    主程序所在目录,及其子目录下里面的组件都会被默认扫描
    修改扫描包路径
//使用↓  或者使用@ComponenScan指定扫描路径
@SpringBootApplication(scanBasePackages="com.ch3cooh")
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}
//或者使用
//@SpringBootConfiguration
//@EnableAutoConfiguration
//@ComponentScan("com.ch3cooh")

各种配置拥有默认值

    默认配置最终都是映射到MultipartProperties
    配置文件的值最终都会绑定在每个类上,这个类会在容器中创建对象

按需加载所有自动配置项

    非常多的starter
    隐入了哪些场景,哪个场景的自动配置才会开启
    SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面

容器功能

组件添加

1、@Configuration

/**
 * 配置类里面使用Bean标注在方法上给容器注册组件,默认也是单实例
 * 配置类本身也是组件,放在容器里的MyConfig是生产的代理对象
 * proxyBeanMethods: 代理bean的方法
 *      Full(proxyBeanMethods = true)  组件之间存在依赖关系
 *      Lite(proxyBeanMethods = false) 组件之间不存在依赖关系
 */
@Import({})//导入指定类,会自动在容器中创建这个类的组件,名称为该类的全限定名称
@Configuration(proxyBeanMethods = true)  //告诉springboot 这是一个配置类 == 配置文件
public class MyConfig {
    /**
     * 外界直接调用该方法 返回的都是在容器注册的单实例对象
     * 所以 如果proxyBeanMethods值为true时,每一次调用,springboot都会去容器中查找是否有改对象,有就返回,没有就创建。
     * 保持组件单实例
     *
     * @return
     */
    @Bean // 给容器中添加组件,以方法名作为组建的id,返回类型就是组件类型 返回的值就是组件在容器中的实例
    public Student student01() {
        return new Student("zhangsan", 24);
    }
}

条件装配

@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
//@ConditionalOnBean(name = "tom")  当容器中含有 tom 的组件组件时 才创建以下组件
@ConditionalOnMissingBean(name = "tom") // 当容器中不含有 tom 的组件组件时 才创建以下组件
public class MyConfig {


    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */

    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

public static void main(String[] args) {
        //1、返回我们IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        //2、查看容器里面的组件
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        boolean tom = run.containsBean("tom");
        System.out.println("容器中Tom组件:"+tom);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:"+user01);

        boolean tom22 = run.containsBean("tom22");
        System.out.println("容器中tom22组件:"+tom22);

    }

原生配置引用@ImportResource

======================beans.xml=========================
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
@ImportResource("classpath:beans.xml")
public class MyConfig {}

======================测试=================
        boolean haha = run.containsBean("haha");
        boolean hehe = run.containsBean("hehe");
        System.out.println("haha:"+haha);//true
        System.out.println("hehe:"+hehe);//true

配置绑定

如何使用Java读取到properties文件中的内容,并且把它封装到JavaBean中,以供随时使用;

public class getProperties {
     public static void main(String[] args) throws FileNotFoundException, IOException {
         Properties pps = new Properties();
         pps.load(new FileInputStream("a.properties"));
         Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
         while(enum1.hasMoreElements()) {
             String strKey = (String) enum1.nextElement();
             String strValue = pps.getProperty(strKey);
             System.out.println(strKey + "=" + strValue);
             //封装到JavaBean。
         }
     }
 }

使用@ConfigurationProperties在配置类中绑定

/**
 * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
 */
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

@EnableConfigurationProperties + @ConfigurationProperties

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
  • 生效的配置类就会给容器中装配很多组件
  • 只要容器中有这些组件,相当于这些功能就有了
  • 定制化配置
  • 用户直接自己@Bean替换底层的组件
  • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties

Web开发

自定义Favicon

自定义网页标签图标

将favicon.ico放在静态资源目录即可,但不能配置静态资源路径映射,否则会失效

静态资源

server.port=8080
server.servlet.context-path=/springboot
#静态资源映射路径,静态资源存放在默认文件夹中,例如resources,static,public 当请求url中包含以下路径,则去静态文件夹中寻找静态资源
#当添加欢迎页的时候,需要关闭静态资源映射路径,因为在底层欢迎页路径是写死了/**
spring.mvc.static-path-pattern=/res/**
#设置静态资源存放地点
spring.web.resources.static-locations=classpath:/res/

webjar

webjar将一个静态资源打包成jar包,在maven中可以引入,例如引入jquery

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
</dependency>

​ 访问时,需要指定路径http://localhost:8080/springboot/webjars/jquery/3.5.1/jquery.js

Rest风格

  • @xxxMapping;
  • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作
  • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
  • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
  • 核心Filter;HiddenHttpMethodFilter
  • 用法: 表单method=post,隐藏域 _method=put
  • SpringBoot中手动开启
  • 扩展:如何把_method 这个名字换成我们自己喜欢的。

    
    @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)等同于@DeleteMapping  以上同理
    @DeleteMapping(value = "/user")
    @ResponseBody
    public String deleteUser(){
        return "DELETE-张三";
    }
    

    Rest基于表单提交原理

    @Bean
    @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
    @ConditionalOnProperty(
        prefix = "spring.mvc.hiddenmethod.filter",
        name = {"enabled"}
    )//使用OrderedHiddenHttpMethodFilter需要在配置文件中开启spring.mvc.hiddenmethod.filter.enabled=true
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }
    
    //OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter
    
    //HiddenHttpMethodFilter extends OncePerRequestFilter
    public class HiddenHttpMethodFilter extends OncePerRequestFilter {
        private static final List<String> ALLOWED_METHODS;
        public static final String DEFAULT_METHOD_PARAM = "_method";
        private String methodParam = "_method";
        static {
            ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
        }
    
        //................. 这个方法首先拦截请求,(整个过程目的是将请求中的method的值换成_method的值)
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            HttpServletRequest requestToUse = request;
            //判断是否是POST 以及请求没有异常
            if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
                //获取名为_method 的参数
                String paramValue = request.getParameter(this.methodParam);
                //如果不为空
                if (StringUtils.hasLength(paramValue)) {
                    //将参数全部转为大写
                    String method = paramValue.toUpperCase(Locale.ENGLISH);
                    //允许的请求方式中是否有传来的参数 允许的有PUT DELETE PATCH
                    if (ALLOWED_METHODS.contains(method)) {
                        //将请求转换为HttpMethodRequestWrapper,其中重写了getMethod方法,目的是当控制器调用该方法时,会返回method
                        requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                    }
                }
            }
            //放行
            filterChain.doFilter((ServletRequest)requestToUse, response);
        }
    }

请求映射原理

/*实现树
    Object
        GenericServlet
            HttpServlet
                HttpServletBean
                    FrameworkServlet(这里实现了doGet doPost doDelete等方法 抽象方法doService
                        DispatcherServlet(这里实现了doService  以及 doDispatch[核心]

大致流程
DispatcherServlet(中央调度器)调用父类doGet doPost等方法接收请求,将原生request,response通过processRequest方法传递doService,后经由doService传递给doDispatch 如下
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //省略......
    try {
        processedRequest = this.checkMultipart(request);//检查是否是文件上传请求
        multipartRequestParsed = processedRequest != request;
        mappedHandler = this.getHandler(processedRequest);//根据请求获取对象的Handler
        if (mappedHandler == null) {
            this.noHandlerFound(processedRequest, response);
            return;
        }
        HandlerAdapter ha = this.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;
            }
        }
        //省略......
    }
    catch (Exception var20) {
        dispatchException = var20;
    }
    //省略......
}
/*
    在doDispatch中 getHandler方法去寻找和请求匹配的控制器方法,具体流程:
        在doDispatch中的List变量handlerMappings中存放着五个Map 
            其中RequestMappingHandlerMapping存放着所有请求以及映射规则
            WelcomePageHandlerMapping存放着与欢迎页相关的请求以及映射规则
            BeanNameUrlHandlerMapping,RouterFunctionMapping,SimpleUrlHandlerMapping等

            当浏览器发送请求时,中央调度器截取请求,遍历handlerMappings中所有handlermapping,去handlermapping中寻找能够处理请求的handler,
            找到则返回相应的handler,找不到返回null
*/

RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则。

所有的请求映射都在HandlerMapping中。

  • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  • SpringBoot自动配置了默认 的 RequestMappingHandlerMapping
  • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
  • 如果有就找到这个请求对应的handler
  • 如果没有就是下一个 HandlerMapping
  • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping

普通参数和基本注解

注解:

@PathVariable、@RequestHeader、@ModelAttribute、@RequestParam、@MatrixVariable、@CookieValue、@RequestBody

//PathVariable 请求参数路径, 将数据和路径结合,从路径中获取数据,
//RequestHeader 获取请求头信息
@GetMapping("/user/{username}/age/{age}")//http://localhost:8080/springboot/user/张飞/age/24
@ResponseBody
public Map<String,Object> PathVariable(@PathVariable("username") String username,
                                       @PathVariable("age")String age,
                                       //可以直接将路径中的所有参数和参数名放入map中
                                       @PathVariable Map<String,String> pv
                                       //获取请求头指定信息
                                       @RequestHeader("user-agent") String userAgent,
                                       //获取请求头所有信息
                                       @RequestHeader Map<String, String> Header) {
    Map<String,Object> map = new HashMap<>();
    map.put("username", username);
    map.put("age", age);
    map.put("pv", pv);
    map.put("userAgent", userAgent);
    map.put("Header", Header);
    return map;
}

//RequestParam 将路径中指定参数名的参数提取到对应的方法参数中
@GetMapping("/user/select")//http://localhost:8080/springboot/user/select?age=24&username=张飞&interest=唱歌&interest=跳舞&interest=篮球
@ResponseBody
public Map<String, Object> RequestParam(@RequestParam("username") String username,
                                        @RequestParam("age") Integer age,
                                        //多个数值的时候
                                        @RequestParam("interest") List interests,
                                        //使用map可以将所有参数名和参数存储,但如果同一个key下面放多个value。可以使用MultiValueMap
                                        @RequestParam MultiValueMap<String, String> params) {

    Map<String, Object> map = new HashMap<>();
    map.put("username",username);
    map.put("age",age);
    map.put("interest",interests);
    map.put("params",params);
    return map;
}

//CookieValue  获取cookie信息
@GetMapping("/cookie")
@ResponseBody
public Map<String,Object> CookieValue(@CookieValue("Idea-658b1eb9") String  cookiename,//获取cookie 指定信息 比如name
                                      //获取完整Cookie 填入Cookie 的 name
                                      @CookieValue("Idea-658b1eb9") Cookie cookie){
    Map<String, Object> map = new HashMap<>();
    map.put("cookie",cookie);
    map.put("cookiename",cookiename);
    System.out.println(cookie.getName() + "====>" + cookie.getValue());
    return map;
}

//RequestBody 获取请求体
@PostMapping("/requestbody")
@ResponseBody
public Map<String,Object> RequestBody(@RequestBody String context){
    Map<String, Object> map = new HashMap<>();
    map.put("context", URLDecoder.decode(context));//中文编码错误 改为10进制
    return map;
}


//RequestAttribute获取请求域中的参数
@GetMapping("/forward")
public String forwarod(HttpServletRequest request) {
    request.setAttribute("msg", "添加成功");
    request.setAttribute("code", 1);
    System.out.println("==============");
    return "forward:/success";
}
@GetMapping("/success")
@ResponseBody
public Map<String, Object> success(@RequestAttribute获取请求域中的参数
                                   @RequestAttribute("code") int code,
                                   HttpServletRequest request) {
    String msg1 = (String) request.getAttribute("msg");
    Integer code1 = (Integer) request.getAttribute("code");

    Map<String, Object> map = new HashMap<>();
    System.out.println(msg);
    map.put("msg1", msg);
    map.put("code1", code);
    map.put("msg", msg);
    map.put("code", code);
    return map;
}

//@MatrixVariable矩阵变量   /shop/shopping/id=1;price=100
//springboot默认禁用支持矩阵变量,需要重写对象开启
@PostMapping("/shop/{path}")
public Map<String, Object>  MatrixVariable(@MatrixVariable("id")Integer id,
                                           @MatrixVariable("price")Integer price,
                                           @PathVariable("path")String path){
    Map<String, Object> map = new HashMap();
    map.put("id",id);
    map.put("price",price);
    map.put("path",path);
}

参数注入原理

拦截器

//定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        System.out.println("拦截了:"+requestURI);

        HttpSession session = request.getSession();
        String username = (String) session.getAttribute("username");
        if (username != null) {
            //放行
            return true;
        }
        String contextPath = request.getContextPath();
        System.out.println(contextPath);
        //重定向
        response.sendRedirect(contextPath+"/index.html");
        //转发
        //request.getRequestDispatcher("/").forward(request,response);
        return false;
    }

    //目标方法执行之后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    //页面渲染之后
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }


}
//将拦截器放入容器中
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())//添加拦截器
            .addPathPatterns("/**")  //所有请求都被拦截器处理
            .excludePathPatterns("/","/index.html","/login","/error","/css/**");//不用拦截器处理指定了路径以及静态资源,这里登录路径,根路径不拦截
    }
}

拦截器原理

1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】

2、先来顺序执行 所有拦截器的 preHandle方法

  • 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
  • 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;

3、如果任何一个拦截器返回false。直接跳出不执行目标方法

4、所有拦截器都返回True。执行目标方法

5、倒序执行所有拦截器的postHandle方法。

6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

7、页面成功渲染完成以后,也会倒序触发 afterCompletion

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
                mappedHandler = this.getHandler(processedRequest);
                if (mappedHandler == null) {
                    this.noHandlerFound(processedRequest, response);
                    return;
                }
                //寻找能够抽取数据的Adapter
                HandlerAdapter ha = this.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;
                    }
                }
                //1 这里是应用拦截器拦截器,主要执行PreHandle,如果拦截器不为true,则返回,就不不会执行下面
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                //2 执行方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }

                this.applyDefaultViewName(processedRequest, mv);
                //3 应用拦截器,主要执行PostHandle
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception var20) {
                dispatchException = var20;
            } catch (Throwable var21) {
                dispatchException = new NestedServletException("Handler dispatch failed", var21);
            }

            this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
        } catch (Exception var22) {
            //过程中出现异常直接执行triggerAfterCompletion
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
        } catch (Throwable var23) {
            //过程中出现异常直接执行triggerAfterCompletion
            this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
        }

    } finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                //过程中出现异常直接执行triggerAfterCompletion
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else if (multipartRequestParsed) {
            this.cleanupMultipart(processedRequest);
        }

    }
}
//如果不为true 中止请求
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}
//applyPreHandle↓↓↓↓↓↓↓↓↓↓↓
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //1.1 将所有在容器中的拦截器进行正序遍历
    for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        //1.2 执行拦截器的preHandle,如果preHandle执行后结果不为真
        if (!interceptor.preHandle(request, response, this.handler)) {
            //1.3 则执行triggerAfterCompletion
            this.triggerAfterCompletion(request, response, (Exception)null);
            //1.4 返回false  结束
            return false;
        }
    }
    //1.5返回true 结束
    return true;
}
//1.3 执行triggerAfterCompletion
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    //1.3.1 对拦截器进行反向遍历
    for(int i = this.interceptorIndex; i >= 0; --i) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);

        try {
            //1.3.2 去执行拦截器中afterCompletion方法
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable var7) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", var7);
        }
    }

}
mappedHandler.applyPostHandle(processedRequest, response, mv);
//applyPostHandle↓↓↓↓↓↓↓↓↓↓↓
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    //3.1倒序遍历拦截器
    for(int i = this.interceptorList.size() - 1; i >= 0; --i) {
        HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
        //3.2执行postHandle
        interceptor.postHandle(request, response, this.handler, mv);
    }

}

文件上传

SpringBoot默认文件上传解析器原理

文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties

  • 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
  • 原理步骤
  • 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
  • 2、参数解析器来解析请求中的文件内容封装成MultipartFile
  • 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>

FileCopyUtils。实现文件流的拷贝

Error处理

原生组件注入(Servlet,Filter,Listener)

1使用ServletAPI

Serlvet

@WebServlet(urlPatterns = "/mySerlvet")//此时该路径并没有被Spring拦截器拦截
public class myServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().println("原生ServletA");
    }
}
@ServletComponentScan(basePackages = "com.ch3cooh.boot.Servlet")//扫描Servlet
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
    }
}

Filter

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
//拦截指定路径
@WebFilter(urlPatterns = {"/css/*","/js/*"})
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("拦截器初始化init");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("拦截器运行doFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        System.out.println("拦截器摧毁destroy");

    }
}

Listener

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class myServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("监听器contextInitialized");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("监听器contextDestroyed");
    }
}

2使用RegistrationBean

import com.ch3cooh.boot.Servlet.MyFilter;
import com.ch3cooh.boot.Servlet.MyServlet;
import com.ch3cooh.boot.Servlet.MyServletContextListener;
import com.sun.org.apache.regexp.internal.RE;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

@Configuration(proxyBeanMethods = true)//单实例模式,防止每次访问都会创建相应的对象
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServletRegistrationBean() {
        MyServlet myServlet = new MyServlet();
        return new ServletRegistrationBean(myServlet, "/myServlet", "/myServletRegistrationBean");
    }

    @Bean
    public FilterRegistrationBean myFilterRegistrationBean() {
        MyFilter myFilter = new MyFilter();
        //拦截myServletRegistrationBean()指定的Servlet路径
        //FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter, myServletRegistrationBean());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
        //指定路径
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/css/*", "/js/*"));
        return filterRegistrationBean;

    }

    @Bean
    public ServletListenerRegistrationBean myListener() {
        MyServletContextListener listener = new MyServletContextListener();
        return new ServletListenerRegistrationBean(listener);
    }
}

原生Servlet没有被拦截的原因:

因为对于Tomcat来说,此时有两个Servlet,一个是DispatcherServlet,一个是MyServelt,其中DispatcherServlet能够处理的路径是 / ,而MySerlvet处理的是/mySerlvet,浏览器向http://localhost:8080/springboot/myServlet发起请求时,根据Tomcat 精确优先匹配原则,首选匹配的是能够处理/myServlet的Servlet,而不是DispatcherServlet,在springboot中配置的拦截器都是在DispatcherServlet流程中生效的,所以请求不会被拦截






数据访问

<!--引入数据库 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--数据库jdbc -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
#数据库连接配置
spring.datasource.url=jdbc:mysql://localhost:3306/mindquiz
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jdbc.template.query-timeout=3
//测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class SpringbootTest {
    @Autowired
    JdbcTemplate template;

    @Test
    public void dbtest() {
        Long aLong = template.queryForObject("select count(*) from user", Long.class);
        System.out.println(aLong);
    }
}

切换数据源

手动配置

<!-- pom.xml 中引入德鲁伊数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.5</version>
</dependency>
//将德鲁伊放入容器
@Configuration
public class DataSourceConfig {

    @ConfigurationProperties("spring.datasource")
    @Bean
    public DataSource dataSource() {
        DruidDataSource druidDataSource = new DruidDataSource();
        //这里开启状态监控以及防火墙,也可以将开启的信息填写在配置文件中
        //druidDataSource.setFilters("stat,wall");
        return druidDataSource;
    }

    /**
     * 配置德鲁伊监控页面功能
     *
     * @return
     */
    @Bean
    public ServletRegistrationBean statViewServlet() {
        StatViewServlet servlet = new StatViewServlet();
        ServletRegistrationBean<StatViewServlet> registrationBean =
                new ServletRegistrationBean<>(servlet, "/druid/*");
        registrationBean.addInitParameter("loginUsername", "lbc");
        registrationBean.addInitParameter("loginPassword", "123");
        return registrationBean;
    }

    /***
     * WebStatFilter用于采集web-jdbc关联监控的数据。
     * @return
     */
    @Bean
    public FilterRegistrationBean webStatFilter() {
        WebStatFilter webStatFilter = new WebStatFilter();

        FilterRegistrationBean filter = new FilterRegistrationBean<WebStatFilter>(webStatFilter);
        filter.setUrlPatterns(Arrays.asList("/*"));
        filter.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filter;
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/mindquiz
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.filters=stat,wall

简化配置

利用场景启动器 start

引入starter

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>
#application.yaml
spring:
  datasource:
      url: jdbc:mysql://localhost:3306/mindquiz
    username: root
    password: 123
    driver-class-name: com.mysql.jdbc.Driver

    druid:
      #开启druid后台
      stat-view-servlet:
        enabled: true
        login-username: lbc
        login-password: 123
        #更改后台地址 不写默认为 /druid/*
        #url-pattern: /test/*
        
      #开启监控
      web-stat-filter:
        enabled: true
        #更改监控的请求路径 不写默认为 /*
        url-pattern: '/*'
        #更改监控的忽视的请求路径 不写默认为 *.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
        
      filter:
        #开启sql监控
        stat:
          enabled: true
        #开启防火墙
        wall:
          enabled: true
          config:
            #不允许删除表操作
            drop-table-allow: false
      #spring监控
      aop-patterns: com.ch3cooh.boot.*

MyBatis

配置文件方式

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml #全局配置文件位置  可以不创建全局配置文件,但是所有配置项都应在configuration中配置
  mapper-locations: classpath:mybatis/mapper/*.xml #sql映射文件位置
  configuration:
    map-underscore-to-camel-case: true #开启驼峰命名策略
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--
    在数据库中,由于大多数数据库设置不区分大小写 ,因此下画线方式的命名很常见,如user_name、user_email 。在 Java 中,一般都使用驼峰式命名,如 userName、userEmail因为数据库和 Java 中的这两种命名方式很常见,因此 MyBatis 还提供一个全局属性mapUnderscoreToCamelCase ,通过配置这个属性为 true 可以自动将以下画线方式命名的数据库列映射到 Java 对象的驼峰式命名属性中。这个属性默认为 false ,如果想要使用该功能,需要在 MyBatis 的配置文件(mybatis-config.xml 文件)中增加如下配置。
-->

<configuration><!--根标签-->
    <!--
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    -->
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ch3cooh.boot.mapper.UserMapper">

    <select id="getAllUser" resultType="com.ch3cooh.boot.domain.User">
        select username, password, nickname, realname
        from user
    </select>

    <select id="getOneUser" resultType="com.ch3cooh.boot.domain.User">
        select username, password, nickname, realname
        from user
        where username = #{username}
    </select>
</mapper>

注解方式

MyBatis-Plus

<!--该启动器中已经引入了 mybatis-spring整合包,以及spring-boot-starter-jdbc-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.3</version>
</dependency>
mybatis-plus:
  mapper-locations: classpath:mybatis/mapper/*.xml #指定maper文件,默认是mapper/**/*.xml
public interface JudgeService extends IService<Judge> {

}

@Service
public class JudgeServiceImp extends ServiceImpl<JudgeMapper, Judge> implements JudgeService {
    @Autowired
    JudgeMapper judgeMapper;

    @Override
    public Judge getOneJudge(Integer id) {
        Judge oneJudge = judgeMapper.getOneJudge(id);
        return oneJudge;
    }
}

@Autowired
JudgeService judgeService;

@GetMapping("/getjudgelist")
@ResponseBody
public List<Judge> getOneJudgerList(){
    //此时judgeService中拥有绝大部分对单一表进行常规的增删改查方法。
    List<Judge> list = judgeService.list();
    return list;
}

Redis

自动配置

​ RedisAutoConfiguration自动配置类。 RedisProperties属性类--> spring redis.xxx是对 redis的配置
​ 连接工厂是准备好的。 Lettuce Connection Configuration、 JedisConnection Configuration
​ 自动注入了 RedisTemplate< object, object>: XXXTemplate
​ 自动注入了 String RedisTemplate;k:v都是 String
​ key: value
​ 底层只要我们使用 String RedisTemplate、 RedisTemplate就可以操作reds

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
  redis:
    host: 114.215.172.169
    port: 6379

RedisTemplate 和 Lettuce

@Autowired
RedisTemplate redisTemplate;

@Test
public void redistest() {
    ValueOperations operations = redisTemplate.opsForValue();

    operations.set("hello", "redis");

    String hello = (String) operations.get("hello");

    System.out.println(hello);

}

切换至jedis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
spring:
  redis:
    host: 114.215.172.169
    port: 6379
    client-type: jedis

用redis记录请求访问次数

//拦截器
@Component
public class UrlCountInterceptor implements HandlerInterceptor {

    @Autowired
    StringRedisTemplate template;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取uri
        String requestURI = request.getRequestURI();
        ValueOperations operations = template.opsForValue();
        System.out.println(requestURI);
        //对uri加1
        operations.increment(requestURI);
        return true;
    }
}
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Autowired
    UrlCountInterceptor urlCountInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器
        registry.addInterceptor(new LoginInterceptor())
            //所有请求都被拦截器处理
            .addPathPatterns("/**")
            //不用拦截器处理指定了路径以及静态资源,这里登录路径,根路径不拦截
            .excludePathPatterns("/", "/index.html", "/login", "/error", "/css/**", "/js/**");
        
        //注册拦截器  这里没有new  是因为urlCountInterceptor拦截器中使用了自动配置注解
        //为了使注解生效,不new
        registry.addInterceptor(urlCountInterceptor)
            .addPathPatterns("/**")
            .excludePathPatterns("/", "/index.html", "/login", "/error", "/css/**", "/js/**");

    }
}

@Autowired
StringRedisTemplate redisTemplate;//在存入值的时候,redis会默认使用序列化,所以存入时使用什么类型,取出也使用什么类型

@GetMapping("/geturicount")
@ResponseBody
public String getUriCount() {

    ValueOperations<String, String> operations = redisTemplate.opsForValue();
    String s = operations.get("/springboot/success.html");
    System.out.println(s);
    return s;
}

Juntil5

引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

junti5常用注解

@Test// 表示方法是测试方法,但是和Junit4的@Test不同,他的职责非常单一,不能声明任何属性,扩展的测试将会由Jupiter提供额外测试。
@ParameterizedTest//表示方法是参数化测试,下面会有详细介绍
@PepeatedTest//表示方法可以重复执行,下面会有详细介绍
@DisplayName//未测试类或者测试方法设置展示名称
@BeforeEach//表示在每个单元测试之前执行
@AfterEach//表示在每个单元测试之后执行
@BeforeAll//表示在所有单元测试之前执行
@AfterAll//表示在所有单元测试之后执行
@Tag//表示单元测试类别,类似于JUnit4中的@Categories
@Disabled//表示测试类或者测试方法不执行,类似于Junit4中的@Ignore
@Timeout//表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith//微测试或者测试方法提供扩展类引用
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.concurrent.TimeUnit;

//如果想用springboot容器的类则需要加上这个注解,
//SpringBootTest注解中包含了@ExtendWith({SpringExtension.class})
@SpringBootTest
public class Junit5Test {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @DisplayName("测试类")
    @Test
    public void Test1(){
        System.out.println("Junit5测试注解");
    }

    @Disabled
    @Test
    public void Test2(){
        System.out.println("测试方法2");
    }

    /**
     * Timeout 注解 表示当前方法允许运行的时间,如果方法运行时间超过指定的value,则报异常
     * value 时间,unit 单位
     * @throws InterruptedException
     */
    @Timeout(value = 500,unit = TimeUnit.MILLISECONDS)
    @Test
    public void TimeOutTest() throws InterruptedException {
        Thread.sleep(501);
    }

    @BeforeEach
    public void beforEach(){
        System.out.println("在每一个测试方法之前执行");
    }

    @AfterEach
    public  void afterEach(){
        System.out.println("在每一个测试方法后执行");
    }

    @BeforeAll
    public static void beforAll(){
        System.out.println("所有测试之前执行");
    }

    @AfterAll
    public static void afterAll(){
        System.out.println("所有测试之后执行");
    }

    //重复次数
    @RepeatedTest(value = 5)
    public void repeactTest(){
        System.out.println(1);
    }

    @Test
    public void springbootTest(){
        System.out.println(jdbcTemplate);
    }
}

断言

断言是测试方法中的核心部分,用来测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。JUnit5内置的断言可以分成如下几个类别

检查业务逻辑返回的数据是否合理

所有的测试运行结束后,会有一个详细的测试报告

简单类型断言

方法说明
assertEquals判断两个对象或两个原始类型是否相等
assertNotEquals判断两个对象或两个原始类型是否不想等
assertSame判断两个对象引用是否指向相同的对象
assertNotSame判断两个对象引用是否指向不相同的对象
assertTrue判断给定的布尔值是否为true
assertFalse判断给定的布尔值是否为false
assertNull判断给定的对象引用是否为空
assertNotNull判断给定的对象引用是否不为空
@DisplayName("测试简单断言")
@Test
public void assertions(){
    //测试sun方法
    int sum = sum(1, 5);
    Assertions.assertEquals(6,sum,"返回的数据不等于期望值");
    Object o = new Object();
    Object o1 = new Object();
    Assertions.assertSame(o,o1,"这两个对象不想等");

}

public int sum(int a , int b){
    return a + b;
}

数组断言

通过assertArrayEquals来判断两个对象或原始类型的组是否相等

@DisplayName("数组断言")
@Test
public void assertArrayEquals() {
    Assertions.assertArrayEquals(new int[]{1, 1, 3}, new int[]{1, 2, 3});
}

组合断言

public int sum(int a, int b) {
    return a + b;
}

@DisplayName("组合断言")
@Test
public void combAsertion() {
    //所有断言全部需要成功
    Assertions.assertAll("test",
                         () -> Assertions.assertTrue(true && returntrue(), "返回的不是真"),
                         () -> Assertions.assertEquals(5, sum(1, 4), "数值不相等"));
    System.out.println("===才会执行到这个地方===");
}

public boolean returntrue() {
    return true;
}

异常断言

@DisplayName("异常断言")
@Test
public void testException(){
    Assertions.assertThrows(ArithmeticException.class,()->{
        int i = 10/0;
    },"业务居然正常允许了?!");
}

超时断言

Assertions.assertTimeout()为测试方法设置了超时时间

@Test
@DisplayName("超时断言")
public void timeout(){
    Assertions.assertTimeout(Duration.ofMillis(1000),()-> {
        System.out.println(1111);
        Thread.sleep(1111);
        System.out.println(2222);
    });
}

快速失败

通过fail方法直接使得测试失效

@Test
@DisplayName("fail")
public void shouldFail(){
    Assertions.fail("this is a fail method");
}    

前置条件

JUnit5中 的前置条件(assumptions[假设]) 类似于断言,不同之处在于不满足的断言会使得测试方法失效,而不满足的前置条件只会使得测试方法的执行中止,前置条件可以看成是测试方法执行的前提,当该前提条件不满足是,就没有继续执行的必要了

@DisplayName("前置条件")
@Test
public void testAssumptions(){
    Assumptions.assumeTrue(true,"结果不是true");
    System.out.println("11111");
}

嵌套测试 @Nested

//当一个测试类中有内部类,且内部类中的测试方法也需要运行时,可以在内部类头上加@Nested
import org.junit.jupiter.api.*;

import java.util.EmptyStackException;
import java.util.Stack;

@DisplayName("嵌套测试")
public class TestNested {
    Stack<Object> stack;

    @Test
    public void initStack() {
        new Stack<>();
        //运行这个方法时,并没有触发第一层类中的BeforeEach,外层类中不能驱动内层类中的BeforeEach/BeforeAll  AfterEach/BeforeAll
        Assertions.assertNull(stack);
    }

    @Nested
    @DisplayName("第一层")
    class FirstLayer {

        @DisplayName("createStack")
        @BeforeEach
        public void createStack() {
            stack = new Stack<>();
        }

        @Test
        @DisplayName("is empty")
        public void isEmpty() {
            Assertions.assertTrue(stack.isEmpty());
        }

        @Test
        public void BeforTest() {
            Assertions.assertNotNull(stack);
        }

        @Test
        public void throwExceptionPoped() {
            Assertions.assertThrows(EmptyStackException.class, stack::pop);
        }

        @Nested
        @DisplayName("第二层")
        class SecondLayer {
            @BeforeEach
            public void pushElement() {
                stack.push("an element");
            }
            //这个测试方法在执行时,外层的BeforeEach先执行了,然后才是这一层的@BeforeEach,
            @Test
            public  void isEmpty(){
                Assertions.assertFalse(stack.isEmpty());
            }
        }
    }
}

参数化测试

参数化测试时JUnit5很重要的一个新特性,他使得用不同参数多次运行测试成为了可能, 也为我们的单元测试带来许多便利

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多测单元测试,而不是每增加一个参数,就要新增一个单元测试,省去了很多冗余代码

@ValueSource:为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型
@NullSource:表示参数化测试提供一个null入参
@EnumSouce:表示为参数化测试提供一个枚举入参
@CsvFileSouce:表示读取指定CSV文件内作为参数化测试入参
@MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

还支持外部各类入参,如CSV YML JSON 文件,甚至方法的返回值也可以作为入参,只需要实现ArgumentsProvider接口。任何文件都可以作为他的入参


@ParameterizedTest//表明这是一个参数化测试方法
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
public void testParameterized(int i){
    System.out.println(i);
}


@ParameterizedTest//表明这是一个参数化测试方法
@DisplayName("参数化测试")
@MethodSource("stringProvider")//方法必须是静态的
public void testParameterizedMethod(int i){
    System.out.println(i);
}

static Stream<Integer> stringProvider(){
    return  Stream.of(1,2,3,4,5,6);
}

监控功能

引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

http://localhost:8080/springboot/actuator

端点可以使用web和jxm两种方式查看

jxm在cmd中使用jconsole调出来java面板来查看

也可以将端点暴露在web中

# management
management:
  endpoints: #配置所有断点
    enabled-by-default: true #默认开启所有端点
    web:
      exposure:
        include: '*'  #以web方式暴露所有端点
  endpoint: #配置一个端点
    health:
      show-details: always #总是显示健康详细信息
      enabled: true #启用某一个该端点,默认启用

定制Endpoint

Boot Admin Server

Boot Admin Server是一个可视化监控界面

引入

<!--服务端-->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.5.1</version>
</dependency>
<!--客户端-->
<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.5.0</version>
</dependency>
#客户端向服务端发送报告数据
spring.boot.admin.client.url=http://localhost:8888
#如果域名解析失败,打开使用ip注册
spring.boot.admin.client.instance.prefer-ip=true
#定义客户端名称
spring.application.name=spring-boot-study

高级特性

profile环境切换

为了方便多环境适配,springboot简化了profile功能

application-profile功能:

    默认配置文件application.yaml;任何时候都回家在,
    指定环境配置文件 application-[env].yaml
    激活指定环境
            配置文件激活
spring.profiles.active=[env]

​ 命令激活 :java -jar xxx.jar --spring.profiles.active=[env]
​ 默认配置与环境配置同时生效,同名配置profile配置优先

@profile注解

可以放在类上,也可以放在方法上。表面在指定环境下,该类或者该方法才会生效

分组功能

#激活某一分组
spring.profiles.active=myprod 

spring.profiles.group.myprod[0]=ppd
spring.profiles.group.myprod[1]=prod

spring.profiles.group.mytest[0]=test

外部配置源

常用:java属性文件,YAML文件,环境变量,命令行参数

配置文件查找位置

1、classpath 根路径
2、classpath 根路径下config目录
3、jar 当前目录
4、jar包当前目录的config目录
5、/config 子目录的直接子目录

由上至下加载,后加载的的覆盖前面加载的相同内容

配置文件加载顺序

1.当前ar包内部的 application properties和 application. yml
2.当前ar包内部的 application-{ profile). properties和 application-{ profile). yml
3.引用的外部jar包的 application. properties和 application. ym
4.引用的外部ar包的 application-profile. properties和 application-{ profile). ym

自定义starter

声明:冰醋酸|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Spring Boot


一切随缘