SpringBoot案例 Spring

2020-07-04 约 20247 字 阅读时长41 分钟

SpringBoot案例

集成

整合jsp

  • 声明SpringBoot内嵌tomcat对jsp支持依赖

    xml
    1<dependency>
    2    <groupId>org.apache.tomcat.embed</groupId>
    3    <artifactId>tomcat-embed-jasper</artifactId>
    4</dependency>
  • 在main目录下创建webapp,并在包设置设定该目录为web资源目录

  • SpringBoot默认支持view不是jsp,需要手动指定jsp文件编译后位置,SpringBoot规定位置为 META-INF/resources

    xml
    1<resource>
    2    <directory>src/main/webapp</directory>
    3    <targetPath>META-INF/resources</targetPath>
    4    <includes>
    5        <include>*.*</include>
    6    </includes>
    7</resource>
  • 在核心配置文件中配置视图解析器

    properties
    1spring.mvc.view.prefix=/
    2spring.mvc.view.suffix=.jsp

集成mybatis(逆向工程)

  • 声明相关依赖,mysql驱动依赖、mybatis整合SpringBoot的起步依赖

    xml
    1<dependency>
    2    <groupId>mysql</groupId>
    3    <artifactId>mysql-connector-java</artifactId>
    4</dependency>
    5<dependency>
    6    <groupId>org.mybatis.spring.boot</groupId>
    7    <artifactId>mybatis-spring-boot-starter</artifactId>
    8    <version>2.2.0</version>
    9</dependency>

使用mybatis提供的逆向工程生成实体类、映射文件、Dao接口

  • 根目录下创建generatorConfig.xml逆向工程配置文件

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<!DOCTYPE generatorConfiguration
     3        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
     4        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
     5
     6<generatorConfiguration>
     7<!--    mysql驱动位置 -->
     8    <classPathEntry location="G:\mysql-connector-java-8.0.25.jar" />
     9
    10    <context id="tables" targetRuntime="MyBatis3">
    11        <!-- 关闭注释生成 -->
    12        <commentGenerator>
    13            <property name="suppressAllComments" value="true" />
    14        </commentGenerator>
    15
    16        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
    17                        connectionURL="jdbc:mysql://192.168.10.129:3306/crm_manage?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"
    18                        userId="crm_manage"
    19                        password="JCRMp3LHkrSZ5y6c">
    20        </jdbcConnection>
    21
    22        <javaTypeResolver >
    23            <property name="forceBigDecimals" value="true" />
    24        </javaTypeResolver>
    25
    26        <javaModelGenerator targetPackage="com.lei.model" targetProject="src/main/java/">
    27            <property name="enableSubPackages" value="true" />
    28            <property name="trimStrings" value="true" />
    29        </javaModelGenerator>
    30
    31        <sqlMapGenerator targetPackage="com.lei.mapper"  targetProject="src/main/java/">
    32            <property name="enableSubPackages" value="true" />
    33        </sqlMapGenerator>
    34
    35        <javaClientGenerator type="XMLMAPPER" targetPackage="com.lei.mapper"  targetProject="src/main/java/">
    36            <property name="enableSubPackages" value="true" />
    37        </javaClientGenerator>
    38
    39        <table tableName="tbl_activity" domainObjectName="Activity"
    40               enableCountByExample="false"
    41               enableSelectByExample="false"
    42               enableDeleteByExample="false"
    43               enableUpdateByExample="false"
    44               selectByExampleQueryId="false"
    45        />
    46    </context>
    47
    48</generatorConfiguration>
  • pom.xml中配置逆向工程插件

    xml
     1<plugin>
     2    <groupId>org.mybatis.generator</groupId>
     3    <artifactId>mybatis-generator-maven-plugin</artifactId>
     4    <version>1.4.0</version>
     5    <configuration>
     6      <!--本地逆向工程配置文件位置-->  
     7        <configurationFile>generatorConfig.xml</configurationFile>
     8        <verbose>true</verbose>
     9        <overwrite>true</overwrite>
    10    </configuration>
    11</plugin>
  • 使用maven的逆向工程插件,生成对应的mapper、domain

mybatis和SpringBoot整合

  1. 在mapper接口上使用注解 @Mapper 注解,将其注入到spring容器

  2. 或者使用 @MapperScan(“com.lei.mapper”)注解,将其注解到主配置文件上,指定需要扫描的mapper包

  3. 在 SpringBoot 核心配置文件中配置 数据库链接信息

    properties
    1spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    2spring.datasource.url=jdbc:mysql://192.168.10.129:3306/crm_manage?useSSL=true&useUnicode=true&characterEncoding=utf8
    3spring.datasource.username=crm_manage
    4spring.datasource.password=JCRMp3LHkrSZ5y6c
  4. pom.xml配置资源文件用于编译mapper.xml文件

