SpringBoot

一、简介

Spring Boot是Spring公司的一个顶级项目,和Spring Framework是一个级别的。

不需要编写xml配置文件,通过利用pring Framework 4 自动配置特性完成配置。

启动器

  就是引入相关的依赖,通过java配置的方式完成自动配置。

1. 特征

  1. 使用Spring Boot可以创建独立的Spring应用程序。

  2. 在Spring Boot中直接嵌入了Tomcat、Jetty、Undertow等Web 容器,所以在使用SpringBoot做Web开发时不需要部署WAR文件。

  3. 通过提供自己的启动器(Starter)依赖,简化项目构建配置。

  4. 尽量的自动配置Spring和第三方库。

  5. 绝对没有代码生成,也不需要XML配置文件。

2. 版本

  1. SNAPSHOT:快照版,即开发版。

  2. CURRENT:最新版,但是不一定是稳定版。

  3. GA:General Availability,正式发布的版本。

3. 核心

起步依赖

  起步依赖本质上是一个Maven项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。 简单的说,起步依赖就是将具备某种功能的坐标打包到一起,并提供一些默认的功能。

自动配置

  Spring Boot的自动配置是一个运行时(更准确地说,是应用程序启动时)的过程,考虑了众多因素,才决定 Spring配置应该用哪个,不该用哪个。该过程是Spring自动完成的。

二、启动类

启动类就是SpringBoot应用的入口,基于mian方法运行的。

1. 新建启动类

 启动类在启动时会扫描同包或者子包下的注解,不能直接放在java文件夹中,必须在包中新建启动类。所以应该放在基础包下。

命名规范:xxxApplication

例:

@SpringBootApplication
public class Springboot01Application {
    public static void main(String[] args) {
        SpringApplication.run(Springboot01Application.class, args);
    }
}

2. 启动类与启动器区别

启动类是项目的入口,启动器表示jar包的坐标

三、resources目录结构

1. 目录结构

   static:存放静态资源(css、js、图片、静态html等)

  templates:存放模板引擎 (thymeleaf,FreeMarker等视图模板)

  application.properties:SpringBoot配置文件。也可以自定义config文件夹存放配置文件。

注意:

  static目录是SpringBoot可以直接识别的目录,会将其中的静态资源编译到web项目中,并放到tomcat中使用。静态资源的访问路径中无需声明static。例如: http://localhost:8080/a.png

  IDEA中经常出现放在static下的静态文件即使重启也不被编译。需要通过Maven面板进行清空缓存,重新编译启动即可识别。

四、SpringBoot配置文件

SpringBoot提供一个名为 application 的全局配置文件,支持properties和yml格式

1. properties格式

例:

#配置端口号
server.port=8888
#配置项目名:必须以'/'开头,不能以'/'结尾
server.servlet.context-path=/test

2. yml格式

YML格式配置文件的扩展名可以是yaml或者yml,非常适合用来做以数据为中心的配置文件。

2.1 格式要求

  1. 大小写敏感

  2. 使用缩进代表层级关系

  3. 缩进不允许使用tab,只允许空格

  4. 相同的部分只出现一次

  5. '#'表示注释

2.2 书写格式

  • 字面量:date、boolean、string、number、null

  • 对象:键值对。map、hash、javabean

  • 数组:array、list、queue、set

① 字面量

server:
   port: 8888

②对象

复制代码
user1: {"id":10, "name":"admin"}
  user2:
    id: 20
    name: guest
  users2:
    - id: 1000
      name: users1000
    - id: 2000
      name: users2000
  map:
    k1:
      id: 19
      name: map1
    k2:
      id: 29
      name: map2
复制代码

③数组

复制代码
myparam:
  list1: v1,v2,v3
  list2:
    - v11
    - v12
    - v13
  array1: a1,a2,a3
  array2:
    - a11
    - a12
    - a13
复制代码

3. 配置文件存放位置

  1. 当前项目根目录中

  2. 当前项目根目录下的一个/config子目录中

  3. 项目的resources即classpath类路径中

  4. 项目的resources即classpath类路径下的/config目录中

4. 配置文件加载顺序

可以有多个配置文件

4.2 不同位置的加载顺序

  1. config/application.properties

  2. config/application.yml

  3. application.properties

  4. application.yml

  5. resources/config/application.properties

  6. resources/config/application.yml

  7. resources/application.properties

  8. resources/application.yml

4.3 不同格式加载顺序

在同一个目录中的配置文件先加载properties格式 后加载yml格式的。

