第一次复习SpringBoot知识点记录

引言

本篇文章是基于学习过SpringBoot的基础上所做的记录。

创建项目

手动创建

依赖配置

需要配置parent,保证版本一致性

<!--    继承springboot父项目-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.6</version>
    </parent>
    
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

image-20220627132730034

创建启动类
@SpringBootApplication
public class SpringBoot_1_Application {
    public static void main(String[] args) {
        //启动springboot应用
        //参数1:指定入口类的类对象.class
        //参数2:main的函数参数
      SpringApplication.run(SpringBoot_1_Application.class,args);
    }
}
创建配置文件
server:
  port: 8888 # 修改tomcat端口号
#  servlet:
#    context-path: /springboot_1 #修改项目名 注意:项目名必须“/”开头

快速初始化

这里如何快速初始化就不介绍了

image-20220627132814709

注解说明

spring项目配置类

@SpringBootApplication

修饰范围:用在类上 只能用在入口类上 只能出现一次
作用:标识这个类是一个springboot入口类 启动整个springboot项目总入口

组合注解:就是由多个注解组合而成一个注解

  •  元注解:用来修饰注解的注释
    
  •      @Target:指定注解作用范围
    
  •      @Retention:指定注解什么时候能有效
    
@SpringBootConfiguration

这个注解就是用来自动配置 spring springmvc(初始化 servlet …) 相关环境

@EnableAutoConfiguration

开启自动配置 自动配置核心注解 自动配置spring相关环境 自动与项目中 引入第三方技术自动配置其环境

mybatis-springboot\redis-springboot等第三方技术

@ComponentScan

组件扫描 根据注解发挥注解作用,在启动类中默认扫描当前包及其子包下的注解

@Configuration

将当前类作为spring容器的配置类,在其中可以添加spring容器中的各项配置

@EnableTransactionManagement

使项目支持事务注解@Transactional

对象管理类

@Component

作用:将当前类注入到spring容器中

@Repository

**作用:**一般用于持久层

@Service

**作用:**一般用于业务层

@Controller

**作用:**一般用于表现层

@Bean

可用于将java本身的一些类注入到spring容器中,方便我们进行使用

**前提:**这些bean都需要在@Configuration注解下进行创建

**作用:**将对象注入到spring容器中

属性注入类

@Autowired

作用:

  • 自动按照类型注入,只要容器中有唯一的一个bean对象和要注入的变量类型匹配,就可以注入成功。
  • 如何Ioc容器中没有任何bean对象与要注入的变量类型相匹配,则会报错。
  • 如何Ioc容器中有多个变量时,则需要结合@Qualifier注解实现。
@Qualifier

作用:

按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法注入时可以单独使用。

@Resource

**前提:**导入javax.annotation-api依赖

<dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.1</version>
        </dependency>

作用:

直接按照bean的id注入,它可以独立使用,当存在多个变量时,使用@Resource注解就不需要使用@Autowired和@Qualifier结合的注解。

@Value

**作用:**用于注入普通类型,在controller层的属性中通过@Value("${名字}")可以直接注入值,在实体类中直接注入对象则还需要其它操作(下面会介绍)

扩展使用:

注入yaml或properties文件中的值

提示:

  • 注入数组的时候多个元素用,隔开即可
  • 注入list的时候多个元素用,隔开即可
  • 注入map的时候,采用JSON数据格式即可"{'name':'小明','age':'18'}",但是使用@Value注入时必须加入"#(${属性})"进行注入
  • 注入对象时其属性必须要有set方法,否则注入会失败

实际开发需求示例:

需要配置key、token等属性,将其配在配置文件中,方便修改

  • oss对象存储
  • 手机短信验证
  • 支付宝支付
yml配置文件示例1
server:
  port: 8081
# 对空格的要求十分高
# 普通的key-value
# 注入到我们的配置类中
name: lzj

# 对象
student:
  name:lzj
  age: 18

# 行内写法
student2: {name: lzj,age: 20}

# 数组
pets:
  - cat
  - dog
  - pig

pets2: [cat,
yaml注入配置文件

yaml配置注入到实体类

1、在springboot项目中的resources目录下新建一个文件 application.yml

2、编写一个实体类 Dog,使用@Value注解给bean注入属性值;

@Component
public class Dog {
    @Value("旺财")
    private String name;
    @Value("3")
    private Integer age;
}

3、在SpringBoot的测试类下注入狗狗输出一下;

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Dog dog;

    @Test
    void contextLoads() {
        System.out.println(dog);
    }

}

结果成功输出,@Value注入成功

4、我们在编写一个复杂一点的实体类:Person 类

@Component//注册bean
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
    }

5、我们来使用yaml配置的方式进行注入,大家写的时候注意区别和优势,我们编写一个yaml配置!

person:
  name: lzj
  age: 18
  happy: true
  birth: 2001/01/18
  maps: {k1: v1,k2: v2}
  lists:
    - code
    - music
    - girl
  dog:
    name: 黑豆
    age: 5

6、我们刚才已经把person这个对象的所有值都写好了,我们现在来注入到我们的类中!

使用@ConfigurationProperties注解时可能会爆红,可以不用管,也可以按照第七步导入依赖

/*
@ConfigurationProperties作用:
将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉SpringBoot将本类中的所有属性和配置文件中相关的配置进行绑定
参数 prefix = “person” : 将配置文件中的person下面的所有属性一一对应
*/
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}

7、IDEA 提示,springboot配置注解处理器没有找到,让我们看文档,我们可以查看文档,找到一个依赖!

图片

图片

<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 --><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional>
</dependency>

8、确认以上配置都OK之后,我们去测试类中测试一下:

@SpringBootTest
class Springboot02ConfigApplicationTests {
    @Autowired
    private Person person;

    @Test
    void contextLoads() {
        System.out.println(person);
    }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cM21O7K-1657027402770)(F:/MyFile/java博客学习/SpringBoot/SpringBoot学习笔记.assets/image-20220308233037771.png)]

结果:所有值全部注入成功!

yaml配置注入到实体类完全OK!

properties注入配置

采用@PropertySource注解和@Value注解

【注意】properties配置文件在写中文的时候,会有乱码 , 我们需要去IDEA中设置编码格式为UTF-8;

settings–>FileEncodings 中配置;

image-20220308234856453

resources目录下编写lzj.properties文件

name=lzj

实体类中引用

@Component
//javaConfig 绑定我们配置文件的值,可以采取这些方式
//加载指定配置文件
@PropertySource(value = "classpath:lzj.properties")
public class Person {
    //SPEL表达式取出配置文件的值
    @Value("${name}")
    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,Object> maps;
    private List<Object> lists;
    private Dog dog;
}
对比

图片

配置文件

配置文件拆分

在开发项目过程中,通常有测试环境和生产环境,不同的环境需要不同的配置,所以我们通过编写一个公共配置文件,一个测试配置文件,一个生产配置文件,然后来进行整合实现