mapper.xml存放位置,两种

  • 和接口放在一起,需要在pom.xml中配置资源目录

  • 直接将mapper.xml和接口分开,将mapper.xml放在 resource目录下

    • 在资源目录下创建mapper文件夹,将mapper.xml文件放进去

    • 在SpringBoot核心配置文件中指定mapper所在位置

      properties
      1mybatis.mapper-locations=classpath:mapper/*.xml
    • 不需要在pom.xml中指定资源目录

集成redis

SpringBoot2.x之后,原来使用的jedis被替换成了 lettuce

区别:

  • jedis:采用直连,多个线程操作的话,是不安全的,如果要避免不安全,使用jedis pool连接池,像 BIO
  • lettuce:采用netty,实例可以多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据,像 NIO
  1. 添加依赖

    xml
    1<!-- SpringBoot集成redis起步依赖 -->
    2<dependency>
    3    <groupId>org.springframework.boot</groupId>
    4    <artifactId>spring-boot-starter-data-redis</artifactId>
    5</dependency>
  2. 在SpringBoot核心配置文件中添加redis配置

    properties
    1spring.redis.host=192.168.10.129
    2spring.redis.port=6379
    3spring.redis.password=123456
  3. redis起步依赖提供了一个操作redis的对象,注入操作redis的对象

    java
    1@Autowired
    2private RedisTemplate<Object,Object> redisTemplate;
  4. 事务操作的实现,通过SessionCallback/RedisCallback;默认情况下:redisTemplate每次操作都是采用新的连接执行,而redisTemplate.multi()是无效的(因为连接池连接默认自动提交),手动提交时会报错ERR EXEC without MULTI

    SessionCallback实现事务操作

    java
     1@Override
     2public void set(String key, String value) {
     3    try {
     4        Boolean execute = redisTemplate.execute(new SessionCallback<Boolean>() {
     5            @Override
     6            public Boolean execute(RedisOperations redisOperations) throws DataAccessException {
     7                redisOperations.multi();
     8                //设置缓存
     9                redisOperations.opsForValue().setIfAbsent(key, value);
    10                //设置超时时间
    11                redisOperations.expire(key, 120L, TimeUnit.SECONDS);
    12                List list = redisOperations.exec();
    13                return (Boolean) list.get(0);
    14            }
    15        });
    16    } finally {
    17        //清除缓存
    18        redisTemplate.delete(key);
    19    }
    20}

    RedisCallback实现事务操作

    java
     1public void set(String key, String value) {
     2    try {
     3        redisTemplate.execute(new RedisCallback<Boolean>() {
     4            @Override
     5            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
     6                redisConnection.multi();
     7                Expiration seconds = Expiration.seconds(60L);
     8                //如果不存在则设置值
     9                redisConnection.set(key.getBytes(),value.getBytes(),seconds, RedisStringCommands.SetOption.SET_IF_ABSENT);
    10                //设置超时时间
    11                redisConnection.expire(key.getBytes(),60L);
    12                List<Object> exec = redisConnection.exec();
    13                return null;
    14            }
    15        });
    16    }finally {
    17        //清除缓存
    18        redisTemplate.delete(key);
    19    }
    20
    21}

集成Dubbo分布式框架

dubbo简介

  • 接口工程:存放实体bean和业务接口
  • 服务提供者:业务接口的实现类并将服务暴露且注册到注册中心,调用数据数据持久层
    • 添加依赖:dubbo,注册中心,接口工程
    • 配置服务者核心配置文件
  • 服务消费者:处理浏览器客户端发送的请求,从注册中心调用服务提供者所提供的服务
    • 添加依赖:dubbo,注册中心,接口工程
    • 配置服务消费者核心配置文件

集成步骤

  1. 创建接口工程(maven项目)、创建服务提供者(SpringBoot项目)、创建服务消费者(SpringBoot项目)

  2. 服务提供者

    • 添加依赖

      xml
      1<!--dubbo集成SpringBoot起步依赖-->
      2dubbo-spring-boot-starter
      3<!--注册中心依赖-->
      4zkclient
      5<!--接口工程-->
    • 核心配置文件中配置

      properties
       1#内嵌tomcat端口号
       2#设置上下文根
       3#声明当前工程名字
       4spring.application.name=
       5
       6#设置dubbo配置,注意,没有提示
       7#声明当前工程是一个服务提供者工程
       8spring.dubbo.server=true
       9#指定注册中心
      10spring.dubbo.registry=zookeeper://ip:2181
  3. 服务消费者

    • 添加依赖

      xml
      1<!--dubbo集成SpringBoot起步依赖-->
      2dubbo-spring-boot-starter
      3<!--注册中心依赖-->
      4zkclient
      5<!--接口工程-->
    • 核心配置文件中配置

      properties
       1#内嵌tomcat端口号
       2#设置上下文根
       3#声明当前工程名字
       4spring.application.name=
       5
       6#设置dubbo配置,注意,没有提示
       7#声明当前工程是一个服务提供者工程
       8spring.dubbo.server=true
       9#指定注册中心
      10spring.dubbo.registry=zookeeper://ip:2181

集成logback日志

SpringBoot默认集成了logback日志框架(log4j 原意 log for java)

SpringBoot核心配置文件可以配置日志,当SpringBoot核心配置文件不够使用时,也可以引入自己扩展的日志配置文件 logback-spring.xml

SpringBoot核心配置文件与logback相关配置

yaml
1logging:
2  level:
3    #单独给mysql日志级别配置为 debug,其他的为 warn
4    com.lei.mapper: debug
5    root: info   #日志级别
6  file:
7    path: /logs    #指定日志输出的文件路径,/在windows下是直接定位到当前工作文件磁盘下的
8    name: log       #指定日志文件名
9  config: classpath:logback-spring.xml  	#指定自定义配置的logback位置

自定义扩展配置 logback-spring.xml

xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
 3<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
 4<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
 5<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
 6<configuration  scan="true" scanPeriod="10 seconds">
 7
 8    <!--输出到控制台的追加器-->
 9    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
10        <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
11        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
12            <level>DEBUG</level>
13        </filter>
14        <encoder>
15            <Pattern>%date [%-5p] [%thread] %-5level %logger{50} - %msg%n</Pattern>
16            <!-- 设置字符集 -->
17            <charset>UTF-8</charset>
18        </encoder>
19    </appender>
20
21    <!--输出到文件的追加器-->
22    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
23        <!-- 正在记录的日志文件的路径及文件名 -->
24        <file>D:/log_debug.log</file>
25        <!--日志文件输出格式-->
26        <encoder>
27            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%-5level] %logger{50} - %msg%n</pattern>
28            <charset>UTF-8</charset> <!-- 设置字符集 -->
29        </encoder>
30        <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
31        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
32            <!-- 日志归档 -->
33            <fileNamePattern>d:/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
34            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
35                <maxFileSize>100MB</maxFileSize>
36            </timeBasedFileNamingAndTriggeringPolicy>
37            <!--日志文件保留天数-->
38            <maxHistory>10</maxHistory>
39        </rollingPolicy>
40        <!-- 此日志文件只记录debug级别的 -->
41        <filter class="ch.qos.logback.classic.filter.LevelFilter">
42            <level>debug</level>
43            <onMatch>ACCEPT</onMatch>
44            <onMismatch>DENY</onMismatch>
45        </filter>
46    </appender>
47    <!--
48        <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。
49        name:用来指定受此logger约束的某一个包或者具体的某一个类。
50        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
51              还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
52              如果未设置此属性,那么当前logger将会继承上级的级别。
53        addtivity:是否向上级logger传递打印信息。默认是true。
54    -->
55    <!--
56        使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
57        第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
58        第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
59     -->
60    <logger name="com.lei.mapper" level="debug"/>
61    <!--
62        root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
63        level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
64        不能设置为INHERITED或者同义词NULL。默认是DEBUG
65        可以包含零个或多个元素,标识这个appender将会添加到这个logger。
66    -->
67
68    <root level="info">
69        <appender-ref ref="CONSOLE" />
70        <appender-ref ref="DEBUG_FILE" />
71    </root>
72
73</configuration>
  1. 日志级别从低到高:TRACE < DEBUG <WARN < ERROR < FATAL,设置了日志级别后,低于该级别的信息都不会输出
  2. scan:此属性为 true 时,配置文件如果发生改变,将会重新加载,默认为 true
    1. scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间间隔,默认为毫秒。当 scan 为 true 时,此属性生效。默认时间间隔为 1 分钟
  3. debug :当此属性设置为true时,控制台将打印出 logback 内部日志信息,实时查看。默认值 false,通常不打印

使用swagger和lombok

OAS规范,Linux基金会的一个项目,试图定义一种用来描述API格式或API定义语言,来规范RESTFUL服务开发过程

Swagger是目前最受欢迎的 OAS 规范开发工具框架;随着项目自动生成API文档(通过一些注解)

Swagger使用

  1. pom.xml 添加起步依赖

    xml
     1<!--3.0导入起步依赖-->
     2<dependency>
     3    <groupId>io.springfox</groupId>
     4    <artifactId>springfox-boot-starter</artifactId>
     5    <version>3.0.0</version>
     6</dependency>
     7<!--老版本2.x导入以下依赖-->
     8<dependency>
     9    <groupId>io.springfox</groupId>
    10    <artifactId>springfox-swagger2</artifactId>
    11    <version>2.9.2</version>
    12</dependency>
    13<dependency>
    14    <groupId>io.springfox</groupId>
    15    <artifactId>springfox-swagger-ui</artifactId>
    16    <version>2.9.2</version>
    17</dependency>
  2. 2.x的老版本swagger除了需要导入相关依赖,还要编写一个配置类

    java
     1@Configuration
     2@EnableSwagger2  //开启swagger2的支持
     3public class MyConfig {
     4    @Bean
     5    public Docket docket(){
     6        //链式编程,构造器模式,基本是固定的
     7        return new Docket(DocumentationType.SWAGGER_2)
     8                .apiInfo(apiInfo())
     9                .select()
    10                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))   //扫描有ApiOperation注解的方法
    11                .paths(PathSelectors.any())
    12                .build();
    13    }
    14    private ApiInfo apiInfo() {
    15        return new ApiInfoBuilder()
    16                .title("接口文档")
    17                .description("接口文档描述")
    18                .termsOfServiceUrl("https://www.lei.com")
    19                .contact(new Contact("lei","https://www.lei.com","lei@qq.com"))
    20                .license("采用api2.0开源许可证")
    21                .licenseUrl("https://www.lei.com/context.txt")
    22                .version("1.0.0")
    23                .build();
    24    }
    25}
  3. 编写控制类,并使用相关注解说明,以便生成接口文档

    java
     1@Api(tags = "SpringBoot使用swagger测试")
     2@RestController
     3public class UserController {
     4    @ApiOperation(value = "获取用户信息",notes = "根据id获取用户信息")
     5    @ApiImplicitParams({
     6            @ApiImplicitParam(paramType = "path",name = "id",value = "用户id",dataTypeClass = String.class)
     7    })
     8    @ApiResponses({
     9            @ApiResponse(code = 400,message = "缺少请求参数"),
    10            @ApiResponse(code = 401,message = "请求路径没有权限"),
    11            @ApiResponse(code = 403,message = "请求路径被隐藏不能访问"),
    12            @ApiResponse(code = 404,message = "请求路径错误"),
    13            @ApiResponse(code = 405,message = "请求方法不支持")
    14    })
    15    @RequestMapping(value = "/query/{id}",method = RequestMethod.GET)
    16    public User query(@PathVariable("id") String id){
    17        User user=new User();
    18        user.setId(id);
    19        user.setName("小王");
    20        return user;
    21    }
    22
    23    @ApiOperation(value = "删除用户",notes = "根据id和用户名删除")
    24    @ApiImplicitParams({
    25            @ApiImplicitParam(paramType = "path",dataTypeClass = String.class,name = "id",value = "用户id"),
    26            @ApiImplicitParam(paramType = "path",dataTypeClass = String.class,name = "name",value = "用户姓名"),
    27    })
    28    @ApiResponses({
    29            @ApiResponse(code = 400,message = "缺少请求参数"),
    30            @ApiResponse(code = 401,message = "请求路径没有权限"),
    31            @ApiResponse(code = 403,message = "请求路径被隐藏不能访问"),
    32            @ApiResponse(code = 404,message = "请求路径错误"),
    33            @ApiResponse(code = 405,message = "请求方法不支持")
    34    })
    35    @RequestMapping(value = "/del/{id}/{name}",method = RequestMethod.POST)
    36    public String del(@PathVariable("id") String id,@PathVariable("name") String name){
    37        return "删除成功";
    38    }
    39}
  4. 实体类相关注解

    java
    1@Data
    2@ApiModel(value = "User",description = "User实体类")
    3public class User {
    4    @ApiModelProperty(name = "id",value = "用户id")
    5    private String id;
    6    @ApiModelProperty(name = "name",value = "用户姓名")
    7    private String name;
    8}
  5. 浏览器访问

    1. 3.0访问地址:http://localhost:8080/swagger-ui/index.html#/
    2. 2.x访问地址:http://localhost:8080/swagger-ui.html
  6. SpringBoot核心配置文件关闭swagger-ui访问

    properties
    1springfox.documentation.swagger-ui.enabled=false

使用jasypt加密敏感信息

  1. 导入依赖

    xml
    1<dependency>
    2    <groupId>com.github.ulisesbocchio</groupId>
    3    <artifactId>jasypt-spring-boot-starter</artifactId>
    4    <version>3.0.3</version>
    5</dependency>
  2. SpringBoot核心配置文件配置加密解密的 key

    properties
    1jasypt.encryptor.password=sdjhauidasdw
  3. 注入 jasypt bean,并使用加密解密功能生成 密文

    java
     1package com.lei;
     2
     3@SpringBootApplication
     4public class Application implements CommandLineRunner {
     5
     6    @Autowired
     7    private StringEncryptor stringEncryptor;	//注入加密解密的bean
     8
     9    public static void main(String[] args) {
    10        SpringApplication springApplication=new SpringApplication(Application.class);
    11        ApplicationContext context=springApplication.run();
    12        StringEncryptor stringEncryptor=context.getBean(StringEncryptor.class);
    13        String username=stringEncryptor.encrypt("root");
    14        System.out.println("username加密结果 :" + username);
    15        System.out.println("username解密结果:"+stringEncryptor.decrypt(username));
    16        String password=stringEncryptor.encrypt("123456");
    17        System.out.println("password加密结果 :" + password);
    18        System.out.println("password解密结果:"+stringEncryptor.decrypt(password));
    19    }
    20}
    21//输出
    22/*
    23username加密结果 :KeJAwKEAUZ+HB8NucwwUygG99tWMT7EO/X9J3J50sqFjddx8Nb6jTTQUPEPji2It
    24username解密结果:root
    25password加密结果 :ov7Is+mJiVsT72YCO5t0lgQE4YpEnwYIWkh4dmx6nh+HSg/4W072tGQMzYS0HmE8
    26password解密结果:123456
    27*/
  4. 在SpringBoot核心配置文件配置;EVC(密文) 为默认识别密文的格式,可以通过配置文件修改

    properties
    1jasypt.encryptor.password=sdjhauidasdw
    2jasypt.encryptor.property.prefix=EVC(
    3jasypt.encryptor.property.suffix=)
    4
    5spring.datasource.username=EVC(KeJAwKEAUZ+HB8NucwwUygG99tWMT7EO/X9J3J50sqFjddx8Nb6jTTQUPEPji2It)
    6spring.datasource.password=EVC(ov7Is+mJiVsT72YCO5t0lgQE4YpEnwYIWkh4dmx6nh+HSg/4W072tGQMzYS0HmE8)

使用fastjson替换默认jackson

  1. 导入依赖

    xml
    1<dependency>
    2    <groupId>com.alibaba</groupId>
    3    <artifactId>fastjson</artifactId>
    4    <version>2.0.7</version>
    5    </dependency>
  2. 配置 HttpMessageConverters Bean

    java
     1@Bean
     2public HttpMessageConverters fastJsonHttpMessageConverters(){
     3    //1、先定义一个convert转换消息的对象
     4    FastJsonHttpMessageConverter fastConverter=new FastJsonHttpMessageConverter();
     5    //2、添加fastjson的配置信息,比如是否要格式化返回的json数据;
     6    FastJsonConfig fastJsonConfig=new FastJsonConfig();
     7    fastJsonConfig.setSerializerFeatures(
     8        SerializerFeature.WriteMapNullValue,
     9        SerializerFeature.WriteNullListAsEmpty,
    10        SerializerFeature.WriteNullNumberAsZero,
    11        //SerializerFeature.UseISO8601DateFormat,
    12        SerializerFeature.WriteClassName,
    13        SerializerFeature.WriteNullStringAsEmpty,
    14        SerializerFeature.WriteDateUseDateFormat  //使用日期格式化
    15    );
    16    //附加:处理中文乱码
    17    List<MediaType> fastMedisTypes = new ArrayList<MediaType>();
    18    fastMedisTypes.add(MediaType.APPLICATION_JSON);
    19    fastConverter.setSupportedMediaTypes(fastMedisTypes);
    20    //3、在convert中添加配置信息
    21    fastConverter.setFastJsonConfig(fastJsonConfig);
    22    HttpMessageConverter<?> converter=fastConverter;
    23    return new HttpMessageConverters(converter);
    24}
  3. 查看

    java
    1/*
    2* AbstractMessageConverterMethodProcessor类
    3* writeWithMessageConverters 写入响应消息的方法
    4*   genericConverter.write(body, targetType, selectedMediaType, outputMessage)
    5* 可以查看消息转换器,fastjson在列表第一个
    6*  */

集成Thymeleaf

  1. Thymeleaf 是一个流行的 java 模板引擎,以 html 为载体
  2. Thymeleaf模板引擎页面访问,必须经过中央调度器(不能直接访问,类似于页面放在 WEB-INF 目录下)
  3. http://www.thymeleaf.org

Thymeleaf起步

  1. 添加thymeleaf起步依赖

    xml
    1<dependency>
    2    <groupId>org.springframework.boot</groupId>
    3    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    4</dependency>
  2. 在 templates 目录下创建模板文件 hello.html,并引入命名空间

    html
     1<!DOCTYPE html>
     2<!--suppress ALL -->
     3<html lang="en"  xmlns:th="http://www.thymeleaf.org" >
     4<head>
     5    <meta charset="UTF-8">
     6    <title>Title</title>
     7</head>
     8<body>
     9<h1 th:text="${msg}"></h1>
    10</body>
    11</html>
  3. SpringBoot核心配置文件配置关闭缓存,方便开发,启动类需要设置动态更新资源

    properties
    1spring.thymeleaf.cache=false
    2spring.thymeleaf.prefix=classpath:templates/
    3spring.thymeleaf.suffix=.html

Thymeleaf表达式

  1. ${}:标准表达式,和el表达式一致,推荐使用
  2. *{}:选择变量表达式,必须使用 th:object 属性来绑定这个对象
  3. @{}:路径表达式,

Thymeleaf常见属性

  1. 标准表达式
    1. th:text
    2. th:method
    3. th:action
    4. th:id
    5. th:name
    6. th:onclick
  2. th:each="user,userStat:${userList}":遍历,依赖于一个html元素
  3. 条件判断
    1. th:if="${sex} eq 1:满足条件显示
    2. th:unless="${sex} eq 1":满足条件不显示,和 th:if 相反
    3. th:switch/th:case:选择,满足显示,否则不显示
  4. th:inline="":内敛表达式,有三个值
    1. text:然后在修饰标签里面,可以通过 [[${}]] 取值,而不需要 th:text="${}" 取值
    2. javascript:可以在 js 中直接取得后台返回过来值,通过 [[${}]]
  5. 字面量:字符串的意思,分为字符字面量、数字字面量、boolean字面量
  6. 字符串拼接
    1. th:text="|这里为需要拼接的${data}字符串|"
  7. 数学运算