对于多个配置文件配置了同一个属性,最先配置的生效。

五、整合MyBatis

1. 依赖启动器

复制代码
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>
复制代码

2. 配置文件

application.yml

复制代码
# 数据源(数据库连接池) 配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  # 加载mybatis配置文件(mybatis有特殊配置时使用)
  # config-location: classpath:mybatis/mybatis-config.xml  
  
  # 加载MyBatis的mapper.xml映射文件(映射接口和映射文件路径不一致时使用)
  # mapper-locations: classpath:mybatis/*.xml              
  
  type-aliases-package: com.xxx.pojo        # 实体类定义别名
复制代码

3. 启动类

在启动类上使用@MapperScan注解表示扫描指定包中的mapper接口

不写@MapperScan时要在每个Mapper接口上加@Mapper注解

六、整合Druid

1. 启动器

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.11</version>
</dependency>

2. 配置文件

复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8
    username: root
    password: root
    # 使用的连接池
    type: com.alibaba.druid.pool.DruidDataSource
     # 连接池的配置信息
    druid:
      # 初始化大小,最小,最大
      initial-size: 5
      max-active: 30
      min-idle: 5
      # 配置获取连接等待超时的时间
      max-wait: 60000
      validation-query: SELECT 1 FROM DUAL
      #配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,slf4j
      # 配置DruidStatViewServlet
      stat-view-servlet:
        # 登录名
        login-username: admin
        # 登录密码
        login-password: admin
        url-pattern: /druid/*
        # IP白名单(没有配置或者为空,则允许所有访问)
        allow: 192.167.10.1,127.0.0.1
        reset-enable: false
        # 必须启用,要不会404
        enabled: true
mybatis:
  # 起别名
  type-aliases-package: com.xxx.pojo
  # 扫描映射文件
  # mapper-locations: classpath:mybatis/*.xml 
application.yml
复制代码

七、整合Junit

1. 添加依赖

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

2. 新建测试类

测试类需要有启动类的支持,

在test中与启动器相同的包路径或子包中新建测试类,或者添加 @SpringBootTest (classes={启动器类名.class})

注意:

  1. 测试类不能叫做Test

  2. 测试方法返回值必须是void

  3. 测试方法必须没有参数

复制代码
@SpringBootTest
public class MyTest {
    @Autowired
    UserMapper userMapper;
    @Test
    public void test(){
        User user = userMapper.selectById(1L);
    }
} 
复制代码

在springBoot2.4之前使用整合单元测试需要写 @SpringBootTest (classes={启动器类名.class})和RunWith(SpringRunner.class)

八、整合PageHelper

Spring Boot整合PageHelper不需要做任何配置文件的配置,添加依赖后就可以直接使用。

1. 添加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.2</version>
</dependency>

2. 直接使用

复制代码
@Service
@Transactional
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;
    public PageInfo<User> queryByPage(int pageNumber, int pageSize){
        // pageNumber当前页码   pageSize每页显示的条数
        PageHelper.startPage(pageNumber,pageSize);
        
        // 查询全部,查询方法必须是查询多行结果,且没有分页语法。否则无法在sql后面拼接limit子句。
        List<User> users = userMapper.selectAll();
        
        // PageInfo是分页查询所有查询结果封装的类,所有的结果都从这个类取
        PageInfo<User> pageInfo = new PageInfo<>(users);
        return pageInfo;
    }
}
UserServiceImpl.java
复制代码

PageHelper.startPage()要写在查询数据库代码之上。

底层拦截MyBatis的执行器根据传入的pageNum和pageSize进行sql的改造:查询总条数、动态拼接limit进行分页查询

九、整合logback

Logback是由log4j创始人设计的一个开源日志组件。

Spring Boot默认使用Logback组件作为日志管理。

在Spring Boot项目中我们不需要额外的添加Logback的依赖,因为在spring-boot-starter或者spring-boot-starter-web中已经包含了Logback的依赖。

1. Logback读取配置文件的步骤

  1. 在classpath下查找文件logback-test.xml

  2. 如果文件不存在,则在classpath下查找logback.xml

2. 使用默认的logback.xml

logging:
  level:
    # 根日志级别
    root: warn
    # 具体包日志级别
    com.xxx.mapper: debug
  file:
    name: mylogs/my.log

3. 自定义logback

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
 <configuration>
    <!--定义日志文件的存储地址-->  
    <property name="LOG_HOME" value="logs/" />  
     
    <!-- 控制台输出 -->   
    <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
       <!-- 日志输出编码 -->  
        <layout class="ch.qos.logback.classic.PatternLayout">   
             <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
        </layout>   
    </appender>   
     
    <!-- 按照每天生成日志文件 -->   
    <appender name="RollingFile"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>   
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>   
        <layout class="ch.qos.logback.classic.PatternLayout">  
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>   
       </layout> 
        <!--日志文件最大的大小-->
       <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
         <MaxFileSize>10MB</MaxFileSize>
       </triggeringPolicy>
    </appender>     

    <!-- 日志输出级别 -->
    <root level="info">   
        <appender-ref ref="Stdout" />   
        <appender-ref ref="RollingFile" />   
    </root>

    <logger name="com.xxx.mapper" level="Trace"></logger>
</configuration>
logback.xml
复制代码

十、整合Thymeleaf

1. Thymeleaf介绍

Thymeleaf的主要目标是将优雅的自然模板带到开发工作流程中,并将HTML在浏览器中正确显示,并且可以作为静态原型,让开发团队能更容易地协作。

Thymeleaf能够处理HTML,XML,JavaScript,CSS,纯文本。

Thymeleaf是原生的,不依赖于标签库,它能够在接受原始HTML的地方进行编辑和渲染。

Thymeleaf在Spring Boot项目中放入到resources/templates中。这个文件夹中的内容是无法通过浏览器URL直接访问的(和WEB-INF效果一样),所有Thymeleaf页面必须先走控制器。

模板引擎的示意图:(模板+数据模型=输出) 

 

2. 使用

2.1 引入依赖

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

2.2 新建html

在resources的templates目录中新建index.html

2.3 Thymeleaf视图解析器

 所以控制单元直接响应名字即可。

3. 语法

使用thymeleaf语法要添加命名空间

<html xmlns:th="http://www.thymeleaf.org" >

3.1 th:text

将th:text中的值添加到标签文本节点中,标签中有内容会覆盖 

<h2 th:text="hello"></h2>

3.2 获取域对象中的数据

3.2.1 HttpServletRequest

复制代码
<span th:text="${#httpServletRequest.getAttribute('req')}"></span>
<span th:text="${#httpServletRequest.getAttribute('mod')}"></span>
<span th:text="${#httpServletRequest.getAttribute('user')}"></span>
<span th:text="${#httpServletRequest.getAttribute('user').uid}"></span>
<hr>
<span th:text="${#request.getAttribute('req')}"></span>
<span th:text="${#request.getAttribute('mod')}"></span>
<span th:text="${#request.getAttribute('user')}"></span>
<span th:text="${#request.getAttribute('user').uname}"></span>
<hr>
<span th:text="${req}"></span>
<span th:text="${mod}"></span>
<span th:text="${user}"></span>
<span th:text="${user.password}"></span>
获取请求域对象的数据
复制代码

3.2.2 HttpSession

复制代码
 <!-- 将th:text中的值添加到标签文本节点中,标签中有内容会覆盖 -->
<span th:text="${#httpSession.getAttribute('ses')}"></span>
<span th:text="${#httpSession.getAttribute('mod')}"></span>
<span th:text="${#httpSession.getAttribute('user')}"></span>
<span th:text="${#httpSession.getAttribute('user').uid}"></span>
<hr>
<span th:text="${#session.getAttribute('ses')}"></span>
<span th:text="${#session.getAttribute('mod')}"></span>
<span th:text="${#session.getAttribute('user')}"></span>
<span th:text="${#session.getAttribute('user').uname}"></span>
<hr>
<span th:text="${session.ses}"></span>
<span th:text="${session.mod}"></span>
<span th:text="${session.user}"></span>
<span th:text="${session.user.password}"></span>
获取会话域对象的数据
复制代码

3.2.3 ServletContext

复制代码
<!-- 将th:text中的值添加到标签文本节点中,标签中有内容会覆盖 -->
<span th:text="${#servletContext.getAttribute('app')}"></span>
<span th:text="${#servletContext.getAttribute('mod')}"></span>
<span th:text="${#servletContext.getAttribute('user')}"></span>
<span th:text="${#servletContext.getAttribute('user').uname}"></span>
<hr>
<span th:text="${application.app}"></span>
<span th:text="${application.mod}"></span>
<span th:text="${application.user}"></span>
<span th:text="${application.user.password}"></span>
获取应用域对象中的数据
复制代码

3.3 th:value

  • 设置表单元素value属性时使用。

  • 注意:不能为空,否则状态码500

<input type="text" th:value="${name}"/>

3.4 th:if

  • 进行逻辑判断。如果成立该标签生效(显示),如果不成立,此标签无效(不显示)。

  • 注意:判断条件中逻辑判断符号写在${}外面的

<span th:if="${name}!='张三'">会显示</span>

3.5 th:each

  • 循环遍历。

  1. th:each=”u,i :${list}” 其中i表示迭代状态。

  2. index:当前迭代器的索引 从0开始

  3. count:当前迭代对象的计数 从1开始

  4. size:被迭代对象的长度

  5. even/odd:布尔值,当前循环是否是偶数/奇数 从0开始

  6. first:布尔值,当前循环的是否是第一条,如果是返回true否则返回false

  7. last:布尔值,当前循环的是否是最后一条,如果是则返回true否则返回false

<tr th:each="u : ${list}">
        <td th:text="${u.uid}" ></td>
        <td th:text="${u.uname}"></td>
    </tr>

3.6 th:href

  • 设置href属性的。取值使用@{}取值 

<a th:href="@{/del(uid=1,uname='zs')}">跳转一</a>
<!-- 获取作用域值-->
<a th:href="@{/del(uid=${uid})}">跳转二</a>

在前端的效果就是 : /del?uid=1

3.7 th:onclick

  • 点击传递参数的单击事件

<a href="javascript:void(0)"  th:onclick="'del('+${user.uid}+')'">删除</a>
<script>
    function del(id) {
        console.log("接收的数据:" , id);
    }
</script>

3.8 字符串操作

Thymeleaf提供了一些内置对象,内置对象可直接在模板中使用。这些对象是以#引用的。

  1. 引用内置对象需要使用#

  2. 大部分内置对象的名称都以s结尾。如:strings、numbers、dates

方法解释
${#strings.isEmpty(key)} 判断字符串是否为空,如果为空返回true,否则返回false
${#strings.contains(msg,'T')} 判断字符串是否包含指定的子串,如果包含返回true,否则返回false
${#strings.startsWith(msg,'a')} 判断当前字符串是否以子串开头,如果是返回true,否则返回false
${#strings.endsWith(msg,'a')} 判断当前字符串是否以子串结尾,如果是返回true,否则返回false
${#strings.length(msg)} 返回字符串的长度
${#strings.indexOf(msg,'h')} 查找子串的位置,并返回该子串的下标,如果没找到则返回-1
${#strings.substring(msg, 0, 3)} 截取字串

 

3.9 日期处理

方法解析
${#dates.format(key)} 格式化日期,默认的以浏览器默认语言为格式化标准
${#dates.format(key,'yyyy/MM/dd')} 按照自定义的格式做日期转换
${#dates.day(key)} Day:取日
${#dates.month(key)} Month:取月
${#dates.year(key)} Year:取年

 

十一、设置异常页面

1. 设置具体的状态码页面

在resources下新建error文件夹,

在error文件夹中根据状态码设置对应的页面,例如:500状态码对应的页面就是 500.html

2. 使用x模糊匹配

例如:

当出现5开头状态码的错误时,显示页面可以命名为5xx.html

当出现50开头状态码的错误时,显示页面可以命名为50x.html

3. 同一错误显示页面

在templates下新建error.html。如果项目中不存在具体状态码的页面或没有使用x成功匹配的页面时,显示error.html作为错误显示页面。

十二、异常处理机制

1. 通过@ExceptionHandler注解处理异常

复制代码
@Controller
public class UsersController {
    @RequestMapping("showInfo")
    public String showInfo(){
        String str = null;
        str.length();
        return "ok";
    }

    @ExceptionHandler(value = {java.lang.NullPointerException.class} )
    public ModelAndView nullpointExcepitonHandler(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("err",e.toString());
        mv.setViewName("error1");
        return mv;
    }
}
局部异常配置
复制代码

只有当前类出现异常才会被处理

2. 通过@ControllerAdvice与@ExceptionHandler注解处理异常

复制代码
/**
 * 全局异常处理类
 */