下面以端口号和项目名示例:

公共配置application.yml

公共配置:

  • 配置了端口号
  • 指定了生效的环境(简名即可)
server:
  port: 8888 # 修改tomcat端口号

spring:
  profiles:
    active: dev #让dev环境生效

测试配置application-dev.yml

测试配置文件

server:
  servlet:
    context-path: /springboot_1 #测试项目名

生产配置application-prod.yml

生产配置文件

server:
  servlet:
    context-path: /springboot #生产项目名

加载外部配置

在项目启动时我们可以通过配置外部的yml文件来替换项目中的yml文件

配置方式:

springboot打成jar包后运行时前面加上配置即可

java -jar --spring.config.location=绝对路径 xxx.jar

模板集成

在SpringBoot框架中默认模板推荐使用Thymeleaf模板

JSP模板集成

引入依赖
<!--c标签-->
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

<!--让内嵌的tomcat具有解析jsp功能-->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
创建webapp目录

java和resources的同级目录

修改视图配置
spring:
  mvc:
    view:
      prefix: / #视图前缀
      suffix: .jsp #视图后缀
运行
  1. 编写controller层

    @Controller
    public class HelloController {
    
        @RequestMapping("/jsp")
        public String jsp(){
            System.out.println("jsp");
            return "index";
        }
    }
    
  2. 引入插件启动或修改idea内嵌tomcat启动

    • 引入插件启动的方式只能通过点击Maven->Plugins->spring-boot->spring-boot:run来进行启动
    • 修改idea内嵌tomcat启动的方式可以直接通过idea启动的方式来进行启动

    引入插件启动

    <!--jsp运行插件-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    

    启动

    image-20220628165018123

    修改idea内嵌tomcat启动

    image-20220628165153632

    image-20220628165228419

    点击ok即可直接通过idea的run启动

配置修改jsp无需重启
server:
  servlet:
    jsp:
      init-parameters:
        development: true #开启jsp页面开发模式

thymeleaf模板集成

官方使用文档:https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#attribute-precedence

//源码中翻阅使用thymeleaf时默认的目录和文件后缀为
public static final String DEFAULT_PREFIX = "classpath:/templates/";

public static final String DEFAULT_SUFFIX = ".html";
  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  2. application配置文件

    # 关闭模板引擎的缓存
    spring.thymeleaf.cache=false
    
  3. 在需要使用的html页的标签后加上约束

    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    
语法

更多语法可以查看官方文档

获取数据(model或request)

<span th:text="${属性名}" />

获取并解析

#内容解析成html格式
<span th:utext="${属性名}" />

获取session中数据

<span th:text="${session.属性名}" />

格式化日期

<span th:text="${#dates.format(格式化值,'格式')}" />

直接遍历

th:each="变量:集合"

#遍历users中的数据
<ul>
    <li th:each="user:${users}">
    	id:<span th:text="${user.id}" />
    </li>
        
</ul>

获取遍历状态

th:each="变量(current_element当前遍历元素),变量(state遍历状态对象):集合"

<ul>
    <li th:each="user,state:${users}">
    	id:<span th:text="${user.id}" />
        state odd:<span th:text="${state.odd}" />
        state size:<span th:text="${state.size}" />
    </li>
        
</ul>

条件展示数据

例如:th:if="${age>23}"

引入css静态文件(static下的)

th:href="@{/}"

<link rel="stylesheet" th:href="@/demo.css">

引入javascript文件

th:src="@{/}"

<script th:src="@{/demo.js}"></script>

通过js代码获取应用名。通过thymeleaf语法获取项目名,使用thymeleaf内联表达式[[thymeleaf]]

比如:

<script>
    let contextPath="[[@{/}]]";
	console.log("项目名",contextPath);
</script>