后台发送过来的值时,才使用 Thymeleaf 属性

Thymeleaf对象

基本表达式对象

  1. #request
  2. #session

功能表达式对象

  1. #dates:java.util.Date 对象的实用方法
  2. #calendars:和dates类似,但是是java.util.Calendar 对象
  3. #numbers:格式化数字对象的实用方法
  4. #strings:字符串对象的实用方法
  5. #objects:对 objects 操作的实用方法
  6. #arrays:数组的实用方法
  7. #bools:对布尔值求值的实用方法
  8. #lists:list 的实用方法,
  9. #sets:set 的实用方法
  10. #maps:map 的实用方法
  11. #aggregates:对数组或集合创建聚合的实用方法

实例

html
 1<!DOCTYPE html>
 2<!--suppress ALL -->  <!--防止模板引擎取值时报红-->
 3<html lang="en"  xmlns:th="http://www.thymeleaf.org" >
 4<head>
 5    <meta charset="UTF-8">
 6    <title>Title</title>
 7</head>
 8<body>
 9<!--
10uList:{user1,user2,user3..}
11sex:1   1表示男,0表示女
12msg:hello
13-->
14<!--循环-->
15<div th:each="user,uStatus:${uList}">
16    index:<span th:text="${uStatus.index} "></span>
17    count:<span th:text="${uStatus.count} "></span>
18    current:<span th:text="${uStatus.current} "></span>
19    id:<span th:text="${user.id} "></span>
20    name:<span th:text="${user.name} "></span>
21    <br>
22</div>
23<!--判断-->
24<div>
25    <span th:if="${sex} eq 1"></span>
26    <span th:if="${sex} eq 0"></span>
27</div>
28<!--inline-->
29<script th:inline="javascript">
30    console.log([[${sex}]])
31</script>
32<!--字符串拼接-->
33<div th:text="|字符串拼接${msg}|"></div>
34<!--路径表达式-->
35<a th:href="@{/t2(msg=hello)}" th:text="跳转"/>
36<!--常用对象-->
37<div>
38    <span th:text="${#request.getRequestURL()}"></span><br>
39    <span th:text="${#session}"></span><br>
40    <span th:text="${#request.getScheme()}"></span><br>
41</div>
42<!--#lists对象使用-->
43<div>
44    uList长度:<span th:text="${#lists.size(uList)}"></span><br>
45</div>
46</body>
47</html>

配置

多数据源

核心配置,指定mapper接口扫描位置和sqlsession/sqlfactory的映射关系mapper 和 sqlsession/sqlfactory 的对应关系

多数据源注意事务问题,如果存在多个事务管理器,需明确指定使用哪一个@Transactional("db1TransactionManager")

  1. 配置文件

    properties
    1my.datasource.db1.url=jdbc:mysql://192.168.10.131:3306/db1?useSSL=true&useUnicode=true&characterEncoding=utf8
    2my.datasource.db1.username=root
    3my.datasource.db1.password=123456
    4
    5my.datasource.db2.url=jdbc:mysql://192.168.10.131:3306/db2?useSSL=true&useUnicode=true&characterEncoding=utf8
    6my.datasource.db2.username=root
    7my.datasource.db2.password=123456
  2. 配置数据源相关

    db1数据源

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3       xmlns:tx="http://www.springframework.org/schema/tx"
     4       xmlns:aop="http://www.springframework.org/schema/aop"
     5       xmlns="http://www.springframework.org/schema/beans"
     6       xsi:schemaLocation="http://www.springframework.org/schema/beans
     7    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
     8    http://www.springframework.org/schema/aop
     9     http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
    10
    11    <bean id="db1DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    12        <property name="url" value="${my.datasource.db1.url}"/>
    13        <property name="username" value="${my.datasource.db1.username}"/>
    14        <property name="password" value="${my.datasource.db1.password}"/>
    15    </bean>
    16    <!--    SqlSessionFactory -->
    17    <bean id="db1SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    18        <property name="dataSource" ref="db1DataSource"/>
    19        <property name="mapperLocations" value="classpath*:sql/db1/*Mapper.xml"/>
    20    </bean>
    21    <!-- 这里的配置可以使用注解 @MapperScan   -->
    22<!--    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    23        <property name="basePackage" value="com.lei.**.db1dao"/>
    24        <property name="sqlSessionFactoryBeanName" value="db1SqlSessionFactory"/>
    25    </bean>-->
    26    <!--    sqlSessionTemplate -->
    27    <bean id="db1SqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    28        <!-- 只能使用构造方法注入,因为该类没有 set 方法-->
    29        <constructor-arg ref="db1SqlSessionFactory"/>
    30    </bean>
    31
    32    <bean id="db1TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    33        <property name="dataSource" ref="db1DataSource"/>
    34    </bean>
    35    <!-- 自动为spring容器中那些配置@aspectJ切面的bean创建代理-->
    36    <aop:aspectj-autoproxy/>
    37</beans>

    db2数据源

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3       xmlns:tx="http://www.springframework.org/schema/tx"
     4       xmlns:aop="http://www.springframework.org/schema/aop"
     5       xmlns="http://www.springframework.org/schema/beans"
     6       xsi:schemaLocation="http://www.springframework.org/schema/beans
     7    http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
     8    http://www.springframework.org/schema/aop
     9     http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
    10
    11    <bean id="db2DataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    12        <property name="url" value="${my.datasource.db2.url}"/>
    13        <property name="username" value="${my.datasource.db2.username}"/>
    14        <property name="password" value="${my.datasource.db2.password}"/>
    15    </bean>
    16    <!--    SqlSessionFactory -->
    17    <bean id="db2SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    18        <property name="dataSource" ref="db2DataSource"/>
    19        <property name="mapperLocations" value="classpath*:sql/db2/*Mapper.xml"/>
    20    </bean>
    21<!--    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">-->
    22<!--        <property name="basePackage" value="com.lei.**.db2dao"/>-->
    23<!--        <property name="sqlSessionFactoryBeanName" value="db2SqlSessionFactory"/>-->
    24<!--    </bean>-->
    25    <!-- sqlSessionTemplate -->
    26    <bean id="db2SqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    27        <!-- 只能使用构造方法注入,因为该类没有 set 方法-->
    28        <constructor-arg ref="db2SqlSessionFactory"/>
    29    </bean>
    30
    31    <bean id="db2TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    32        <property name="dataSource" ref="db2DataSource"/>
    33    </bean>
    34    <aop:aspectj-autoproxy/>
    35</beans>
  3. 启动类配置相关注解

    java
     1@SpringBootApplication
     2//@EnableTransactionManagement
     3//导入数据源配置文件
     4@ImportResource({"classpath*:config/db1.xml","classpath*:config/db2.xml"})
     5//绑定mapper接口和sqlsession
     6@MapperScan(basePackages ="com.lei.**.db1dao", sqlSessionTemplateRef = "db1SqlSession")
     7@MapperScan(basePackages ="com.lei.**.db2dao", sqlSessionTemplateRef = "db2SqlSession")
     8public class TwoApplication {
     9    public static void main(String[] args) {
    10        SpringApplication.run(TwoApplication.class, args);
    11    }
    12}

进行测试,多数据源配置成功

注意:定义扫描dao接口时,可能多个数据源扫描了同一位置,此时后扫描到的dao接口时,不再创建实例bean,此时可以通过数据源配置文件导入先后顺序来控制

xml
1<!-- 
2	这里的db1配置扫描的mapper范围会将db2的mapper扫描到
3	因此先加载db2配置,这样db2 的 mapper 先实例化,后续db1扫描到就会忽略
4-->
5
6<!--分布式关系数据库-->
7<import resource="classpath:config/db2.xml"/>
8<import resource="classpath:config/db1.xml"/>

事务支持

声明式事务

@Transactional:可以标注在类上或方法上,事务支持

@EnableTransactionManagement:Application 类上,表示开启事务,可以省略

编程式事务

事务管理器 PlatformTransactionManager、事务状态 TransactionStatus、事务属性的定义 TransactionDefinition

  1. 通过PlatformTransactionManager实现编程式事务

    java
     1@Override
     2public StudentEntity update(StudentEntity studentEntity) {
     3    //事务定义
     4	DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
     5    definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
     6    //新开事务
     7    TransactionStatus status = transactionManager.getTransaction(definition);
     8    try {
     9        studentsDao.update(studentEntity);
    10        //事务定义
    11        DefaultTransactionDefinition definition1 = new DefaultTransactionDefinition();
    12        definition1.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
    13        //新开事务
    14        TransactionStatus status1 = transactionManager.getTransaction(definition1);
    15        try {
    16            StudentEntity studentEntity1=new StudentEntity();
    17            BeanUtils.copyProperties(studentEntity,studentEntity1);
    18            studentEntity1.setId(2L);
    19            studentEntity1.setName("后续更新");
    20            studentsDao.update(studentEntity1);
    21            int a=10/0;
    22            transactionManager.commit(status1);
    23        }catch (Exception e){
    24            transactionManager.rollback(status1);
    25        }
    26        //提交事务
    27        transactionManager.commit(status);
    28    }catch (Exception e){
    29        //回滚事务
    30        transactionManager.rollback(status);
    31    }
    32}
  2. 通过transactionTemplate实现编程式事务

    java
     1@Override
     2public StudentEntity update(StudentEntity studentEntity) {
     3	transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
     4    this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
     5        @Override
     6        protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
     7            try {
     8                studentsDao.update(studentEntity);
     9                //设置事务的传播级别
    10                transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
    11                //新开事务
    12                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    13                    @Override
    14                    protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
    15                        try {
    16                            StudentEntity studentEntity1=new StudentEntity();
    17                            BeanUtils.copyProperties(studentEntity,studentEntity1);
    18                            studentEntity1.setId(2L);
    19                            studentEntity1.setName("后续更新");
    20                            studentsDao.update(studentEntity1);
    21                        }catch(Exception e){
    22                            //标记事务为回滚
    23                            transactionStatus.setRollbackOnly();
    24                        }
    25                    }
    26                });
    27                int a=10/0;
    28            }catch (Exception e){
    29                transactionStatus.setRollbackOnly();
    30            }
    31        }
    32    });
    33}

注:以上两种编程式事务,都是事务嵌套;外层事务和内层事务通过事务传播级别关联;设置不同的事务传播级别,可以实现:内部事务抛错外部回滚,内部抛错外部提交等;通过事务传播级别,设置事务管理器中的事务是否RollbackOnly

注意:SpringBoot集成mybatis时,通过sqlSession进行数据库操作时,必须注意 sqlSession 可以设置自动提交,但是连接池连接的自动提交并不能在获取sqlSession时设置

openSession(boolean autoCommit),autoCommit 可选参数传递 true 值即可开启自动提交功能,没有提供同时设置 ConnectionautoCommit 的方法,这是因为 MyBatis 会依据传入的 Connection 来决定是否启用 autoCommit

SqlSessionTemplate

SqlSessionTemplate 是 MyBatis-Spring 的核心,作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSessionSqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用,SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关

java
 1@Override
 2public StudentEntity update(StudentEntity studentEntity) {
 3    SqlSession sqlSession=null;
 4    TransactionDefinition definition = new DefaultTransactionDefinition();
 5    TransactionStatus status = transactionManager.getTransaction(definition);
 6    try {
 7        //这里通过sqlSessionTemplate获取到的sqlSession总是加入当前已存在事务
 8        sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.SIMPLE,false);
 9        StudentsDao mapper = sqlSession.getMapper(StudentsDao.class);
10        mapper.update(studentEntity);
11        //sqlSessionTemplate的特性,这里的提交失效,以外层事务为准
12        sqlSession.commit();
13    }catch (Exception e){
14        sqlSession.rollback();
15    }finally {
16        if (sqlSession!=null){
17            sqlSession.close();
18        }
19    }
20    //这里提交或回滚,包括sqlSessionTemplate进行的sql操作
21    transactionManager.commit(status);
22    return this.queryById(studentEntity.getId());
23}

rollback-only