@ControllerAdvice
public class GlobalException {
    @ExceptionHandler(value = {java.lang.NullPointerException.class} )
    public ModelAndView nullpointExcepitonHandler(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("err",e.toString());
        mv.setViewName("error1");
        return mv;
    }

    @ExceptionHandler(value = {java.lang.ArithmeticException.class} )
    public ModelAndView arithmeticExceptionHandler(Exception e){
        ModelAndView mv = new ModelAndView();
        mv.addObject("err",e.toString());
        mv.setViewName("error2");
        return mv;
    }
}
全局异常处理类
复制代码

3. 通过SimpleMappingExceptionResolver对象处理异常

复制代码
/**
 * 全局异常 
 * SimpleMappingExceptionResolver
 */ 
@Configuration
public class GlobalException2 {

    /**
     * 此方法返回值必须是SimpleMappingExceptionResolver对象
     * @return
     */
    @Bean
    public SimpleMappingExceptionResolver getSimpleMappingExceptionResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        /*
         * 参数一:异常类型,并且是全名
         * 参数二:视图名称
         */
        properties.put("java.lang.NullPointerException","error3");
        properties.put("java.lang.ArithmeticException","error4");

        resolver.setExceptionMappings(properties);
        return resolver;
    }

}
全局异常
复制代码