整合Mybatis

  1. 引入依赖

    <!--mybaits-spring-boot-->
    <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>
    
    <!--druid-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    
    <!--mysql-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
  2. 编写yml配置文件

    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    
    # 整合mybatis
    mybatis.type-aliases-package=com.lzj.entity
    mybatis.mapper-locations=classpath:mapper/*.xml
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    
  3. 扫描dao层包

    在启动类上加上@MapperScan(“com.lzj.mapper”),其中com.lzj.mapper为你dao层路径

  4. 事务管理

    **注意:**可能还需要引入aop的包spring-boot-starter-aop

    不需要再做配置,在service层中直接使用@Transactional注解即可

本地测试类

  1. 引入依赖

    junit依赖和springboot的test依赖,我这里没有标明版本

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    
  2. 编写测试类

    这里需要注意,@RunWith和@SpringBootTest一定不能忘记了,否则不能测试运行springboot项目的

    这里的user中的值采用的是读取配置文件中的值的方式注入属性,可以不用管

    //@RunWith:表示启动这个单元测试类(单元测试类是不能够运行的),需要传递一个参数,必须是SpringRunner实例类型
    @RunWith(SpringRunner.class)
    //@SpringBootTest:标注当前的类是一个测试类,不会随同项目一块打包,classes为你项目的启动类
    @SpringBootTest(classes = SpringBoot_1_Application.class )
    public class Test1 {
        @Autowired
        private User user;
    
        @Test
        public void tt(){
            System.out.println(user);
        }
    }
    
  3. 测试

    image-20220628160026790

热部署

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional>
    </dependency>
    
  2. 配置热加载

     <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        
    <!--                    配置热加载-->
                        <executable>true</executable>
                        <fork>true</fork>
                        
                        <excludes>
                            <exclude>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </exclude>
                        </excludes>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
  3. File-Settings-Compiler-Build Project automatically

    img

  4. ctrl+shirft+A 搜索Registy(或者ctrl+alt+shift +/ 选择Registy)

    image-20220412130914638

  5. 勾上 Compiler autoMake allow when app running

    如果idea版本较高,没有该选项,则去:

    File > Settings > Advanced Settings > 选中 Allow auto-make to start even if developed application is currently running

    image-20220419233112282

  6. 启动配置中进行如下配置

    image-20220419233416947

    image-20220419233520279

  7. 配置成功

    • 修改类–>保存:应用会重启
    • 修改配置文件–>保存:应用会重启
    • 修改页面–>保存:应用不会重启,但会重新加载,页面会刷新

日志配置

集成logback日志,分为三个模块:logback-core、logback-access、logback-classic

我自己使用的是配置示例3

日志级别

由高到低

  • OFF | 关闭:最高级别,不打印日志。
  • FATAL | 致命:指明非常严重的可能会导致应用终止执行错误事件。
  • ERROR | 错误:指明错误事件,但应用可能还能继续运行。
  • WARN | 警告:指明可能潜在的危险状况。
  • INFO | 信息:指明描述信息,从粗粒度上描述了应用运行过程。
  • DEBUG | 调试:指明细致的事件信息,对调试应用最有用。
  • TRACE | 跟踪:指明程序运行轨迹,比DEBUG级别的粒度更细。
  • ALL | 所有:所有日志级别,包括定制级别。

所以,日志优先级别标准顺序为:

ALL < TRACE< DEBUG < INFO < WARN < ERROR < FATAL < OFF

如果日志设置为L,一个级别为P的输出日志只有当P >= L时日志才会输出。

即如果日志级别L设置INFO,只有P的输出级别为INFO、WARN,后面的日志才会正常输出。

配置示例1

配置打印日志
  1. 引入依赖

    <!-- Spring Boot Web 依赖 -->
     <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
    
  2. src/main/resources下创建logback-spring.xml

    <?xml version="1.0" encoding="UTF-8"?>  
    <configuration scan="true" scanPeriod="60 seconds">  
        <!-- 都说spring boot使用日志需要引入这个,但是我引入了之后总是打印两份日志,所以我去除了,并不影响使用 -->
        <!-- <include resource="org/springframework/boot/logging/logback/base.xml"/> -->
        <!-- 控制台设置 -->  
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">  
            <encoder>  
                <pattern>${CONSOLE_LOG_PATTERN}</pattern>  
            </encoder>  
        </appender>  
        <!-- INFO -->  
        <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">  
            <!-- 文件路径 ,注意LOG_PATH是默认值,
                它的配置对应application.properties里的logging.path值-->  
            <file>${LOG_PATH}/info/info.log</file>  
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!-- 文件名称 -->  
                <fileNamePattern>info/info-%d{yyyy-MM-dd}.log  
                </fileNamePattern>  
                <!-- 文件最大保存历史数量 -->  
                <MaxHistory>30</MaxHistory>  
            </rollingPolicy>  
            <encoder>  
                <pattern>${FILE_LOG_PATTERN}</pattern>  
            </encoder>  
            <filter class="ch.qos.logback.classic.filter.LevelFilter">  
                <level>INFO</level>  
                <onMatch>ACCEPT</onMatch>    
                <onMismatch>DENY</onMismatch>    
            </filter>  
        </appender>
    
        <!-- DEBUG -->  
        <appender name="debugAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">  
            <!-- 文件路径 ,注意LOG_PATH是默认值,
                它的配置对应application.properties里的logging.path值-->  
            <file>${LOG_PATH}/debug/debug.log</file>  
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!-- 文件名称 -->  
                <fileNamePattern>debug/debug-%d{yyyy-MM-dd}.log</fileNamePattern>  
                <!-- 文件最大保存历史数量 -->  
                <MaxHistory>30</MaxHistory>  
            </rollingPolicy>  
            <encoder>  
                <pattern>${FILE_LOG_PATTERN}</pattern>  
            </encoder>  
            <filter class="ch.qos.logback.classic.filter.LevelFilter">  
                <level>DEBUG</level>  
                <onMatch>ACCEPT</onMatch>    
                <onMismatch>DENY</onMismatch>    
            </filter>  
        </appender> 
         <!-- WARN -->  
        <appender name="warnAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">  
            <!-- 文件路径 ,注意LOG_PATH是默认值,
                它的配置对应application.properties里的logging.path值-->   
            <file>${LOG_PATH}/warn/warn.log</file>  
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!-- 文件名称 -->  
                <fileNamePattern>warn/warn-%d{yyyy-MM-dd}.log  
                </fileNamePattern>  
                <!-- 文件最大保存历史数量 -->  
                <MaxHistory>30</MaxHistory>  
            </rollingPolicy>  
            <encoder>  
                <pattern>${FILE_LOG_PATTERN}</pattern>  
            </encoder>  
            <filter class="ch.qos.logback.classic.filter.LevelFilter">  
                <level>WARN</level>  
                <onMatch>ACCEPT</onMatch>    
                <onMismatch>DENY</onMismatch>    
            </filter>  
        </appender> 
    
        <!-- ERROR -->  
        <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">  
            <!-- 文件路径 ,注意LOG_PATH是默认值,
                它的配置对应application.properties里的logging.path值-->  
            <file>${LOG_PATH}/error/error.log</file>  
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">  
                <!-- 文件名称 -->  
                <fileNamePattern>error/error-%d{yyyy-MM-dd}.log  
                </fileNamePattern>  
                <!-- 文件最大保存历史数量 -->  
                <MaxHistory>30</MaxHistory>  
            </rollingPolicy>  
            <encoder>  
                <pattern>${FILE_LOG_PATTERN}</pattern>  
            </encoder>  
            <filter class="ch.qos.logback.classic.filter.LevelFilter">  
                <level>ERROR</level>  
                <onMatch>ACCEPT</onMatch>    
                <onMismatch>DENY</onMismatch>    
            </filter>  
        </appender>
          <logger name="org.springframework" additivity="false">
            <level value="ERROR" />
            <appender-ref ref="STDOUT" />
            <appender-ref ref="errorAppender" />
        </logger>
    
        <!-- 由于启动的时候,以下两个包下打印debug级别日志很多 ,所以调到ERROR-->
        <logger name="org.apache.tomcat.util" additivity="false">
            <level value="ERROR"/>
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="errorAppender"/>
        </logger>
    
        <!-- 默认spring boot导入hibernate很多的依赖包,启动的时候,会有hibernate相关的内容,直接去除 -->
        <logger name="org.hibernate.validator" additivity="false">
            <level value="ERROR"/>
            <appender-ref ref="STDOUT"/>
            <appender-ref ref="errorAppender"/>
        </logger>
        <root level="DEBUG">
             <appender-ref ref="STDOUT"/>  
             <appender-ref ref="infoAppender"/>
             <appender-ref ref="debugAppender"/>
              <appender-ref ref="warnAppender"/>
             <appender-ref ref="errorAppender"/>
        </root> 
    </configuration>  
    
  3. 编写application.properties

    1. 配置日志输出的位置,注意在logback-spring.xml里用LOG_PATH才能获取到值

      这里可以是相对路径

      logging.path=d:/logs/springBoot
      
    2. 配置指向日志配置文件的位置

      logging.config=classpath:logback-spring.xml
      
    3. 配置控制台打印日志格式设置,注意在logback-spring.xml里用CONSOLE_LOG_PATTERN才能获取

      logging.pattern.console=[%d{yyyy-MM-dd HH:mm:ss}] -- [%-5p]: [%c] -- %m%n
      
    4. 配置文件打印日志格式设置,注意在logback-spring.xml里用FILE_LOG_PATTERN才能获取到

      logging.pattern.file=[%d{yyyy-MM-dd HH:mm:ss}] -- [%-5p]: [%c] -- %m%n
      
日志打印控制

日志的打印控制,比如有一些打印日志我不想要,就可以通过配置logback-spring.xml文件,添加一个logger标签即可。比如如下:

[2017-06-05 19:14:23] -- [INFO ]: [org.I0Itec.zkclient.ZkClient] -- zookeeper state changed (SyncConnected)
[2017-06-05 19:14:23] -- [DEBUG]: [org.I0Itec.zkclient.ZkClient] -- Leaving process event
[2017-06-05 19:14:23] -- [DEBUG]: [org.I0Itec.zkclient.ZkClient] -- State is SyncConnected123

以上三条日志我现在不需要让它打印了,那就需要额外配置了,因为它们来自于org.I0Itec.zkclient.ZkClient 类,并且打印的内容分别是DEBUG和INFO级别,日志打印只要将该类的日志打印级别调高(日志从低到高为 TRACE、DEBUG、INFO、WARN、ERROR),因此只要将该类的日志级别配置为WARN以上,就不会打印上边的内容了。配置方法如下:

 <logger name="org.I0Itec.zkclient.ZkClient" additivity="false">
        <level value="ERROR" />
        <appender-ref ref="STDOUT" />
        <appender-ref ref="errorAppender" />
</logger>12345

其中:

  • name表示日志的打印位置,从上边可以看出是来自该类。

  • additivity设置为false表示该日志打印设置(控制台打印还是文件打印等具体设置)不会向根root标签传递,也就是说该logger里怎么设置的那就会怎么打印,跟root无关。

  • level value=’error’表示将该类日志级别设置为error级才会打印。

  • 最后两行表示error级时会打印控制台和error文件同时打印日志。

注意
  1. logback和log4j最好不要放在一起,会冲突,最主要的jar包是slf4j-log4j12.jarlogback-classic.jar 这两个jar包的设计简直是反人类,有一个类,这两个都会有,只要这两个jar包同时引入,就会看到启动spring boot会有一串冲突的红字,因此,只要用logback日志,就需要特别注意除了排除log4j主要jar包,别忘了排除slf4j-log4j12.jar ,具体的排除方法,可以选择maven项目的pom.xml,在右边找到Dependency Hierarchy,然后找到要排除的jar包,右击选择Exclude Maven Artifact,然后保存即可。
  2. logback貌似没有log4j常用,很多的第三方jar包都使用的log4j,比如aliba的dubbo和zookeeper都是默认的log4j,所以你在引入第三方的jar时,又使用的是logback,就特别注意是不是它们默认使用的log4j,如果是的话,有slf4j-log4j12.jar 就排除即可。

配置示例2

配置打印日志
  1. 引入依赖

  2. 配置logback-spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="true">
     
        <!-- appender是configuration的子节点,是负责写日志的组件。 -->
        <!-- ConsoleAppender:把日志输出到控制台 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <!-- 默认情况下,每个日志事件都会立即刷新到基础输出流。 这种默认方法更安全,因为如果应用程序在没有正确关闭appender的情况下退出,则日志事件不会丢失。
             但是,为了显着增加日志记录吞吐量,您可能希望将immediateFlush属性设置为false -->
            <!--<immediateFlush>true</immediateFlush>-->
            <encoder>
                <!-- %37():如果字符没有37个字符长度,则左侧用空格补齐 -->
                <!-- %-37():如果字符没有37个字符长度,则右侧用空格补齐 -->
                <!-- %15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符 -->
                <!-- %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符 -->
                <!-- %msg:日志打印详情 -->
                <!-- %n:换行符 -->
                <!-- %highlight():转换说明符以粗体红色显示其级别为ERROR的事件,红色为WARN,BLUE为INFO,以及其他级别的默认颜色。 -->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) --- [%15.15(%thread)] %cyan(%-40.40(%logger{40})) : %msg%n</pattern>
                <!-- 控制台也要使用UTF-8,不要使用GBK,否则会中文乱码 -->
                <charset>UTF-8</charset>
            </encoder>
        </appender>
     
        <!-- info 日志-->
        <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
        <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_info.log -->
        <!--             2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
        <appender name="info_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日志文件路径和名称-->
            <File>logs/project_info.log</File>
            <!--是否追加到文件末尾,默认为true-->
            <append>true</append>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>DENY</onMatch><!-- 如果命中ERROR就禁止这条日志 -->
                <onMismatch>ACCEPT</onMismatch><!-- 如果没有命中就使用这条规则 -->
            </filter>
            <!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
             RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
            作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!-- 日志文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
                <!-- 文件名:logs/project_info.2017-12-05.0.log -->
                <!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
                <fileNamePattern>logs/project_info.%d.%i.log</fileNamePattern>
                <!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
                如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
                <maxHistory>30</maxHistory>
                <!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
                <totalSizeCap>20GB</totalSizeCap>
                <!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
                <maxFileSize>10MB</maxFileSize>
            </rollingPolicy>
            <!--编码器-->
            <encoder>
                <!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
                <!-- 记录日志的编码:此处设置字符集 - -->
                <charset>UTF-8</charset>
            </encoder>
        </appender>
     
        <!-- error 日志-->
        <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
        <!-- 以下的大概意思是:1.先按日期存日志,日期变了,将前一天的日志文件名重命名为XXX%日期%索引,新的日志仍然是project_error.log -->
        <!--             2.如果日期没有发生变化,但是当前日志的文件大小超过10MB时,对当前日志进行分割 重命名-->
        <appender name="error_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <!--日志文件路径和名称-->
            <File>logs/project_error.log</File>
            <!--是否追加到文件末尾,默认为true-->
            <append>true</append>
            <!-- ThresholdFilter过滤低于指定阈值的事件。 对于等于或高于阈值的事件,ThresholdFilter将在调用其decision()方法时响应NEUTRAL。 但是,将拒绝级别低于阈值的事件 -->
            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
                <level>ERROR</level><!-- 低于ERROR级别的日志(debug,info)将被拒绝,等于或者高于ERROR的级别将相应NEUTRAL -->
            </filter>
            <!--有两个与RollingFileAppender交互的重要子组件。 第一个RollingFileAppender子组件,即RollingPolicy:负责执行翻转所需的操作。
            RollingFileAppender的第二个子组件,即TriggeringPolicy:将确定是否以及何时发生翻转。 因此,RollingPolicy负责什么和TriggeringPolicy负责什么时候.
           作为任何用途,RollingFileAppender必须同时设置RollingPolicy和TriggeringPolicy,但是,如果其RollingPolicy也实现了TriggeringPolicy接口,则只需要显式指定前者。-->
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
                <!-- 文件名:logs/project_error.2017-12-05.0.log -->
                <!-- 注意:SizeAndTimeBasedRollingPolicy中 %i和%d令牌都是强制性的,必须存在,要不会报错 -->
                <fileNamePattern>logs/project_error.%d.%i.log</fileNamePattern>
                <!-- 每产生一个日志文件,该日志文件的保存期限为30天, ps:maxHistory的单位是根据fileNamePattern中的翻转策略自动推算出来的,例如上面选用了yyyy-MM-dd,则单位为天
                如果上面选用了yyyy-MM,则单位为月,另外上面的单位默认为yyyy-MM-dd-->
                <maxHistory>30</maxHistory>
                <!-- 每个日志文件到10mb的时候开始切分,最多保留30天,但最大到20GB,哪怕没到30天也要删除多余的日志 -->
                <totalSizeCap>20GB</totalSizeCap>
                <!-- maxFileSize:这是活动文件的大小,默认值是10MB,测试时可改成5KB看效果 -->
                <maxFileSize>10MB</maxFileSize>
            </rollingPolicy>
            <!--编码器-->
            <encoder>
                <!-- pattern节点,用来设置日志的输入格式 ps:日志文件中没有设置颜色,否则颜色部分会有ESC[0:39em等乱码-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level --- [%15.15(%thread)] %-40.40(%logger{40}) : %msg%n</pattern>
                <!-- 记录日志的编码:此处设置字符集 - -->
                <charset>UTF-8</charset>
            </encoder>
        </appender>
     
        <!--给定记录器的每个启用的日志记录请求都将转发到该记录器中的所有appender以及层次结构中较高的appender(不用在意level值)。
        换句话说,appender是从记录器层次结构中附加地继承的。
        例如,如果将控制台appender添加到根记录器,则所有启用的日志记录请求将至少在控制台上打印。
        如果另外将文件追加器添加到记录器(例如L),则对L和L'子项启用的记录请求将打印在文件和控制台上。
        通过将记录器的additivity标志设置为false,可以覆盖此默认行为,以便不再添加appender累积-->
        <!-- configuration中最多允许一个root,别的logger如果没有设置级别则从父级别root继承 -->
        <root level="INFO">
            <appender-ref ref="STDOUT" />
        </root>
     
        <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
        <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
        <logger name="com.sailing.springbootmybatis" level="INFO">
            <appender-ref ref="info_log" />
            <appender-ref ref="error_log" />
        </logger>
     
        <!-- 利用logback输入mybatis的sql日志,
        注意:如果不加 additivity="false" 则此logger会将输出转发到自身以及祖先的logger中,就会出现日志文件中sql重复打印-->
        <logger name="com.sailing.springbootmybatis.mapper" level="DEBUG" additivity="false">
            <appender-ref ref="info_log" />
            <appender-ref ref="error_log" />
        </logger>
     
        <!-- additivity=false代表禁止默认累计的行为,即com.atomikos中的日志只会记录到日志文件中,不会输出层次级别更高的任何appender-->
        <logger name="com.atomikos" level="INFO" additivity="false">
            <appender-ref ref="info_log" />
            <appender-ref ref="error_log" />
        </logger>
     
    </configuration>
    
  3. 编写application.properties

    注:logging.file和logging.path二者不能同时使用,如若同时使用,则只有logging.file生效
    logging.file=文件名
    logging.path=日志文件路径
     
    logging.level.包名=指定包下的日志级别
    logging.pattern.console=日志打印规则
    
    • logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log
    • logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log
  4. logback-spring.xml配置文件里面的logger节点name属性改成自己的包路径

配置示例3

  1. 引入依赖

  2. 配置application.yml

    #配置日志级别
    logging:
      level:
        root: info #默认根级别为info
        com.lzj.mapper: debug #输出指定包的日志级别
        com.lzj.service: debug
      file:
        name: run.log #指定生成日志文件名称
        path: ./ #将日志文件生成当前目录(当前项目的目录中)
    
  3. 声明日志对象使用,输出调试

    定义对象方式

    private static final Logger log=LoggerFactory.getLogger(UserServiceImpl.class)
    

    注解方式

    在需要使用日志输出调试语句的层中,可以用@Slf4j注解来使用log.debug控制输出语句,避免使用System.out来输出调试

    @Service
    @Transactional
    @Slf4j
    public class UserServiceImpl implements UserService{
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public User queryUserById(Integer id) {
            log.debug("id+{}",id);
            return userMapper.queryUserById(id);
        }
    }
    
  4. 测试

    @SpringBootTest
    public class UserServiceTest {
        @Autowired
        private UserService userService;
    
        @Test
        public void test1(){
            userService.queryUserById(1);
        }
    }
    

    image-20220702143929012

AOP切面

使用注解的方式

相关注解介绍

  • @Configuration:代表这是一个spring配置类,使用在类上

  • @Aspect:代表这是一个切面类,使用在类上

  • @Before:代表这个方法是一个前置附加操作

  • @After:代表这个方法是一个后置附加操作

    **注意:**使用@Before和@After注解声明方法上加入一个参数 定义一个参数 JoinPoint 连接点

  • @Around:代表这个方法是一个环绕附加操作

    **注意:**在@Around注解声明方法上加上ProceedingJoinPoint参数,处理中的连接点

    • value属性:用来书写切入点表达式

**注意:**如果使用@Around,则方法的返回值必须为Object,方法的参数中必须有ProceedingJoinPoint。在其前置环绕完成后需要运行proceedingJoinPoint.proceed()放行才可以进入后置环绕。

切入表达式介绍

最优的是基于注解的切入点表达式

  • execution 方法级别切入点表达式 运行效率低

    execution( * com.lzj.service.*.*(..))

  • within 类级别切入点表达式 控制越粗,效率越高

    within(com.lzj.service.*)

  • @annotation 基于注解的切入点表达式(需要在切点的方法上加上注解)

    @annotation(com.lzj.annotations.xxx)

如果使用基于注解的切入点表达式来进行切入则需要做如下操作

  1. 创建annotations包编写一个注解类(自定义注解)

    //指定运行时生效
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MyAdvice {
    
    }
    
  2. 编写切面方法

    这里以环绕方法示例

    @annotation(com.lzj.annotations.MyAdvice))

    @Around("@annotation(com.lzj.annotations.MyAdvice))")
    //Object返回值作用:用来将业务方法返回结果返回给调用者
    //如果没有返回,则会接收不到返回值
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("======进入环绕的前置操作======");
        System.out.println("当前执行类:"+proceedingJoinPoint.getTarget());
        System.out.println("当前执行方法:"+proceedingJoinPoint.getSignature().getName());
        //放行目标方法执行
        Object proceed = proceedingJoinPoint.proceed();
        System.out.println("======进入环绕的后置操作======");
        return proceed;
    }
    
  3. 使用该切面

    在需要添加切面的切点上加上@MyAdvice注解即可

    @Override
    @MyAdvice
    public User queryUserById(Integer id) {
        log.debug("id+{}",id);
        return userMapper.queryUserById(id);
    }
    

实战测试

包含前置、环绕、注解切入表达实践

  1. 引入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
  2. 新建config包,新建MyAspectConfig类

    这里的切入点是com.lzj.service包

    @Configuration//代表这是一个spring配置类
    @Aspect//代表这是一个切面类
    public class MyAspectConfig {
        /**
         * @Before:代表这个方法是一个前置附加操作
         * @After:代表这个方法是一个后置附加操作
         *      注意:使用@Before和@After注解声明方法上加入一个参数 定义一个参数 JoinPoint 连接点
         * @Around:代表这个方法是一个环绕附加操作
         *      注意:在@Around注解声明方法上加上ProceedingJoinPoint参数,处理中的连接点
         *  value属性:用来书写切入点表达式
         */
    
        /**
         * 切入点表达式:
         *  1.execution 方法级别切入点表达式 运行效率低
         *      execution( * com.lzj.service.*.*(..))
         *  2.within  类级别切入点表达式 控制越粗,效率越高
         *      within(com.lzj.service.*)
         *  3.@annotation   基于注解的切入点表达式
         *      @annotation(com.lzj.annotations.xxx)
         *
         */
    
        @Before("execution( * com.lzj.service.*.*(..))")
        public void before(JoinPoint joinPoint){
            System.out.println("当前执行目标类:"+joinPoint.getTarget());
            System.out.println("当前执行目标类中方法:"+joinPoint.getSignature().getName());
        }
    
    //    @Around("execution( * com.lzj.service.*.*(..))")
    //    //Object返回值作用:用来将业务方法返回结果返回给调用者
    //    //如果没有返回,则会接收不到返回值
    //    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    //        System.out.println("======进入环绕的前置操作======");
    //        System.out.println("当前执行类:"+proceedingJoinPoint.getTarget());
    //        System.out.println("当前执行方法:"+proceedingJoinPoint.getSignature().getName());
    //        //放行目标方法执行
    //        Object proceed = proceedingJoinPoint.proceed();
    //        System.out.println("======进入环绕的后置操作======");
    //        return proceed;
    //    }
       
        @Around("@annotation(com.lzj.annotations.MyAdvice))")
        //Object返回值作用:用来将业务方法返回结果返回给调用者
        //如果没有返回,则会接收不到返回值
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("======进入环绕的前置操作======");
            System.out.println("当前执行类:"+proceedingJoinPoint.getTarget());
            System.out.println("当前执行方法:"+proceedingJoinPoint.getSignature().getName());
            //放行目标方法执行
            Object proceed = proceedingJoinPoint.proceed();
            System.out.println("======进入环绕的后置操作======");
            return proceed;
        }
    
    
    }
    
  3. 测试访问