当事务嵌套时,事务传播特性(propagation)为REQUIRED时,以下情况会出现rollback-only

  1. 当里层事务抛错,标记为回滚,但是错误被捕获,外层未抛错出来,此时里层缺已经标记为只能rollbackOnly,外层提交事务就会抛错rollback-only
  2. 当外层为只读事务时,里层事务抛错,此时只读事务提交也会抛错rollback-only

业务代码执行时,记录错误

java
 1@Override
 2@Transactional("db1TransactionManager")
 3public Object testB() {
 4    Db1Tb1 db1Tb1=new Db1Tb1();
 5    db1Tb1.setAge(12);
 6    db1Tb1.setName("db1");
 7    db1Tb1Dao.insert(db1Tb1);
 8
 9    DefaultTransactionDefinition transactionDefinition=new DefaultTransactionDefinition();
10    //注意事务传播级别,新开事务
11    //用于记录业务代码中的错误,业务代码回滚,错误信息提交
12    transactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
13    TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
14    try {
15        db1Tb1.setAge(16);
16        db1Tb1Dao.insert(db1Tb1);
17        platformTransactionManager.commit(transaction);
18    }catch (Exception e){
19        platformTransactionManager.rollback(transaction);
20    }
21    //这里抛错 不会影响第新开事务的数据插入
22    int i =1/0;
23    return null;
24}

RESTFul风格

主要是用@PathVariable("")注解

  1. controller代码

    java
    1@RestController
    2public class TestController {
    3    @RequestMapping("/test/{id}/{name}")
    4    public String test(@PathVariable("id") Integer id,
    5                       @PathVariable("name") String name){
    6        return id+" "+name;
    7    }
    8}
  2. 访问地址:127.0.0.1:8080/test/123/唐磊

注意请求路径不清楚的情况,restful风格,通常使用对应的方法请求mapping注解,例:

java
 1//请求访问时,会出现请求模糊的错误
 2@RestController
 3public class TestController {
 4    @RequestMapping("/test/{id}/{name}")
 5    public String test(@PathVariable("id") Integer id,
 6                       @PathVariable("name") String name){
 7        return id+" "+name;
 8    }
 9    @RequestMapping("/test/{id}/{desc}")
10    public String test2(@PathVariable("id") Integer id,
11                       @PathVariable("desc") String name){
12        return id+" "+name;
13    }
14}

修改为以下代码无误,然后通过对应的请求方式发送请求

java
 1@RestController
 2public class TestController {
 3    @PostMapping("/test/{id}/{name}")
 4    public String test(@PathVariable("id") Integer id,
 5                       @PathVariable("name") String name){
 6        return id+" "+name;
 7    }
 8    @GetMapping("/test/{id}/{desc}")
 9    public String test2(@PathVariable("id") Integer id,
10                       @PathVariable("desc") String name){
11        return id+" "+name;
12    }
13}

普通java程序

SpringBoot实现普通java程序,有两种方法

  1. 在SpringBoot启动方法中获取 ConfigurableApplicationContext

    java
    1//获得容器,然后取得bean并执行
    2ConfigurableApplicationContext context=SpringApplication.run(Application.class, args);
  2. 在SpringBoot启动类实现CommandLineRunner接口,并重写run方法

    java
     1//实现CommandLineRunner接口,并重写run方法
     2CommandLineRunner
     3@SpringBootApplication
     4public class Application implements CommandLineRunner
     5CommandLineRunner {
     6    //注入bean
     7    @Autowired
     8    private UserService userService;
     9    public static void main(String[] args) {
    10        SpringApplication.run(Application.class, args);
    11    }
    12//重写接口方法,调用bean方法,SpringBoot容器启动后会回调该方法
    13    @Override
    14    public void run(String... args) throws Exception {
    15        userService.say();
    16    }
    17}

拦截器 Interceptor

  1. 实现HandlerInterceptor接口并实现相关方法

    java
     1@Order(1)    //order代表拦截器执行顺序
     2@Component	 //注册到spring容器,以便后续将拦截在WebMvcConfigurer中注册
     3public class LoginInterceptor implements HandlerInterceptor {
     4    //预处理回调方法,实现处理器的预处理,如登录检查;true表示执行下一流程、false中断流程(需要通过response来响应)
     5    @Override
     6    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
     7        if (request.getSession().getAttribute("user")==null){
     8            response.sendRedirect(request.getContextPath()+"/user/err");
     9            return false;
    10        }
    11        return true;
    12    }
    13
    14    //后处理回调方法,在控制器后视图层前,可以通过modelAndView来处理视图层
    15    @Override
    16    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    17        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    18    }
    19
    20    //整个请求处理完毕后的回调方法,即在视图渲染完毕后回调;如性能监控,在此输出结束时间,还可以进行资源清理(类似于finally)
    21    @Override
    22    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    23        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    24    }
    25}
  2. 注册 WebMvcConfigurer bean,添加拦截器到SpringBoot容器,并配置需要拦截的请求

    java
     1@Bean
     2WebMvcConfigurer createWebMvcConfigurer(@Autowired LoginInterceptor loginInterceptor) {
     3    return new WebMvcConfigurer() {
     4        //需要被拦截的请求 **代表当前请求路径下的所有请求,*代表当前请求路径同级请求
     5        // /user/* 表示拦截/user/xx的请求,/user/** 表示拦截 /user/xx/xx...的请求
     6        String[] addPath={"/user/**"};
     7        String[] excludePath={"/user/login","/user/err"};
     8        public void addInterceptors(InterceptorRegistry registry) {
     9            registry.addInterceptor(loginInterceptor)
    10                .addPathPatterns(addPath)      //需要拦截的请求地址
    11                .excludePathPatterns(excludePath);  //不需要拦截的请求地址
    12        }
    13    };
    14}
  3. 编写测试类

    java
     1@RestController
     2@RequestMapping("/user")
     3public class UserController {
     4    //登录,可不登录访问
     5    @RequestMapping("/login")
     6    public String login(HttpServletRequest request, HttpServletResponse response) throws IOException {
     7        User user= (User) request.getSession().getAttribute("user");
     8        if (user==null){
     9            user=new User();
    10            user.setId(UUID.randomUUID().toString());
    11            request.getSession().setAttribute("user",user);
    12        }else {
    13            response.sendRedirect(request.getContextPath()+"/user/home");
    14        }
    15        return "login";
    16    }
    17    //需要登录后访问
    18    @RequestMapping("/home")
    19    public String home(HttpServletRequest request){
    20        User user= (User) request.getSession().getAttribute("user");
    21        return "home"+user;
    22    }
    23    //错误,可不登录访问,未登录时跳转这个请求
    24    @RequestMapping("/err")
    25    public String err(){
    26        return "err";
    27    }
    28}

servlet、过滤器、监听器

servlet @WebServlet

两种实现方式:主要步骤都是编写一个Servlet,然后在web.xml中注册该servlet

  1. 方式一:注解方式实现 @WebServlet @ServletComponentScan

    1. servlet编写

      java
       1//@WebServlet声明该类是一个servlet
       2@WebServlet(urlPatterns = "/oneSetvlet")
       3public class OneServlet extends HttpServlet {
       4    @Override
       5    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
       6        resp.getWriter().println("one servlet");
       7        resp.getWriter().flush();
       8        resp.getWriter().flush();
       9    }
      10}
    2. SpringBoot启动类配置servlet扫描

      java
      1@SpringBootApplication
      2//@ServletComponentScan 启动servlet扫描
      3@ServletComponentScan(basePackages = "com.lei.servlet")
      4public class Application {
      5    public static void main(String[] args) {
      6        SpringApplication.run(Application.class, args);
      7    }
      8}
  2. 方式二:配置类注册组件实现

    1. 编写servlet

      java
      1public class TwoServlet extends HttpServlet {
      2    @Override
      3    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      4        resp.getWriter().println("two servlet");
      5        resp.getWriter().flush();
      6        resp.getWriter().flush();
      7    }
      8}
    2. 编写配置类,并注册servlet

      java
      1@Configuration
      2public class MyConfig {
      3    @Bean
      4    public ServletRegistrationBean<HttpServlet> registrationBean(){
      5        return new ServletRegistrationBean<>(new TwoServlet(),"/twoServlet");
      6    }
      7}

过滤器 @WebListenner

过滤器属于servlet的,声明过滤器,注册到SpringBoot容器中

SpringBoot有两种实现方式:

  1. 方式一:注解方式 @WebFilter@

    1. 创建过滤器,实现 servlet 下的 Filter 接口,并重写方法

      java
       1//定义该类为一个过滤器
       2@WebFilter(urlPatterns = "/*")
       3public class TwoFilter implements Filter {
       4    //过滤器的创建由web服务器控制,过滤器执行顺序,可在注册时通过setOrder,当不设置 setOrder 次序时,过滤器的执行顺序默认是 Bean 的加载顺序
       5    //初始化参数,在创建Filter时自动调用。当我们需要设置初始化参数的时候,可以写到该方法中
       6    @Override
       7    public void init(FilterConfig filterConfig) throws ServletException {
       8        Filter.super.init(filterConfig);
       9    }
      10    //拦截到要执行的请求时,doFilter就会执行。这里面写我们对请求和响应的预处理
      11    @Override
      12    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      13        System.out.println("过滤器1生效");
      14    }
      15    //在销毁Filter时自动调用
      16    @Override
      17    public void destroy() {
      18        Filter.super.destroy();
      19    }
      20}
    2. SpringBoot启动类,配置启动servlet扫描

      java
      1@SpringBootApplication
      2@ServletComponentScan(basePackages = "com.lei.filter")
      3public class Application {
      4    public static void main(String[] args) {
      5        SpringApplication.run(Application.class, args);
      6    }
      7}
  2. 方式二:配置类注册组件方式

    1. 创建过滤器,实现 servlet 下的 Filter 接口,并重写方法

      java
       1public class Filter1 implements Filter {
       2    @Override
       3    public void init(FilterConfig filterConfig) throws ServletException {
       4        Filter.super.init(filterConfig);
       5    }
       6    @Override
       7    public void destroy() {
       8        Filter.super.destroy();
       9    }
      10    @Override
      11    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
      12        System.out.println("过滤器1");
      13        //放行
      14       filterChain.doFilter(servletRequest,servletResponse);
      15    }
      16}
    2. 编写配置类,注册过滤器到SpringBoot

      java
       1@Configuration
       2public class MyConfig {
       3    @Bean
       4    public FilterRegistrationBean<Filter> registrationBean(){
       5        FilterRegistrationBean<Filter> registrationBean=new FilterRegistrationBean<>(new Filter1());
       6        registrationBean.addUrlPatterns("/*");
       7        System.out.println(registrationBean.getOrder());
       8        return registrationBean;
       9    }
      10}

listenner 和 servlet 监听器实现类似

过滤器实现过滤空参数

创建ServletRequest包装类处理空字符

java
 1public class TrimHttpServletRequestWrapper extends HttpServletRequestWrapper {
 2    /**
 3     * 设置不需要去掉空格的参数
 4     * key:为uri地址
 5     * values:参数名称
 6     */
 7    private final Map<String, Set<String>> ignoreFieldMap;
 8
 9    public TrimHttpServletRequestWrapper(HttpServletRequest request, Map<String, Set<String>> ignoreFieldMap) {
10        super(request);
11        this.ignoreFieldMap = ignoreFieldMap;
12    }
13
14
15    @Override
16    public String[] getParameterValues(String name) {
17        String[] values = super.getParameterValues(name);
18        if (values == null || ignoreFieldMap.get(name) != null) {
19            return values;
20        } else {
21            int length = values.length;
22            String[] escapeValues = new String[length];
23
24            for (int i = 0; i < length; i++) {
25                escapeValues[i] = values[i].trim();
26            }
27            return escapeValues;
28        }
29    }
30
31    @Override
32    public String getParameter(String parameter) {
33        String value = super.getParameter(parameter);
34        if (value == null || ignoreFieldMap.get(parameter) != null) {
35            return value;
36        } else {
37            return value.trim();
38        }
39    }
40}

创建过滤器