4. 通过自定义HandlerExceptionResolver对象处理异常、

复制代码
/**
 * 自定义HandlerExceptionResolver对象处理异常
 * 必须要实现HandlerExceptionResolver
 */
@Component
public class GlobalException3 implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) {
        ModelAndView mv = new ModelAndView();
        //判断不同异常类型,做不同视图的跳转
        if(e instanceof NullPointerException){
            mv.setViewName("error5");
        }
        if(e instanceof ArithmeticException){
            mv.setViewName("error6");
        }
        mv.addObject("error",e.toString());
        return mv;
    }
}
自定义异常处理
复制代码

十三、开发者工具热部署

1. 引入依赖

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

2. 相关设置

 

3. 使用热部署

3.1 自动热部署

  • idea失去焦点后,自动热部署。

3.2 手动热部署

  • ctrl+f9

十四、文件上传、下载

1. 文件上传

前端必须是post请求,需要设置enctype="Multipart/form-data"

后端上传文件解析器已经被自动配置,直接只用MultipartFile接收上传的文件。

所以在文件上传时都会生成一个全局唯一的文件名。常见有两种方式:

(1)时间戳+随机数

(2)UUID

复制代码
/**
 * 文件上传控制单元方法实现
 * @param photo 名字必须和表单中文件域的name属性值相同
 * @return
 * @throws IOException transferTo抛出的异常,可以使用try...catch处理异常。示例中为了让代码看起来简洁直接抛出了。
 */