文件上传下载

文件上传

使用Multipartfile上传

前提:配置好虚拟路径和配置拦截过滤

注意:需要配置文件的上传大小

spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=50MB
# 解决url中中文路径的问题
spring.mvc.pathmatch.matching-strategy=ant_path_matcher

前端表单form标签需要加上

enctype="multipart/form-data"

后端

/**
 * 添加歌曲
 */
@RequestMapping(value = "/add",method = RequestMethod.POST)
public JsonResult<String> addSong(HttpServletRequest request, @RequestParam("file")MultipartFile mpFile){
    //获取前端传来的参数
    String singerId = request.getParameter("singerId").trim();  //所属歌手id
    String name = request.getParameter("name").trim();          //歌名
    String introduction = request.getParameter("introduction").trim();          //简介
    String pic = "/img/songPic/tubiao.jpg";                     //默认图片
    String lyric = request.getParameter("lyric").trim();     //歌词
    //上传歌曲文件
    if(mpFile.isEmpty()){
        throw new FileEmptyExcepetion("歌曲为空,上传失败");
    }
    //文件名=当前时间到毫秒+原来的文件名
    String fileName = System.currentTimeMillis()+mpFile.getOriginalFilename();
    //文件路径,Consts.SONGADDRESS为定义的常量
    String filePath = Consts.SONGADDRESS;
    //如果文件路径不存在,新增该路径
    File file1 = new File(filePath);
    if(!file1.exists()){
        file1.mkdir();
    }
    //实际的文件地址
    File dest = new File(filePath+System.getProperty("file.separator")+fileName);
    //存储到数据库里的相对文件地址
    String storeUrlPath = "/song/"+fileName;
    try {
        mpFile.transferTo(dest);
    }catch (IOException e) {
        throw new FileUploadIOException("文件读写异常");
    }catch (FileStateException e){
        throw new FileStateException("文件状态异常");
    }

    Song song = new Song();
    song.setSingerId(Integer.parseInt(singerId));
    song.setName(name);
    song.setIntroduction(introduction);
    song.setPic(pic);
    song.setLyric(lyric);
    song.setUrl(storeUrlPath);//歌曲路径
    boolean flag = songService.insert(song);
    if(!flag){
        throw new UpdateException("更新数据时产生异常");
    }
    return new JsonResult<>(OK,storeUrlPath);

}