java
 1/**
 2 * 处理请求参数中前后空格
 3 */
 4@Component
 5@WebFilter(urlPatterns = "/**", filterName = "ParamsFilter", dispatcherTypes = DispatcherType.REQUEST)
 6public class ParamFilter implements Filter {
 7
 8    @Value("${server.servlet.context-path}")
 9    public String contextPath;
10
11    /**
12     * 不过滤空格的uri地址
13     */
14    private Set<String> excludeUris;
15
16    /**
17     * 设置需要过滤空格的uri
18     */
19    private Set<String> includeUris;
20
21    /**
22     * 设置不需要去掉空格的参数
23     * key:为uri地址
24     * values:参数名称
25     */
26    private Map<String, Set<String>> ignoreFieldMap;
27
28    private static final PathMatcher matcher = new AntPathMatcher();
29
30    @Override
31    public void init(FilterConfig filterConfig) throws ServletException {
32        this.includeUris = new HashSet<>();
33        this.excludeUris = new HashSet<>();
34        this.ignoreFieldMap = new HashMap<>();
35
36        includeUris.add(contextPath + "/**/testA");
37        ignoreFieldMap.put("", new HashSet<>());
38    }
39
40    @Override
41    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
42
43        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
44        String requestUri = httpServletRequest.getRequestURI();
45
46        //是否需要过滤标志
47        boolean includeFlag = false;
48        boolean excludeFlag = true;
49        //匹配需要进行处理的url
50        for (String uris : includeUris) {
51            if (match(uris, requestUri)) {
52                includeFlag = true;
53                break;
54            }
55        }
56        //匹配除去的url
57        for (String uris : excludeUris) {
58            if (match(uris, requestUri)) {
59                excludeFlag = false;
60                break;
61            }
62        }
63        chain.doFilter(request, response);
64        if (includeFlag && excludeFlag) {
65            //过滤器处理
66            chain.doFilter(new TrimHttpServletRequestWrapper(httpServletRequest, ignoreFieldMap), response);
67        } else {
68            //放行
69            chain.doFilter(request, response);
70        }
71    }
72
73    private boolean match(String uris, String requestUri) {
74        if (Objects.isNull(uris) || Objects.isNull(requestUri)) {
75            return false;
76        }
77        return matcher.match(uris, requestUri);
78    }
79
80    @Override
81    public void destroy() {
82        Filter.super.destroy();
83    }
84}

设置字符编码

针对servlet使用,两种方式

方式一:使用SpringBoot核心配置文件设置

  • application.properties

    properties
    1server.servlet.encoding.enabled=true
    2server.servlet.encoding.force=true
    3server.servlet.encoding.charset=utf-8
  • 设置浏览器编码

    java
     1@WebServlet(urlPatterns = "/oneSetvlet")
     2public class OneServlet extends HttpServlet {
     3    @Override
     4    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     5        resp.setContentType("text/html;character=utf-8");
     6        resp.getWriter().println("你好世界,one servlet");
     7        resp.getWriter().flush();
     8        resp.getWriter().close();
     9    }
    10}

方式二:通过注册spring编码过滤器,并关闭SpringBoot自带编码

  • SpringBoot自带编码过滤器注册

    java
     1@Configuration
     2public class MyConfig {
     3    @Bean
     4    public FilterRegistrationBean<Filter> filterFilterRegistrationBean(){
     5        CharacterEncodingFilter characterEncodingFilter=new CharacterEncodingFilter("utf-8");
     6        //forceEncoding用来设置是否理会 request.getCharacterEncoding()方法,设置为true则强制覆盖之前的编码格式
     7        characterEncodingFilter.setForceEncoding(true);
     8        FilterRegistrationBean<Filter> filterFilterRegistrationBean=new FilterRegistrationBean<>();
     9        filterFilterRegistrationBean.setFilter(characterEncodingFilter);
    10        return filterFilterRegistrationBean;
    11    }
    12}
  • 关闭SpringBoot自带编码

    properties
    1server.servlet.encoding.enabled=false
  • 设置浏览器编码

    java
     1@WebServlet(urlPatterns = "/oneSetvlet")
     2public class OneServlet extends HttpServlet {
     3    @Override
     4    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     5        resp.setContentType("text/html;character=utf-8");
     6        resp.getWriter().println("你好世界,one servlet");
     7        resp.getWriter().flush();
     8        resp.getWriter().close();
     9    }
    10}

SpringBoot工程打包

打包war

  1. 修改pom.xml

    1. 修改打包形式
    2. 移除内嵌tomcat
    3. 添加servlet依赖
    4. 指定web资源文件夹,指定配置资源文件夹
  2. 配置类继承 SpringBootServletInitializer 并重写 configure 方法

    java
     1@SpringBootApplication
     2@ServletComponentScan(basePackages = "com.lei.filter")
     3//打包war启动类继承SpringBootServletInitializer类,并重写configure方法
     4public class Application extends SpringBootServletInitializer {
     5    public static void main(String[] args) {
     6        SpringApplication.run(Application.class, args);
     7    }
     8
     9    @Override
    10    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    11        return builder.sources(Application.class);
    12    }
    13}

SpringBoot打war包,在SpringBoot核心配置文件中配置的tomcat相关配置失效,以部署的tomcat配置为主

热部署插件

  1. JRebel插件 或者 devtools 或者 启动配置更新 resource 和 class
  2. 代码修改后 build一下

数据库连接池

SpringBoot2.x后默认使用 HikariCP 作为数据源,因此使用其他数据源需要进行配置

  1. pom.xml 排除默认的 HikariCP 数据库连接池,并添加 数据库连接池

    xml
     1<dependency>
     2    <groupId>org.mybatis.spring.boot</groupId>
     3    <artifactId>mybatis-spring-boot-starter</artifactId>
     4    <exclusions>
     5        <exclusion>
     6            <groupId>com.zaxxer</groupId>
     7            <artifactId>HikariCP</artifactId>
     8        </exclusion>
     9    </exclusions>
    10</dependency>
    11<dependency>
    12    <groupId>com.alibaba</groupId>
    13    <artifactId>druid</artifactId>
    14    <version>1.2.6</version>
    15</dependency>
  2. SpringBoot核心配置文件指定数据源

    properties
    1#指定数据源
    2spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

也可以直接使用起步依赖,不需要在SpringBoot核心配置文件中指定数据源,并且可以在SpringBoot核心配置文件中配置该连接池相关参数

xml
 1<dependency>
 2    <groupId>org.mybatis.spring.boot</groupId>
 3    <artifactId>mybatis-spring-boot-starter</artifactId>
 4    <exclusions>
 5        <exclusion>
 6            <groupId>com.zaxxer</groupId>
 7            <artifactId>HikariCP</artifactId>
 8        </exclusion>
 9    </exclusions>
10</dependency>
11<dependency>
12    <groupId>com.alibaba</groupId>
13    <artifactId>druid-spring-boot-starter</artifactId>
14    <version>1.2.6</version>
15</dependency>

CORS跨域

传统ajax请求,只能请求同一域下的资源,否则存在跨域错误

同源:指的是协议、端口、host都相同,则视为同源

  1. 使用CorsRegistry,注册 WebMvcConfigurer bean,在该bean中配置可以跨域访问的请求

    java
     1@Bean
     2public WebMvcConfigurer mvcConfigurer(){
     3    return new WebMvcConfigurer() {
     4        @Override
     5        public void addCorsMappings(CorsRegistry registry) {
     6            registry.addMapping("/api/**")
     7                //放行哪些请求方式
     8                .allowedMethods("GET","POST")
     9                //设置放行哪些原始域
    10                .allowedOrigins("*")
    11                //放行哪些原始请求头部信息
    12                .allowedHeaders("*")
    13                //暴露哪些原始请求头部信息
    14                .exposedHeaders("*");
    15        }
    16    };
    17}
  2. 创建配置类,实现接口WebMvcConfigurer ,并重写 addCorsMappings() 方法

    java
     1@Configuration
     2public class MyWebMvcConfig implements WebMvcConfigurer {
     3    @Override
     4    public void addCorsMappings(CorsRegistry registry) {
     5        registry.addMapping("/api/**")
     6                //放行哪些请求方式
     7                .allowedMethods("GET","POST")
     8                //设置放行哪些原始域
     9                .allowedOrigins("*")
    10                //放行哪些原始请求头部信息
    11                .allowedHeaders("*")
    12                //暴露哪些原始请求头部信息
    13                .exposedHeaders("*");
    14    }
    15}
  3. 使用注解@CrossOrigin(origins = "*",methods = {RequestMethod.GET,RequestMethod.POST})

  4. 使用jsonp方式,只支持get请求,局限性大,但是支持老式浏览器

静态资源的处理

SpringBoot默认静态资源位于 classpath 目录下,且满足如下命名

  1. /static
  2. /public
  3. /resources
  4. /META-INF/resources

以上目录编译后在classpath下,可以直接访问,不需要加目录名

可以通过资源类查看

java
1private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

可以通过核心配置文件修改默认的静态资源放置位置,修改后原先默认的失效

properties
1spring.web.resources.static-locations=classpath:/mystatic/

线程池异步调用

SpringBoot框架自动装配提供了一个线程池,用于异步提交任务

有了线程池,只需要编写任务即可,采用注解@Async提交任务

SpringBoot自动装配的线程池还可以根据需要配置

  1. 创建配置类,注入线程池 bean,覆盖默认的线程池,会覆盖SpringBoot创建的默认线程池

    java
     1@Bean
     2public ThreadPoolTaskExecutor asyncExecutor(){
     3    ThreadPoolTaskExecutor taskExecutor=new ThreadPoolTaskExecutor();
     4    taskExecutor.setThreadNamePrefix("mythread");
     5    taskExecutor.setCorePoolSize(2);
     6    taskExecutor.setMaxPoolSize(10);
     7    taskExecutor.setQueueCapacity(9999);
     8    taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
     9    return taskExecutor;
    10}
  2. SpringBoot核心配置文件也可以配置线程池

    properties
    1spring.task.execution.pool.core-size=5
    2spring.task.execution.pool.max-size=100
    3spring.task.execution.thread-name-prefix=th-
    4spring.task.execution.pool.queue-capacity=999
  3. 实例一:通过@Async注解,需要在启动类开启异步执行@EnableAsync,@Async标注的方法会放入线程池中执行(通过AOP方式)

    java
     1@Service
     2public class MyServiceImpl implements MyService {
     3    @Async		//标注该方法异步执行
     4    @Override
     5    public Future<String> speak() {
     6        return AsyncResult.forValue("hello");
     7    }
     8}
     9
    10
    11@SpringBootApplication
    12@EnableAsync    //开启异步执行
    13public class Application implements CommandLineRunner {
    14
    15    @Autowired
    16    private MyService myService;
    17
    18    public static void main(String[] args) {
    19       ApplicationContext context= SpringApplication.run(Application.class, args);
    20        System.out.println(1);
    21    }
    22
    23    @Override
    24    public void run(String... args) throws Exception {
    25        System.out.println("主--------------");
    26        Future<String> th =myService.speak();
    27        System.out.println("主------------");
    28        System.out.println("线程池任务-----"+th.get());
    29    }
    30}
  4. 实例二:通过注入的线程池执行对象

    java
     1@SpringBootApplication
     2public class Application implements CommandLineRunner {
     3    @Autowired
     4    private ThreadPoolTaskExecutor taskExecutor;
     5
     6    public static void main(String[] args) {
     7       ApplicationContext context= SpringApplication.run(Application.class, args);
     8    }
     9
    10    @Override
    11    public void run(String... args) throws Exception {
    12        List<Future<String>> futureList=new ArrayList<>();
    13        for (int i=0;i<20;i++){
    14            Future<String> future=taskExecutor.submit(()->{
    15                System.out.println(Thread.currentThread().getName()+"线程池任务运行");
    16                Thread.sleep(150);
    17                return "运行成功";
    18            });
    19            futureList.add(future);
    20        }
    21
    22        for(Future<String> future:futureList){
    23            //轮询查询任务执行完没,完了获取返回值终止等待该任务返回值,否则睡5ms再次查询
    24            while (true){
    25                if (future.isDone()&&!future.isCancelled()){
    26                    //获取线程执行返回值
    27                    System.out.println(future.get()+ LocalDateTime.now());
    28                    break;
    29                }else {
    30                    Thread.sleep(10);
    31                }
    32            }
    33        }
    34    }
    35}

内嵌服务器

SpringBoot内嵌服务器默认为 Tomcat,也可以将程序切换为其他服务器 Undertow、Jetty,只需要通过pom.xml配置即可

排除默认的tomcat服务器,导入其他服务器的起步依赖