@RequestMapping("/upload")
@ResponseBody
public String upload(MultipartFile photo, HttpServletRequest request) throws IOException {
    // 判断上传文件流是否为空。如果不为空继续执行
    if(!photo.isEmpty()) {
        // 使用UUID生成文件名称
        // String fileName = UUID.randomUUID().toString();
        // 使用时间戳+随机数生成文件名
        long timeMillis = System.currentTimeMillis();
        Random random = new Random();
        String fileName = timeMillis + "" + random.nextInt(1000);
        // 获取上传时文件名
        String oldName = photo.getOriginalFilename();
        // 获取上传时文件的扩展名
        String suffix = oldName.substring(oldName.lastIndexOf("."));
        String suffix = oldName.substring(oldName.lastIndexOf("."));
        // 获取到当前项目images目录,发布到Tomcat后的绝对路径。
        String realPath = request.getServletContext().getRealPath("/images");
        photo.transferTo(new File(realPath,fileName + suffix));
        return "ok";
    }
    return "err";
}
上传文件
复制代码

2. 文件下载

如果希望所有的文件都是下载,而不是能打开则打开。可以在响应头中设置Content-Disposition参数为attachment。attachment结合filename可以设置下载文件的名称。

复制代码
@RequestMapping("/download")
public void download(HttpServletRequest req, HttpServletResponse response, String filename) {
    try {
        // filename=的值就是客户端看到的下载文件名称
         String newFilename = new String(filename.getBytes("utf-8"),"iso-8859-1");
        response.setHeader("Content-Disposition", "attachment;filename=" + newFilename);
        File file = new File(req.getServletContext().getRealPath("/images"), filename);
        FileInputStream fis = new FileInputStream(file);
        ServletOutputStream os = response.getOutputStream();
        IOUtils.copy(fis, os);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
下载文件
复制代码

十五、内容协商

根据客户端接收能力不同,返回不同媒体类型的数据。

只需引入依赖即可直接使用。

1. 导入依赖

SpringBoot 已经包含了 json的相关依赖,所以只需再引入xml的相关依赖即可

<dependency>
   <groupId>com.fasterxml.jackson.dataformat</groupId>
   <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

2. 接收不同格式数据

复制代码
<script>
    function getJson(){
        $.ajax({
            url:"show",
            type:"get",
            dataType:"json",
            success:function(data){
                console.log(data);
            }
        });
    }
    function getXml(){
        $.ajax({
            url:"show",
            type:"get",
            dataType:"xml",
            success:function(data){
                console.log(data);
            }
        });
    }
</script>
复制代码

后端根据ajax请求所需数据的格式解析出对应格式的数据返回

十六、更换内置服务器

SpringBoot提供了3款内置的服务器:

  • tomcat(默认):apache出品,粉丝多,应⽤⾯⼴,负载了若⼲较重的组件

  • jetty:更轻量级,负载性能远不及tomcat

  • undertow:负载性能勉强跑赢tomcat

想⽤哪个,加个坐标就OK。前提是把tomcat排除掉,因为tomcat是默认加载的。

SpringBoot默认使用tomcat服务器,切换服务器只需修改依赖

复制代码
<dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
         <exclusions>
             <exclusion>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-tomcat</artifactId>
             </exclusion>
         </exclusions>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-undertow</artifactId>
     </dependency>
</dependencies>
复制代码

<exclusion>标签表示排斥、排除

十七、多环境配置文件

 

Spring Boot应用支持多环境配置文件,在开发、测试、上线等不同的环境中,可以激活不同的环境配置,解决切换环境时修改配置文件的麻烦。

1. 环境配置文件定义要求

  1. 必须定义在application配置文件所在目录中,即只能是 classpath、classpath/config、 root、 root/config 等四个目录之一。

  2. 配置文件命名是application-xxx。其中xxx即环境名称,如: application-dev、application-test、application-prod等。

  3. application配置文件中需要激活相应的环境配置,使用 spring.profiles.active=环境名称 来激活。或者在启动时,增加启动参数 -Dspring.profiles.active=环境名称

  4. 配置文件类型: yaml、yml、properties。

  5. 可以一次性激活若干环境配置,按照激活顺序依次读取环境配置,后读取的覆盖先读取的同名配置。不同名的配置使用保留|追加方式处理。

2. 环境配置文件使用方式

2.1 在application配置文件激活

  • 配置公共的配置

  • 激活指定的配置文件

# 选择使用的环境
spring:
  profiles:
    active: prop
# 配置通用的配置

十八、整合拦截器

1. 新建拦截器

@Component
public class DemoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行拦截器");
        return false;
    }
}