文件下载

@Controller
@Slf4j
public class FileController {

    //下载文件所在的真实路径,从配置文件中获取
    @Value("${file.download.dir}")
    private String realPath;

    /**
     * 文件下载
     * @param fileName 前端传来的文件名
     */
    @RequestMapping("/download")
    public void download(String fileName, HttpServletResponse response) throws IOException {
        log.debug("当前下载文件名为:{}",fileName);
        //1.去指定目录中读取文件
        File file = new File(realPath, fileName);
        //2.将文件读取为文件输入流
        FileInputStream fileInputStream = new FileInputStream(file);
        //2.1.获取响应流之前,要设置以附件形式下载,否则默认在游览器中打开文件 attachment附件
        //下载下来的文件名为fileName,URLEncoder.encode防止中文文件名乱码
        response.setHeader("content-disposition","attachment;filename="+ URLEncoder.encode(fileName,"UTF-8"));
        //3.获取响应输出流
        ServletOutputStream responseOutputStream = response.getOutputStream();
//        //4.输入流复制给输出流
//        int len=0;
//        byte[] b=new byte[1024];
//        while (true){
//            len= fileInputStream.read(b);
//            if (len==-1) break;
//            responseOutputStream.write(b,0,len);
//        }
//        //5.释放资源
//        fileInputStream.close();
        
        //使用spring自带的工具类,复制输入流给输出流
        FileCopyUtils.copy(fileInputStream,responseOutputStream);
    }
}