xml
 1<dependency>
 2    <groupId>org.springframework.boot</groupId>
 3    <artifactId>spring-boot-starter-web</artifactId>
 4    <exclusions>
 5        <exclusion>
 6            <groupId>org.springframework.boot</groupId>
 7            <artifactId>spring-boot-starter-tomcat</artifactId>
 8        </exclusion>
 9    </exclusions>
10</dependency>
11<!--        
12<dependency>
13     <groupId>org.springframework.boot</groupId>
14     <artifactId>spring-boot-starter-undertow</artifactId>
15</dependency>
16-->
17<!--如果需要支持jsp,需要额外添加依赖,undertow不支持jsp-->
18<dependency>
19    <groupId>org.springframework.boot</groupId>
20    <artifactId>spring-boot-starter-jetty</artifactId>
21</dependency>

HTTPS支持

  1. 生成证书,JDK生成(也可以采用openssl生成)

    bash
    1#生成jks证书文件
    2keytool -genkey -keyalg RSA -keystore tomcat.jks
    3#转换为行业规范 pkcs12 证书
    4keytool -importkeystore -srckeystore tomcat.jks -destkeystore tomcat.pkcs12 -deststoretype pkcs12
    5#查看证书内容
    6keytoll -list -v -keystore tomcat.jks
  2. SpringBoot核心配置文件配置

    properties
    1#ssh端口
    2server.port=8088
    3#证书
    4server.ssl.key-store=classpath:tomcat.pkcs12
    5#证书密码
    6server.ssl.key-store-password=123456
    7#证书证书类型 jks/PKCS12
    8server.ssl.trust-store-type=PKCS12
  3. 浏览器访问:https://127.0.0.1:8088/web/t1

  4. HTTPS和HTTP不能同时配置,可以通过配置一个HTTPS,编写类实现一个HTTP

HTTPS和HTTP共存

  • 方法一

    • 核心配置文件中启动 HTTPS支持

    • 在配置类中注册 servletContainer bean

      java
       1@Bean
       2public TomcatServletWebServerFactory servletContainer(){
       3    TomcatServletWebServerFactory tomcat=new TomcatServletWebServerFactory(){
       4        @Override
       5        //该方法重写可以实现用户访问HTTP时,跳转到HTTPS
       6        protected void postProcessContext(Context context) {
       7            SecurityConstraint constraint=new SecurityConstraint();
       8            constraint.setUserConstraint("CONFIDEETIAL");
       9            SecurityCollection collection=new SecurityCollection();
      10            collection.addPattern("/*");
      11            constraint.addCollection(collection);
      12            context.addConstraint(constraint);
      13        }
      14    };
      15    tomcat.setContextPath("/web");
      16    tomcat.addAdditionalTomcatConnectors(createHttpConnector());
      17    return tomcat;
      18}
      19//创建连接器
      20private Connector createHttpConnector() {
      21    Connector connector=new Connector("org.apache.coyote.http11.Http11NioProtocol");
      22    connector.setScheme("http");
      23    connector.setPort(8080);
      24    connector.setSecure(false);
      25    //https端口
      26    connector.setRedirectPort(8088);
      27    return  connector;
      28}
    • 此时访问http会自动跳转到https上

  • 全部通过代码实现HTTP和HTTPS,HTTPS实现也是注册一个bean

全局异常拦截

方式一:

  1. 创建一个拦截异常的控制器

    java
     1//ControllerAdvice返回指定页面;RestControllerAdvice返回JSON数据,也可以使用@ControllerAdvice和@ResponBody配合返回JSON数据
     2
     3//annotations表示存在该注解的类,发生错误时,拦截
     4@RestControllerAdvice(annotations = {Controller.class, RestController.class})
     5public class ExceptionAdvice {
     6    // 可以注入已存在的 bean
     7    @ExceptionHandler(LoginException.class)
     8    public Object errHandlerError(HttpServletRequest request, Exception e) throws Exception{
     9        Map<String,String> map=new HashMap<>();
    10        map.put("请登录","value1");
    11        // 获取错误信息
    12        map.put("error",e.getMessage());
    13        return map;
    14    }
    15}
  2. 此时当控制器出现异常时,会返回指定信息;也可以配置为返回指定错误页,不能多次定义一个异常

方式二

注册一个 WebServerFactory Bean,并重写其中的 factory方法,会拦截SpringBoot的错误响应,并根据状态码返回不同的页面

java
 1@Bean
 2public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
 3    return ((ConfigurableWebServerFactory factory) -> {
 4        ErrorPage errorPage400=new ErrorPage(HttpStatus.BAD_REQUEST,"/400.html");
 5        ErrorPage errorPage404=new ErrorPage(HttpStatus.NOT_FOUND,"/404.html");
 6        ErrorPage errorPage500=new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/500.html");
 7        ErrorPage errorPage503=new ErrorPage(HttpStatus.SERVICE_UNAVAILABLE,"/503.html");
 8        factory.addErrorPages(errorPage400,errorPage404,errorPage500,errorPage503);
 9    });
10}

注意:错误页放在以下目录中都可:

tex
1[Classpath [META-INF/resources/], Classpath [resources/], Classpath [static/], Classpath [public/], ServletContext [/]]

方式三

在控制器中创建@ExceptionHandler注解标记的方法,该异常处理方法没有固定的方法签名,可以传入ExceptionHttpServletRequest等,返回值可以是void,也可以是ModelAndView

ControllerA定义的ExceptionHandler对ControllerB不生效

java
 1@Controller
 2public class BaseController {
 3    @ExceptionHandler(LoginException.class)
 4    @ResponseBody
 5    public Object handleUnknowException(Exception ex) {
 6        Map<String,String> map=new HashMap<>();
 7        map.put("请登录","value1");
 8        map.put("key2","value2");
 9        return map;
10    }
11}

统一切面日志打印

java
  1/**
  2 * 请求日志方面
  3 *
  4 * @author lei
  5 * @date 2023/05/09
  6 */
  7@Component
  8@Aspect
  9@Slf4j
 10public class RequestLogAspect {
 11
 12    @Pointcut("execution(* org.lei.*.controller..*(..))")
 13    public void requestServer() {
 14    }
 15
 16    @Around("requestServer()")
 17    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
 18        long start = System.currentTimeMillis();
 19        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
 20        HttpServletRequest request = attributes.getRequest();
 21        Object result = proceedingJoinPoint.proceed();
 22        RequestInfo requestInfo = new RequestInfo();
 23        requestInfo.setIp(request.getRemoteAddr());
 24        requestInfo.setUrl(request.getRequestURL().toString());
 25        requestInfo.setHttpMethod(request.getMethod());
 26        requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(),
 27                proceedingJoinPoint.getSignature().getName()));
 28        requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint));
 29        requestInfo.setResult(result);
 30        requestInfo.setTimeCost(System.currentTimeMillis() - start);
 31        log.info("Request Info      : {}", JSON.toJSONString(requestInfo));
 32
 33        return result;
 34    }
 35
 36
 37    @AfterThrowing(pointcut = "requestServer()", throwing = "e")
 38    public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) {
 39        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
 40        HttpServletRequest request = attributes.getRequest();
 41        RequestErrorInfo requestErrorInfo = new RequestErrorInfo();
 42        requestErrorInfo.setIp(request.getRemoteAddr());
 43        requestErrorInfo.setUrl(request.getRequestURL().toString());
 44        requestErrorInfo.setHttpMethod(request.getMethod());
 45        requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(),
 46                joinPoint.getSignature().getName()));
 47        requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint));
 48        requestErrorInfo.setException(e);
 49        log.warn("Error Request Info      : {}", JSON.toJSONString(requestErrorInfo));
 50    }
 51
 52    /**
 53     * 获取入参
 54     * @param proceedingJoinPoint
 55     *
 56     * @return
 57     * */
 58    private Map<String, Object> getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) {
 59        //参数名
 60        String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames();
 61        //参数值
 62        Object[] paramValues = proceedingJoinPoint.getArgs();
 63
 64        return buildRequestParam(paramNames, paramValues);
 65    }
 66
 67    private Map<String, Object> getRequestParamsByJoinPoint(JoinPoint joinPoint) {
 68        //参数名
 69        String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
 70        //参数值
 71        Object[] paramValues = joinPoint.getArgs();
 72
 73        return buildRequestParam(paramNames, paramValues);
 74    }
 75
 76    private Map<String, Object> buildRequestParam(String[] paramNames, Object[] paramValues) {
 77        Map<String, Object> requestParams = new HashMap<>();
 78        for (int i = 0; i < paramNames.length; i++) {
 79            Object value = paramValues[i];
 80
 81            //如果是文件对象
 82            if (value instanceof MultipartFile) {
 83                MultipartFile file = (MultipartFile) value;
 84                value = file.getOriginalFilename();  //获取文件名
 85            }
 86
 87            requestParams.put(paramNames[i], value);
 88        }
 89
 90        return requestParams;
 91    }
 92
 93    @Data
 94    public static class RequestInfo {
 95        private String ip;
 96        private String url;
 97        private String httpMethod;
 98        private String classMethod;
 99        private Object requestParams;
100        private Object result;
101        private Long timeCost;
102    }
103
104    @Data
105    public static class RequestErrorInfo {
106        private String ip;
107        private String url;
108        private String httpMethod;
109        private String classMethod;
110        private Object requestParams;
111        private RuntimeException exception;
112    }
113}

SpringCache

  1. 定义缓存管理器

    java
    1// 这里使用简单的本地缓存
    2@Bean("cacheManager")
    3public CacheManager cacheManager() {
    4    SimpleCacheManager cacheManager = new SimpleCacheManager();
    5    ConcurrentMapCache cache = new ConcurrentMapCache("concurrentMapCache");
    6    cacheManager.setCaches(List.of(cache));
    7    return cacheManager;
    8}
  2. 启动添加注解

    java
    1@SpringBootApplication
    2@EnableCaching
    3public class Application implements ApplicationRunner {
    4    //...............
    5}
  3. 在需要缓存的方法上使用注解开启缓存

    java
     1/*
     2* cacheManager 缓存管理器bean
     3* cacheNames 缓存的名字
     4* key 缓存的key,可自定义,默认是方法参数值 使用spEL 表达式
     5* unless 否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存 使用spEL 表达式
     6* condition 符合条件的情况下才缓存 使用spEL 表达式
     7*/
     8@Cacheable(cacheManager = "cacheManager", cacheNames = {"concurrentMapCache"}, key = "#root.methodName+'['+#paramA+#paramB+']'", unless = "#paramA=='noCache'",,condition = "#result!=null")
     9public Object cacheableTestB(String paramA, String paramB) {
    10    return paramA + paramB;
    11}

SpringCache 中 spEL 表达式常用元数据

9b44287bd9e642abfc8edcd09a51b87a

配置元数据

spring-configuration-metadata.json用于配置元数据,主要作用是当在配置文件中尝试编写配置时,IDE 可以根据这个元数据信息进行提示

配置元数据文件位于jars中 META-INF/spring-configuration-metadata.json 下。 它们使用JSON格式,项目分类为 “groups” 或 “properties”,附加值提示分类为 “hints”

Group 属性

包含在 groups 数组中的JSON对象可以包含下表中的属性。

Name类型目的
nameString该组的全名。 这个属性是强制性的。
typeString该组的数据类型的类名。 例如,如果该组是基于一个用 @ConfigurationProperties 注解的类,该属性将包含该类的完全限定名称。 如果它是基于一个 @Bean 方法,它将是该方法的返回类型。 如果类型不详,该属性可以省略。
descriptionString对该组的简短描述,可以显示给用户。 如果没有描述,可以省略。 建议描述是简短的段落,第一行提供一个简洁的摘要。 描述中的最后一行应以句号(.)结束。
sourceTypeString贡献这个组的来源的类名。 例如,如果这个组是基于一个 @Bean 方法,用 @ConfigurationProperties 注释,这个属性将包含包含该方法的 @Configuration 类的完全合格名称。 如果不知道源类型,该属性可以省略。
sourceMethodString贡献这个组的方法的全名(包括括号和参数类型)(例如,一个 @ConfigurationProperties 注释的 @Bean 方法的名称)。 如果源方法不知道,可以省略。

Property 属性

properties 数组中包含的JSON对象可以包含下表中描述的属性。