注意:在类上加@Component注解

2. 配置拦截器

使用java配置类的方式进行配置

复制代码
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Autowired
    private DemoInterceptor demoInterceptor;
    //配置拦截器的映射
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(demoInterceptor).addPathPatterns("/*").excludePathPatterns("/login");
    }
}
复制代码

注意:

类上有注解@Configuration。此类相当于SpringMVC配置文件。

addPathPattern(): 拦截哪些URL。 /* 拦截全部

excludePathPatterns(): 不拦截哪些URL。当和addPathPattern()冲突时,excludePathPatterns()生效。

十九、原生组件注入

注入Servlet的过滤器,监听器。

1. 新建过滤器、监听器

在基础包下新建filter包

新建过滤器:

复制代码
@WebFilter("/*")//过滤的url
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("执行访问资源之前 执行");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("执行访问资源之后 执行");
    }
}
复制代码

在基础包下新建listener包

新建监听器(监听域对象的行为)

复制代码
@WebListener
public class MyRequestListener extends RequestContextListener {
    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        System.out.println("请求对象初始化了");
    }
    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        System.out.println("请求对象初销毁了");
    }
}
复制代码

二十、SpringBoot项目打jar包

1. 添加插件

复制代码
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <fork>true</fork>
            </configuration>
        </plugin>
    </plugins>
</build>
复制代码

2. 打包

在maven面板的Lifecycle中点击package

即可完成打包

3. 运行jar包

使用命令行,在jar包所在目录执行 java  -jar 文件名.jar

 二十一、常用注解

  1. @Configuration

  2. @Bean

  3. @Import

  4. @Conditional

  5. @ConfigurationProperties

  6. @EnableConfigurationProperties

1. @Configuration

  • 通常使用在配置类(组件)上。

  • 可以被@ComponentScan扫描到

2. @Bean

  • 通常用在方法上,方法的返回值交由IOC管理,IOC中的默认id为方法名。

3. @Import

  • 通常用在配置类上,导入其它的类,被导入的类交由IOC管理,类中的方法有@Bean注解,同上。

4. @Conditional

  • 提交注解,符合条件才会进行。

注解例如解释
@ConditionalOnBean @ConditionalOnBean(User.class) Spring容器中存在对应的bean生效
@ConditionalOnMissingBean @ConditionalOnMissingBean(name = "user") Spring容器中不存在对应的bean生效
@ConditionalOnClass @ConditionalOnClass(Menu.class) 类加载器可以加载到对应的类生效
@ConditionalOnMissingClass @ConditionalOnMissingClass(Menu.class) 类加载器加载不到对应的类生效
@ConditionalOnProperty @ConditionalOnProperty(prefix = “spring.aop”) 应用环境中的属性满足条件生效
@ConditionalOnSingleCandidate @ConditionalOnMissingClass(Menu.class) 表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean
@ConditionalOnWebApplication   当前应用是Web应用生效
@ConditionalOnNotWebApplication   当前应用不是Web应用生效

5. @ConfigurationProperties

  • 读取Springboot配置文件中的数据,自动注入到属性中。

  • 前提是该类由spring容器管理

6. @EnableConfigurationProperties

  • 很多时候我们注入的类(组件)是第三方提供的,我们不可能在组件上添加@Component注解,通过@EnableConfigurationProperties注解注册第三方组件

二十二、SpringBoot启动流程

1. 启动流程

以该启动类为例:

 把启动类和主方法的参数传入run方法并执行:

primarySource就是传入的启动类的类对象 

debug跟进去

在这里创建了一个spring的应用程序并run()

看一下它的构造方法

 定义了一些相关的信息:

复制代码
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    //资源加载器。默认为null
   this.resourceLoader = resourceLoader;
    //判断主类是否为null
   Assert.notNull(primarySources, "PrimarySources must not be null");
    //主类存储到set集合中
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    //获取容器的类型SERVLET。三种取值:NONE SERVLET REACTIVE
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //获取初始化器,默认为0
   this.bootstrapRegistryInitializers = new ArrayList<>(
         getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
    //从META-INF/spring.factories中读取ApplicationContextInitializer初始化器
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    //从META-INF/spring.factories中读取ApplicationListener监听器
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    //找到main方法所在的类
   this.mainApplicationClass = deduceMainApplicationClass();
}
相关解释
复制代码

再看run方法:

复制代码
public ConfigurableApplicationContext run(String... args) {
    //创建StopWatch对象用于统计执行的耗时
    long startTime = System.nanoTime();
    //创建引导上下文对象
    DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    //上下文对象
    ConfigurableApplicationContext context = null;
    //设置简单图像处理,多用于缺失显示器,键盘,鼠标等情况
    configureHeadlessProperty();
    //获取运行时的监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    //启动监听器
    listeners.starting(bootstrapContext, this.mainApplicationClass);
    try {
        //将命令行存储到ApplicationArguments对象中
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        //根据监听器,引导上下文,命令行参数获取配置环境
        ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
        //设置需要忽略的内容
        configureIgnoreBeanInfo(environment);
        //打印banner
        Banner printedBanner = printBanner(environment);
        //根据应用程序类型创建对应的上下文对象 AnnotationConfigServletWebServerApplicationContext
        context = createApplicationContext();
        //Spring5.3新添加的内容,负责记录执行步骤和时间
        context.setApplicationStartup(this.applicationStartup);
        //准备上下文对象
        prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
        //刷新上下文对象
        /*
            1.完成自动装配
            2.如果项目为web项目,启动内置的服务器
        */
        refreshContext(context);
        //刷新后的回调,默认为空方法。可以重写该方法完成刷新上下文后的自定义操作
        afterRefresh(context, applicationArguments);
        //计算执行时间
        Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
        if (this.logStartupInfo) {
            //打印启动日志信息
            new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
        }
        //启动上下文的监听器
        listeners.started(context, timeTakenToStartup);
        //启动运行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        //计算执行时间
        Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
        //完成监听器准备操作
        listeners.ready(context, timeTakenToReady);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, null);
        throw new IllegalStateException(ex);
    }
    //返回上下文对象 AnnotationConfigServletWebServerApplicationContext
    return context;
}    
run
复制代码