拦截器interceptor

类似于filter过滤器

**filter过滤器:**过滤可以拦截javaweb中请求,放行、中断。强大,可以拦截一切资源

**拦截器:**只能拦截controller相关的请求和放行

执行顺序:

  • preHandler:预先处理方法: 最先执行方法 返回值布尔类型 true 放行请求 false 中断
  • controller:控制器中的方法
  • postHandler:过程中处理:controller返回之后回到postHandler这个方法执行,执行完成这个方法开始响应游览器
  • afterCompletion:最后完成:当响应结束之后会执行拦截器中这个方法内容

拦截器实现

  1. 创建interceptors包,创建MyInterceptor类

    一般我们使用preHandle即可

    /**
     * <p>
     *自定义拦截器
     * </p>
     *
     * @author:lzj
     * @date:2022/7/3
     */
    @Slf4j
    @Component
    public class MyInterceptor implements HandlerInterceptor {
    
        /**
         * 最先执行的
         * @param request
         * @param response
         * @param handler 当前请求请求的控制器方法对象 DemoComtroller
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.debug("=============1=============");
    //        response.sendRedirect("/403");//重定向到403页面
            return true;//放行 false中断
        }
    
        /**
         * controller执行完之后执行的,执行完成这个方法开始响应游览器
         * @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.debug("=========2==========");
        }
    
        /**
         * 当响应结束之后会执行拦截器中这个方法内容
         * @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.debug("=========3==========");
        }
    }
    
  2. 创建config包,创建MvcConfig类配置拦截器

    /**
     * <p>
     *拦截器配置类
     * </p>
     *
     * @author:lzj
     * @date:2022/7/3
     */
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
        @Autowired
        private MyInterceptor myInterceptor;
    
        /**
         * 配置拦截器相关的方法
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(myInterceptor)//指定拦截器
                .addPathPatterns("/**")//拦截所有
                .excludePathPatterns("/login");//放行这些路径
            //            .order(2);//指定拦截器顺序,默认按照自然顺序排
        }
    }
    

配置多个拦截器

拦截器1:

​ preHandler:1

​ postHandler:2

​ afterCompletion:3

拦截器2:

​ preHandler:4

​ postHandler:5

​ afterCompletion:6

在执行时会根据的方式来执行,先进后出

**则执行顺序为:**1->4->5->2->6->3

image-20220703232544291

配置拦截器优先级

由于拦截器运行默认按照我们写在配置类中的顺序来,这样会导致我们在拦截器多了后调其运行顺序很困难

实现方式:

使用.order()指定拦截器顺序,若指定拦截器顺序相同时,按照配置顺序执行

    /**
     * 配置拦截器相关的方法
     * 如果配置多个拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor)//指定拦截器
            .addPathPatterns("/**")//拦截所有
            .excludePathPatterns("/login")//放行这些路径
            .order(2);//指定拦截器顺序,默认按照自然顺序排	数字相同时按照配置顺序
    }

部署方式(war和jar)

springboot默认使用jar包方式

war包部署

  1. 在项目pom中,调整打包方式为war包

    <!--    指定war包部署-->
        <packaging>war</packaging>
    

    image-20220703235300881

  2. 去除项目内嵌的tomcat依赖

    第一个依赖可能没有,没有就不用管,第二个依赖一定要修改(如果没有该依赖就自己加上去,然后加上<scope>provided</scope>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <!--            provided当前idea环境可用,打包不参与-->
        <scope>provided</scope>
        </dependency>
    
        <!--        去掉内嵌的tomcat依赖-->
        <dependency>
       <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <!--            provided当前idea环境可用,打包不参与-->
        <scope>provided</scope>
        </dependency>
    
  3. 在pom.xml中配置入口类

        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
    <!--                配置springboot入口类-->
                    <configuration>
                        <fork>true</fork>
    <!--                    增加jvm参数-->
                        <jvmArguments>-Dfile.encoding=UTF-8</jvmArguments>
    <!--                    指定入口类-->
                        <mainClass>com.lzj.SpringBoot_1_Application</mainClass>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
  4. 修改入口类

    继承SpringBootServletInitializer,重写configure方法

    @SpringBootApplication
    //SpringBootServletInitializer:不再使用内嵌容器使用,使用外部tomcat容器启动
    public class SpringBoot_1_Application extends SpringBootServletInitializer {
        public static void main(String[] args) {     SpringApplication.run(SpringBoot_1_Application.class,args);
        }
    
        /**
         * 配置入口类是谁
         * @param builder
         * @return
         */
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(SpringBoot_1_Application.class);
        }
    }
    
  5. application配置文件说明

    一但使用war包部署

    1. 配置的port、context-path失效
    2. 访问时使用打成war包的名字和外部的tomcat端口号进行访问项目
  6. 打war包

    1. 先clean

    2. 在package

      image-20220704001212665

    3. 再部署到外部tomcat

      如何部署到外部tomcat我就不多说了

  7. 测试访问