Name类型目的
nameString属性的全名。 名称采用小写的句号分隔形式(例如,server.address)。 这个属性是强制性的。
typeString该属性的数据类型的完整签名(例如,java.lang.String),但也有完整的通用类型(例如 java.util.Map<java.lang.String,com.example.MyEnum>)。 你可以使用此属性来指导用户可以输入的值的类型。 为了保持一致性,基元的类型是通过使用其包装类型来指定的(例如,boolean 变成 java.lang.Boolean)。 请注意,这个类可能是一个复杂的类型,当值被绑定时,会从 String 转换过来。 如果该类型不知道,可以省略。
descriptionString可以显示给用户的该property的简短描述。 如果没有描述,可以省略。 建议描述是简短的段落,第一行提供一个简洁的摘要。 描述中的最后一行应以句号(.)结束。
sourceTypeString贡献此属性的来源的类名。 例如,如果该属性来自于一个用 @ConfigurationProperties 注解的类,该属性将包含该类的完全限定名称。 如果源类型未知,可以省略。
defaultValueObject默认值,如果没有指定该属性,则使用该值。 如果该属性的类型是一个数组,它可以是一个数组的值。 如果默认值是未知的,它可以被省略。
deprecationDeprecation指定该属性是否被废弃。 如果该字段没有被废弃,或者不知道该信息,可以省略。 下表提供了关于 deprecation 属性的更多细节。

注解处理器(Annotation Processor)生成元数据

该处理器同时拾取带有 @ConfigurationProperties 注解的类和方法。

导入依赖

xml
1<dependency>
2    <groupId>org.springframework.boot</groupId>
3    <artifactId>spring-boot-configuration-processor</artifactId>
4    <optional>true</optional>
5</dependency>

image-20240810113259726

实例

json
 1{
 2  "groups": [
 3    {
 4      "name": "lei.security.desensitization",
 5      "type": "org.lei.ch01.config.asd.SecurityProperties",
 6      "sourceType": "org.lei.ch01.config.asd.SecurityProperties"
 7    }
 8  ],
 9  "properties": [
10    {
11      "name": "lei.security.desensitization.enabled",
12      "type": "java.lang.Boolean",
13      "description": "是否启用",
14      "sourceType": "org.lei.ch01.config.asd.SecurityProperties",
15      "defaultValue": false
16    },
17    {
18      "name": "lei.security.desensitization.fields",
19      "type": "java.util.Set<org.lei.ch01.config.asd.SecurityProperties$DesensitizationField>",
20      "description": "脱敏字段",
21      "sourceType": "org.lei.ch01.config.asd.SecurityProperties"
22    },
23    {
24      "name": "lei.security.desensitization.no-desensitization-urls",
25      "type": "java.util.Set<java.lang.String>",
26      "description": "不需要脱敏的 urls",
27      "sourceType": "org.lei.ch01.config.asd.SecurityProperties"
28    }
29  ],
30  "hints": []
31}

脱敏

脱敏的规则有很多种,例如:

  • 替换(常用):将敏感数据中的特定字符或字符序列替换为其他字符。例如,将信用卡号中的中间几位数字替换为星号(*)或其他字符。
  • 删除:将敏感数据中的部分内容随机删除。例如,将电话号码的随机 3 位数字进行删除。
  • 重排:将原始数据中的某些字符或字段的顺序打乱。例如,将身份证号码的随机位交错互换。
  • 加噪:在数据中注入一些误差或者噪音,达到对数据脱敏的效果。例如,在敏感数据中添加一些随机生成的字符。

方式1(@JsonSerialize)

利用Hutool 提供的 DesensitizedUtil脱敏工具类配合 Jackson 通过注解的方式完成数据脱敏

  1. 新建自定义序列化类DesensitizationSerialize

    java
     1
     2/**
     3 * 自定义序列化类,用于数据脱敏处理
     4 * 支持多种脱敏类型,包括自定义规则。
     5 */
     6@AllArgsConstructor
     7@NoArgsConstructor
     8public class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {
     9
    10    private DesensitizationTypeEnum type;
    11    private Integer startInclude;
    12    private Integer endExclude;
    13
    14    @Override
    15    public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    16        jsonGenerator.writeString(type.desensitize(s, startInclude, endExclude));
    17    }
    18
    19    @Override
    20    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
    21        if (beanProperty != null) {
    22            // 判断数据类型是否为String类型
    23            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
    24                // 获取定义的注解
    25                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);
    26                // 如果字段上没有注解,则从上下文中获取注解
    27                if (desensitization == null) {
    28                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);
    29                }
    30                // 如果找到了注解,创建新的序列化实例
    31                if (desensitization != null) {
    32                    return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(), desensitization.endExclude());
    33                }
    34            }
    35            // 如果不是String类型,使用默认的序列化处理
    36            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
    37        }
    38        // 如果beanProperty为null,返回默认的null值序列化处理
    39        return serializerProvider.findNullValueSerializer(null);
    40    }
    41}
  2. 新建常用的脱敏类型DesensitizationTypeEnum

    java
     1public enum DesensitizationTypeEnum {
     2    // 自定义
     3    MY_RULE {
     4        @Override
     5        public String desensitize(String str, Integer startInclude, Integer endExclude) {
     6            return StrUtil.hide(str, startInclude, endExclude);
     7        }
     8    },
     9    // 用户id
    10    USER_ID {
    11        @Override
    12        public String desensitize(String str, Integer startInclude, Integer endExclude) {
    13            return String.valueOf(DesensitizedUtil.userId());
    14        }
    15    },
    16    MOBILE_PHONE {
    17        @Override
    18        public String desensitize(String str, Integer startInclude, Integer endExclude) {
    19            return String.valueOf(DesensitizedUtil.mobilePhone(str));
    20        }
    21    },
    22    EMAIL {
    23        @Override
    24        public String desensitize(String str, Integer startInclude, Integer endExclude) {
    25            return String.valueOf(DesensitizedUtil.email(str));
    26        }
    27    },
    28    ID_CARD {
    29        @Override
    30        public String desensitize(String str, Integer startInclude, Integer endExclude) {
    31            return String.valueOf(DesensitizedUtil.idCardNum(str,4,4));
    32        }
    33    };
    34    // 省略其他枚举字段
    35    // ...
    36
    37
    38    // 使用 hutool 提供的脱敏工具对数据进行脱敏
    39    public abstract String desensitize(String str, Integer startInclude, Integer endExclude);
    40}
  3. 新建注解,用于标记需要脱敏字段Desensitization

    java
     1@Target(ElementType.FIELD)
     2@Retention(RetentionPolicy.RUNTIME)
     3@JacksonAnnotationsInside
     4// 指定序列化时使用 DesensitizationSerialize 这个自定义序列化类
     5@JsonSerialize(using = DesensitizationSerialize.class)
     6public @interface Desensitization {
     7    /**
     8     * 脱敏数据类型,在MY_RULE的时候,startInclude和endExclude生效
     9     */
    10    DesensitizationTypeEnum type() default DesensitizationTypeEnum.MY_RULE;
    11
    12    /**
    13     * 脱敏开始位置(包含)
    14     */
    15    int startInclude() default 0;
    16
    17    /**
    18     * 脱敏结束位置(不包含)
    19     */
    20    int endExclude() default 0;
    21}
  4. 在需要脱敏的实体类字段上使用该注解标注即可

    image-20240810102841864

方式2(@JsonComponent)

不是用注解,在配置文件配置需要脱敏的字段和脱敏方式

  1. 新增配置信息

    yaml
    1lei:
    2  security:
    3    desensitization:
    4      enabled: true
    5      fields:
    6        - field: email
    7          sensitive-strategy: email
    8        - field: idCard
    9          sensitive-strategy: id_card
  2. 新建配置读取文件,可以使用spring-boot-configuration-processor生成配置元数据,方便尝试编写配置时 IDE 可以提示

    java
     1@Data
     2@Component
     3@ConfigurationProperties(prefix = "lei.security.desensitization")
     4public class DesensitizationProperties {
     5
     6    /**
     7     * 是否启用
     8     */
     9    private Boolean enabled = false;
    10    /**
    11     * 不需要脱敏的 urls
    12     */
    13    private Set<String> noDesensitizationUrls = new HashSet<>();
    14    /**
    15     * 脱敏字段
    16     */
    17    private Set<DesensitizationField> fields = new HashSet<>();
    18
    19    @Data
    20    public static class DesensitizationField{
    21        private String field;
    22        private SensitiveStrategy sensitiveStrategy;
    23    }
    24}
  3. 自定义 String 类型的JsonSerializer

    java
     1@JsonComponent
     2public class DesensitizationJsonSerializer extends JsonSerializer<String> {
     3
     4    @Resource
     5    private DesensitizationProperties desensitizationProperties;
     6
     7    public DesensitizationJsonSerializer() {
     8    }
     9
    10    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    11        JsonStreamContext outputContext = gen.getOutputContext();
    12        String currentName = outputContext.getCurrentName();
    13        // 判断是否启用脱敏 及 脱敏字段是否为空
    14        if (this.desensitizationProperties.getEnabled() && !this.desensitizationProperties.getFields().isEmpty()) {
    15            Optional<DesensitizationProperties.DesensitizationField> first = this.desensitizationProperties.getFields().stream()
    16                    .filter((desensitizationFieldx) -> desensitizationFieldx.getField().equalsIgnoreCase(currentName))
    17                    .findFirst();
    18            if (first.isPresent()) {
    19                DesensitizationProperties.DesensitizationField desensitizationField = first.get();
    20                // 通过脱敏枚举类进行脱敏操作
    21                String apply = desensitizationField.getSensitiveStrategy().desensitizer().apply(value);
    22                gen.writeString(apply);
    23                return;
    24            }
    25        }
    26        gen.writeString(value);
    27    }
    28}
  4. 新建脱敏枚举类SensitiveStrategy

    java
     1public enum SensitiveStrategy {
     2    PERSON_NAME((s) -> {
     3        return s.replaceAll("(?<=^.{1}).", "*");
     4    }),
     5    ID_CARD((s) -> {
     6        return s.replaceAll("\\d{6}(\\d{8})\\w{4}", "******$1****");
     7    }),
     8    PHONE((s) -> {
     9        return s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    10    }),
    11    ADDRESS((s) -> {
    12        return s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****");
    13    }),
    14    EMAIL((s) -> {
    15        return s.replaceAll("(\\w+)\\w{3}@(\\w+)", "$1***@$2");
    16    }),
    17    BANK_CARD((s) -> {
    18        return s.replaceAll("(\\S{3})\\S*(\\S{3})", "$1******$2");
    19    });
    20
    21    private final Function<String, String> desensitizer;
    22
    23    SensitiveStrategy(Function<String, String> desensitizer) {
    24        this.desensitizer = desensitizer;
    25    }
    26
    27    public Function<String, String> desensitizer() {
    28        return this.desensitizer;
    29    }
    30}

综合案例

创建父工程

image-20210624012754252

  1. 创建 parent 工程,用于管理依赖
  2. 修改 pom.xml 中package为 pom,指定JDK版本号以及相关依赖,声明SpringBoot父依赖,因为后续子工程包含有SpringBoot工程
  3. pom.xml文件如下
xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <modelVersion>4.0.0</modelVersion>
 6
 7    <parent>
 8        <groupId>org.springframework.boot</groupId>
 9        <artifactId>spring-boot-starter-parent</artifactId>
10        <version>2.5.1</version>
11        <relativePath/> <!-- lookup parent from repository -->
12    </parent>
13
14    <groupId>com.lei</groupId>
15    <artifactId>ch11-inte-parent</artifactId>
16    <packaging>pom</packaging>
17    <version>1.0.0</version>
18
19    <properties>
20        <maven.compiler.source>11</maven.compiler.source>
21        <maven.compiler.target>11</maven.compiler.target>
22        <mybatis-spring-boot.version>2.2.0</mybatis-spring-boot.version>
23        <dubbo-spring-boot.version>2.7.8</dubbo-spring-boot.version>
24        <dubbo.version>2.7.8</dubbo.version>
25        <curator.version>4.2.0</curator.version>
26    </properties>
27    <dependencyManagement>
28        <dependencies>
29            <dependency>
30                <groupId>org.mybatis.spring.boot</groupId>
31                <artifactId>mybatis-spring-boot-starter</artifactId>
32                <version>${mybatis-spring-boot.version}</version>
33            </dependency>
34            <dependency>
35                <groupId>org.apache.dubbo</groupId>
36                <artifactId>dubbo-spring-boot-starter</artifactId>
37                <version>${dubbo-spring-boot.version}</version>
38            </dependency>
39            <!--dubbo-->
40            <dependency>
41                <groupId>org.apache.dubbo</groupId>
42                <artifactId>dubbo</artifactId>
43                <version>${dubbo.version}</version>
44            </dependency>
45            <!--zookeeper客户端-->
46            <dependency>
47                <groupId>org.apache.curator</groupId>
48                <artifactId>curator-framework</artifactId>
49                <version>${curator.version}</version>
50            </dependency>
51            <dependency>
52                <groupId>org.apache.curator</groupId>
53                <artifactId>curator-recipes</artifactId>
54                <version>${curator.version}</version>
55            </dependency>
56        </dependencies>
57    </dependencyManagement>
58</project>