注意run方法中的refreshContext(context)方法

 
重点关注refresh(context)方法

 点进去

 执行了父类的refresh()方法

复制代码
@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

            // Prepare this context for refreshing.
            prepareRefresh();

            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);

            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);

                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);

                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();

                // Initialize message source for this context.
                initMessageSource();

                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();

                // Initialize other special beans in specific context subclasses.
                onRefresh();

                // Check for listener beans and register them.
                registerListeners();

                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);

                // Last step: publish corresponding event.
                finishRefresh();
            }

            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }

                // Destroy already created singletons to avoid dangling resources.
                destroyBeans();

                // Reset 'active' flag.
                cancelRefresh(ex);

                // Propagate exception to caller.
                throw ex;
            }

            finally {
                // Reset common introspection caches in Spring's core, since we
                // might not ever need metadata for singleton beans anymore...
                resetCommonCaches();
                contextRefresh.end();
            }
        }
    }
父类的refresh方法
复制代码

注意invokeBeanFactoryPostProcessors(beanFactory);

该方法中完成了自动装配
如果是web项目,在onRefresh()方法中执行creatWebServer()创建并启动Tomcat服务器

2. 自动装配

启动类上只有一个@SpringBootApplication注解

@SpringBootApplication是一个组合注解

前四个都是元注解