jar包部署

  1. pom.xml中,调整打包方式为jar包

    <!--    指定jar包部署-->
        <packaging>jar</packaging>
    
  2. 打jar包

    image-20220704001212665

  3. 运行jar包

    java -jar ***.jar nohup &

注意事项:

  1. springboot项目在使用jsp模板时,jar包部署默认无法找到jsp页面

    如何解决?

    • 插件版本必须为1.4.2
    • 指定jsp文件打包位置
      <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
    <!--                打包jsp模板时,必须使用1.4.2-->
                    <version>1.4.2.RELEASE</version>
                </plugin>
            </plugins>
    <!--        执行jsp文件打包位置-->
            <resources>
    <!--            打包时将jsp文件拷贝到META-INF目录下-->
                <resource>
    <!--                指定resources插件处理哪个目录下的资源文件-->
                    <directory>src/main/webapp</directory>
    <!--                指定必须要放在此目录下才能被访问到-->
                    <targetPath>META-INF/resources</targetPath>
                    <includes>
                        <include>**/**</include>
                    </includes>
                </resource>
                <resource>
                    <directory>src/main/resources</directory>
                    <includes>
                        <include>**/**</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    

Restful

Restful风格是什么我就不介绍了

需要通过ajax来设置请求格式:

资源操作:使用POST(添加)、DELETE(删除)、PUT(修改)、GET(查询),使用不同请求方法对资源进行操作

  • 【GET】/users 查询用户信息列表
  • 【GET】/users/1001 查看某个用户信息
  • 【POST】/users 新建用户信息
  • 【PUT】/users/1001 更新用户信息(全部字段)
  • 【PATCH】/users/1001 更新用户信息(部分字段)
  • 【DELETE】/users/1001 删除用户信息

设计风格原则

  1. 使用名词而不是动词
  2. Get方法和查询参数不应该涉及状态改变
  3. 使用复数名词
  4. 使用子资源表达关系
  5. 使用Http头声明序列化格式
  6. 为集合提供过滤 排序和分页等功能
  7. 版本化API v1 v2 v3
  8. 使用http状态码处理错误

结合使用springmvc中自带工具类

  • ResponseEntity:springmvc封装一个专用于restful响应类,这个类在响应时可以提供响应的状态码,同时还能自定义响应头信息
  • HttpStatus:springmvc封装一个枚举类型类,这个类中都是网络中状态码

使用示例:

@GetMapping("/{id}")
@ResponseBody
public ResponseEntity<User> user(@PathVariable("id") integer id){
    User user=new User("id","name","salary","birthday");
    return new ResponseEntity<>(user,HttpStatus.OK) 
}