接口工程

image-20210624012731571

  1. 创建接口工程 interface,包含业务接口、实体bean
  2. 声明父依赖
  3. pom.xml文件
xml
 1<?xml version="1.0" encoding="UTF-8"?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <parent>
 6        <artifactId>ch11-inte-parent</artifactId>
 7        <groupId>com.lei</groupId>
 8        <version>1.0.0</version>
 9        <relativePath>../ch11-inte-parent/pom.xml</relativePath>
10    </parent>
11    <modelVersion>4.0.0</modelVersion>
12    <artifactId>ch11-inte-interface</artifactId>
13</project>

提供者

image-20210624012846302

  1. 创建服务提供者 provider 工程

  2. pom.xml依赖,指定父工程,主要有 redis起步依赖、mybatis起步依赖、mysql驱动、dubbo、dubbo起步依赖、zookeeper客户端、接口工程

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     4    <modelVersion>4.0.0</modelVersion>
     5    <parent>
     6        <groupId>com.lei</groupId>
     7        <artifactId>ch11-inte-parent</artifactId>
     8        <version>1.0.0</version>
     9        <relativePath>../ch11-inte-parent/pom.xml</relativePath>
    10    </parent>
    11    <artifactId>ch11-inte-provider</artifactId>
    12    <properties>
    13        <java.version>11</java.version>
    14    </properties>
    15    <dependencies>
    16        <dependency>
    17            <groupId>org.springframework.boot</groupId>
    18            <artifactId>spring-boot-starter</artifactId>
    19        </dependency>
    20        <dependency>
    21            <groupId>org.mybatis.spring.boot</groupId>
    22            <artifactId>mybatis-spring-boot-starter</artifactId>
    23        </dependency>
    24        <dependency>
    25            <groupId>mysql</groupId>
    26            <artifactId>mysql-connector-java</artifactId>
    27        </dependency>
    28        <dependency>
    29            <groupId>org.springframework.boot</groupId>
    30            <artifactId>spring-boot-starter-data-redis</artifactId>
    31        </dependency>
    32        <!--SpringBoot dubbo整合起步依赖-->
    33        <dependency>
    34            <groupId>org.apache.dubbo</groupId>
    35            <artifactId>dubbo-spring-boot-starter</artifactId>
    36        </dependency>
    37        <!--dubbo-->
    38        <dependency>
    39            <groupId>org.apache.dubbo</groupId>
    40            <artifactId>dubbo</artifactId>
    41        </dependency>
    42        <!--zookeeper客户端-->
    43        <dependency>
    44            <groupId>org.apache.curator</groupId>
    45            <artifactId>curator-framework</artifactId>
    46        </dependency>
    47        <dependency>
    48            <groupId>org.apache.curator</groupId>
    49            <artifactId>curator-recipes</artifactId>
    50        </dependency>
    51        <dependency>
    52            <groupId>com.lei</groupId>
    53            <artifactId>ch11-inte-interface</artifactId>
    54            <version>1.0.0</version>
    55        </dependency>
    56    </dependencies>
    57    <build>
    58        <plugins>
    59            <plugin>
    60                <groupId>org.mybatis.generator</groupId>
    61                <artifactId>mybatis-generator-maven-plugin</artifactId>
    62                <version>1.4.0</version>
    63                <configuration>
    64                    <!--本地逆向工程配置文件位置-->
    65                    <configurationFile>generatorConfig.xml</configurationFile>
    66                    <verbose>true</verbose>
    67                    <overwrite>true</overwrite>
    68                </configuration>
    69            </plugin>
    70            <plugin>
    71                <groupId>org.springframework.boot</groupId>
    72                <artifactId>spring-boot-maven-plugin</artifactId>
    73            </plugin>
    74        </plugins>
    75    </build>
    76</project>
  3. mybatis逆向工程文件 generatorConfig.xml

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<!DOCTYPE generatorConfiguration
     3        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
     4        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
     5
     6<generatorConfiguration>
     7<!--    mysql驱动位置 -->
     8    <classPathEntry location="G:\study\soft\apache\MavenRepository\mysql\mysql-connector-java\8.0.25\mysql-connector-java-8.0.25.jar" />
     9
    10    <context id="tables" targetRuntime="MyBatis3">
    11        <!-- 关闭注释生成 -->
    12        <commentGenerator>
    13            <property name="suppressAllComments" value="true" />
    14        </commentGenerator>
    15
    16        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
    17                        connectionURL="jdbc:mysql://192.168.10.129:3306/jdbc_learn?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"
    18                        userId="jdbc_learn"
    19                        password="tWDp8b2xDFZpzF5N">
    20        </jdbcConnection>
    21
    22        <javaTypeResolver >
    23            <property name="forceBigDecimals" value="true" />
    24        </javaTypeResolver>
    25
    26        <javaModelGenerator targetPackage="com.lei.model" targetProject="G:\study\code\javaProject\SpringBoot\ch11-inte-interface\src\main\java\">
    27            <property name="enableSubPackages" value="true" />
    28            <property name="trimStrings" value="true" />
    29        </javaModelGenerator>
    30
    31        <sqlMapGenerator targetPackage="mapper"  targetProject="src/main/resources/">
    32            <property name="enableSubPackages" value="true" />
    33        </sqlMapGenerator>
    34
    35        <javaClientGenerator type="XMLMAPPER" targetPackage="com.lei.mapper"  targetProject="src/main/java/">
    36            <property name="enableSubPackages" value="true" />
    37        </javaClientGenerator>
    38
    39        <table tableName="students" domainObjectName="Students"
    40               enableCountByExample="false"
    41               enableSelectByExample="false"
    42               enableDeleteByExample="false"
    43               enableUpdateByExample="false"
    44               selectByExampleQueryId="false"
    45        />
    46    </context>
    47
    48</generatorConfiguration>
  4. SpringBoot核心配置文件

    properties
     1spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
     2spring.datasource.url=jdbc:mysql://192.168.10.129:3306/jdbc_learn?useSSL=true&useUnicode=true&characterEncoding=utf8
     3spring.datasource.username=jdbc_learn
     4spring.datasource.password=tWDp8b2xDFZpzF5N
     5
     6mybatis.mapper-locations=classpath:mapper/*.xml
     7
     8mybatis.type-aliases-package=com.lei.model
     9dubbo.application.name=provider
    10dubbo.registry.address=zookeeper://192.168.10.129
    11dubbo.registry.port=2181
    12dubbo.protocol.name=dubbo
    13dubbo.protocol.port=20880
    14dubbo.scan.base-packages=com.lei.service.impl
    15
    16spring.redis.host=192.168.10.129
    17spring.redis.port=6379
    18
    19logging.level.root=info
    20logging.level.com.lei.mapper=debug
  5. SpringBoot启动类上配置 mapper 包扫描

    java
    1@SpringBootApplication
    2@MapperScan(basePackages = "com.lei.mapper")
    3public class Ch11InteProviderApplication {
    4    public static void main(String[] args) {
    5        SpringApplication.run(Ch11InteProviderApplication.class, args);
    6    }
    7}
  6. 创建接口工程业务的实现,并使用 @DubboService 暴露服务

    java
     1@DubboService
     2public class StudentServiceImpl implements StudentService {
     3    @Autowired
     4    private StudentsMapper studentsMapper;
     5    @Autowired
     6    private RedisTemplate<Object,Object> redisTemplate;
     7    @Override
     8    public Students getById(int id) {
     9        Students students=null;
    10        students=studentsMapper.queryById(id);
    11        return students;
    12    }
    13}

消费者

image-20210624014220404

  1. 创建服务消费者 consumer,并指定父工程

  2. pom.xml 文件添加依赖,dubbo起步依赖、dubbo、zookeeper客户端、SpringBoot-web起步依赖、SpringBoot-thymeleaf起步依赖、接口工程

    xml
     1<?xml version="1.0" encoding="UTF-8"?>
     2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
     4    <modelVersion>4.0.0</modelVersion>
     5    <parent>
     6        <groupId>com.lei</groupId>
     7        <artifactId>ch11-inte-parent</artifactId>
     8        <version>1.0.0</version>
     9        <relativePath>../ch11-inte-parent/pom.xml</relativePath>
    10    </parent>
    11    <artifactId>ch11-inte-consumer</artifactId>
    12    <properties>
    13        <java.version>11</java.version>
    14    </properties>
    15    <dependencies>
    16        <dependency>
    17            <groupId>org.springframework.boot</groupId>
    18            <artifactId>spring-boot-starter-thymeleaf</artifactId>
    19        </dependency>
    20        <dependency>
    21            <groupId>org.springframework.boot</groupId>
    22            <artifactId>spring-boot-starter-web</artifactId>
    23        </dependency>
    24        <dependency>
    25            <groupId>org.apache.dubbo</groupId>
    26            <artifactId>dubbo</artifactId>
    27        </dependency>
    28        <dependency>
    29            <groupId>org.apache.dubbo</groupId>
    30            <artifactId>dubbo-spring-boot-starter</artifactId>
    31        </dependency>
    32        <dependency>
    33            <groupId>org.apache.curator</groupId>
    34            <artifactId>curator-framework</artifactId>
    35        </dependency>
    36        <dependency>
    37            <groupId>org.apache.curator</groupId>
    38            <artifactId>curator-recipes</artifactId>
    39        </dependency>
    40        <dependency>
    41            <groupId>com.lei</groupId>
    42            <artifactId>ch11-inte-interface</artifactId>
    43            <version>1.0.0</version>
    44        </dependency>
    45
    46    </dependencies>
    47
    48    <build>
    49        <plugins>
    50            <plugin>
    51                <groupId>org.springframework.boot</groupId>
    52                <artifactId>spring-boot-maven-plugin</artifactId>
    53            </plugin>
    54        </plugins>
    55    </build>
    56
    57</project>
  3. SpringBoot核心配置文件

    properties
     1spring.thymeleaf.cache=false
     2
     3dubbo.application.name=consumer
     4dubbo.registry.address=zookeeper://192.168.10.129
     5dubbo.registry.port=2181
     6dubbo.scan.base-packages=com.lei.controller
     7
     8server.servlet.encoding.enabled=false
     9server.servlet.encoding.force=true
    10server.servlet.encoding.charset=UTF-8
  4. 编写控制类,并远程注入 服务bean

    java
     1@Controller
     2public class StudentController {
     3    @DubboReference
     4    private StudentService studentService;
     5    @RequestMapping("/t1/{id}")
     6    public String getUser(@PathVariable("id") int id, Model model){
     7        Students students=studentService.getById(id);
     8        model.addAttribute("student",students);
     9        return "t1";
    10    }
    11}
  5. 编写模板文件 t1.html

    xml
     1<!DOCTYPE html>
     2<!--suppress ALL -->  <!--防止模板引擎取值时报红-->
     3<html lang="en"  xmlns:th="http://www.thymeleaf.org" >
     4<head>
     5    <meta charset="UTF-8" >
     6    <title>Title</title>
     7</head>
     8<body>
     9<h1>hello</h1>
    10<div>
    11    id:<span th:text="${student.id}+'  '"></span>
    12    name:<span th:text="${student.name}+'  '"></span>
    13    gender:<span th:if="${student.gender eq 1}"></span>
    14    gender:<span th:unless="${student.gender eq 1}"></span>
    15    score:<span th:text="${student.score}+'  '"></span>
    16</div>
    17</body>
    18</html>
使用滚轮缩放
按住拖动