先看@SpringBootConfiguration

 本质就是@Configuration,代表配置类(该类由spring容器管理)

@component就是扫描@Component及其子注解,扫描到类的都交给spring容器管理

重点是@EnableAutoConfiguration

 先看@AutoConfigurationPackage

 @Import(AutoConfigurationPackages.Registrar.class)

该类的内部类被导入了。

看一下这个内部类

 主要的作用就是注册启动类所在的包(基础包),这样可以通过@ComponentScan扫描同包和子包下的@Component及子注解

也就是先注册了启动类所在的包,后续该包及子包中存在需要自动配置的类可以进行自动装配。

再回到@EnableAutoConfiguration

 还有个@Import(AutoConfigurationImportSelector.class)

@Import除了将导入的类注册到IoC中,还可以进行一些其他的操作:

spring提供了ConfigurationClassParser,可以获取@Import导入的类

该类中的一个内部类DeferredImportSelectorGrouping中的getImports()方法为AutoConfigurationImportSelector的入口(获取AutoConfigurationImportSelector并执行)。

复制代码
        /**
         * Return the imports defined by the group.
         * @return each import with its associated configuration class
     *返回该组定义的导入。返回:每个导入及其关联的配置类
         */
        public Iterable<Group.Entry> getImports() {
            for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
                this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                        deferredImport.getImportSelector());
            }
            return this.group.selectImports();
        }
复制代码

当前 deferredImport即为启动类,调用了getImportSelector()方法返回AutoConfigurationImportSelector类对象

获取到导入的AutoConfigurationImportSelector后执行process方法

 在该方法中将拿到的导入类转成AutoConfigurationEntry类型(自动配置信息的键值对)

AutoConfigurationEntry是AutoConfigurationImportSelector的一个内部类

 接着调用getAutoConfigurationEntry(annotationMetadata)方法

进入getCandidateConfigurations()方法

 该方法中调用loadFactoryNames()方法

传入两个参数,第一个是Spring Factories Loader工厂类,第二个是bean类加载器

 执行loadFactoryNames()方法

 进入loadSpringFactories()方法

 此时result为19 说明之前已经加载过相关配置了

 根据上面的路径进行加载获取到19个配置信息,然而这些配置信息并不是都需要。

返回到loadFactoryNames()方法中调用getOrDefault()方法,参数factoryTypeName就是

根据键获取对应的key最后拿到需要的配置信息。

 发现都是第三方的相关配置,说明先加载的是第三方的配置

返回到getCandidateConfigurations()方法,再获取配置文件变成152个了,这次加载的是

org.springframework.boot.autoconfigure.AutoConfiguration.imports的配置文件

 但这些配置文件也不是全部都需要配置的

再回到getAutoConfigurationEntry(annotationMetadata)方法

 先调用removeDuplicates()方法 移除重复的配置

再通过 获取移除的类、检查移除的类、移除不需要的配置。(在启动类上@SpringBootApplication可以设置排除类的参数)

 最后 filter()进行过滤

剩下的就是需要的配置信息了

就可以通过包名+类名拿到类对象及注解进行自动配置了。

3. 总结

SpringBoot启动 先执行run方法

先调用refreshContext()

在该方法中调用invokeBeanFactoryPostProcessors(),在此方法中完成自动装配

调用onrefresh(),如果是web项目,创建Tomcat服务器,启动Tomcat服务器

最后执行其他的操作。

 

 

posted @   ygdgg  阅读(86)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示