异常处理

传统方式异常处理

异常捕捉器,捕捉controller层的异常

/**
 * <p>
 *传统方式异常处理
 * </p>
 *
 * @author:lzj
 * @date:2022/7/5
 */
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {

    /**
     *当控制器中任意一个方法出现异常时,如果该控制器的方法没有自己的异常处理,则会进入当前方法
     *注意:在异常处理这个方法中,完成自定义异常处理
     * @param request   当前请求对象
     * @param response  当前请求对象响应对象
     * @param handler   当前出现错误的方法对象
     * @param ex        出现异常的异常对象
     * @return          模型和视图
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("这是全局异常处理....");
        System.out.println("当前异常为:"+ex);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("500");//返回到500界面
        return modelAndView;
    }
}

自定义异常,异常捕捉器中根据异常类型来跳转视图

/**
 * <p>
 *业务层异常的基类
 * </p>
 *
 * @autor:lzj
 * @date:2022/3/16
 */
public class ServiceException extends RuntimeException{
    public ServiceException() {
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

前后端分离异常处理

方法1

配套的基类控制器异常捕捉器,每个controller基础该类即可

public class BaseController {
    //操作成功的状态码
    public static final int OK = 200;

    //请求处理方法,这个方法的返回值就是需要传递给前端的数据
    //自动将异常对象传递给此方法的参数列表上
    //当前项目中产生了异常,被统一拦截到此方法中,这个方法此时就充当的是请求处理方法,方法的返回值直接给前端
    @ExceptionHandler(ServiceException.class)//统一处理抛出的异常
    public JsonResult<Void> handleException(Throwable e){
        JsonResult<Void> result = new JsonResult<>(e);
        if(e instanceof UsernameDuplicatedException){
            result.setState(4000);
            result.setMessage("用户名已经被占用");
        }else if(e instanceof InsertException){
            result.setState(5000);
            result.setMessage("注册时产生未知的异常");
        }
        return result;
    }
}
方法二
/**
 * <p>
 *分离开发异常处理
 * </p>
 *
 * @author:lzj
 * @date:2022/7/5
 */
@ControllerAdvice
public class SeparateExceptionResolver {

    //用在方法上,用来处理指定异常
    //value:指定处理异常类型
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseEntity<String> exceptionHandler(Exception ex){
        System.out.println("进入自定义异常");
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);//返回500异常
    }

}

配套的异常类编写,service层中其它异常类基础该类即可,如果有在其它层的异常,在那个层的包下新建ex包编写即可

package com.lzj.service.ex;
/**
 * <p>
 *业务层异常的基类
 * </p>
 *
 * @autor:lzj
 * @date:2022/3/16
 */
public class ServiceException extends RuntimeException{
    public ServiceException() {
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    public ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

CORS跨域

跨域资源共享:允许游览器跨域从当前源服务器通过ajax访问另外一个源服务地址

资源引入不受同源限制

分离式项目下使用ajax可能会出现同源限制,出现跨域问题Access-Control-Allow-Origin

解决跨域问题

局部解决

@CrossOrigin

controller层中加上该注解,则该controller中所有请求允许被跨域访问

全局解决

新建config包,config包下新建CorsConfig类

/**
 * <p>
 *解决跨域问题
 * </p>
 *
 * @autor:lzj
 * @date:2022/4/14
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("*")
                .allowCredentials(true);
        		.maxAge(3600);
    }
}

Jasypt加密

用于加密我们application配置文件中的账号密码等

Jasypt也即Java Simplifed Encryption是Sourceforge.net上的-个开源项目。在当地时间11月23号的通告中,Jasypt 1.4的新特征包括:加密属性文件(encryptable properties files) . SpringFramework集成、加密Hibemnate数据源配置、 新的命令行工具、URL加密的Apache wicket集成以及升级文档。

根据Jasypt文档,该技术可用于加密任务与应用程序,例如加密密码,敏感信息和数据通信、创建完整检查数据的sums.其他性能包括高安全性、基于标准的加密技术.可同时单向和双向加密的加密密码、文本,数字和二进制文件。Jasyp也可以与Acegj Security整合也即Spring Security. Jasypt亦拥有加密应用配置的集成功能,而且提供-个开放的API从而任何一个Java Cryptography Extension都可以
使用Jasypt.

Jasypt还符合RSA标注你的基于密码的加密,并提供了无配置加密工具以及新的、高可配置标准的加密工具

  1. 导入依赖

    <!--        jasypt-->
            <dependency>
                <groupId>com.github.ulisesbocchio</groupId>
                <artifactId>jasypt-spring-boot-starter</artifactId>
                <version>3.0.4</version>
            </dependency>
    
  2. 编写配置

    一般加密密钥不写在配置文件中,在我们启动jar包时自己手动输入

    nphup java -jar -Djasypt.encryptor.password=asiofhOHBFDKFfhkj xxx.jar

    或者

    我们在idea中启动项目时,在其tomcat的VM options中配置

    -Djasypt.encryptor.password=asiofhOHBFDKFfhkj

    image-20220705195523966

    jasypt:
      encryptor:
        algorithm: PBEWITHHMACSHA512ANDAES_256 #指定加密算法
        password: asiofhOHBFDKFfhkj #指定密钥
    
  3. 测试

    /**
     * <p>
     *
     * </p>
     *
     * @author:lzj
     * @date:2022/7/5
     */
    @SpringBootTest
    public class JasyptTests {
        @Autowired
        private StringEncryptor stringEncryptor;
    
        @Test
        public void testSecret(){
            for (int i = 0; i <10 ; i++) {
                //加密
                String secret = stringEncryptor.encrypt("root");
                System.out.println(secret);
            }
    //        //解密
    //        String decrypt = stringEncryptor.decrypt("4My//essuHJRxQtT+lmZYC3r/hXDg9VJV5gvmAdk9fzFHnFMPqv/tzn2BYxYxueG");
    //        System.out.println(decrypt);
        }
    }
    
  4. 加密使用方式

    在application.yml中对数据库连接账号和密码加密

    使用ENC(加密后的字符串),来替代原用户名和密码

    spring:
      datasource:
    #    username: root
    #    password: root
        username: ENC(4My//essuHJRxQtT+lmZYC3r/hXDg9VJV5gvmAdk9fzFHnFMPqv/tzn2BYxYxueG)
        password: ENC(4My//essuHJRxQtT+lmZYC3r/hXDg9VJV5gvmAdk9fzFHnFMPqv/tzn2BYxYxueG)
    

总结

本次复习SpringBoot的时间,距离上一次学习的时间相隔大概3个月左右,在这期间忘记了很多其相关知识,经过本次的复习,巩固了我对SpringBoot的知识,也让我学习到了许多。

事实证明,在学习的过程中,要观看不同老师的视频去学习知识,否则学习的知识面就会比较单一。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱学习的大雄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值