SpringBoot案例 ¶
集成 ¶
整合jsp ¶
声明SpringBoot内嵌tomcat对jsp支持依赖
xml1<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
xml1<resource> 2 <directory>src/main/webapp</directory> 3 <targetPath>META-INF/resources</targetPath> 4 <includes> 5 <include>*.*</include> 6 </includes> 7</resource>在核心配置文件中配置视图解析器
properties1spring.mvc.view.prefix=/ 2spring.mvc.view.suffix=.jsp
集成mybatis(逆向工程) ¶
声明相关依赖,mysql驱动依赖、mybatis整合SpringBoot的起步依赖
xml1<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逆向工程配置文件xml1<?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&useUnicode=true&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中配置逆向工程插件
xml1<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整合
在mapper接口上使用注解 @Mapper 注解,将其注入到spring容器
或者使用 @MapperScan(“com.lei.mapper”)注解,将其注解到主配置文件上,指定需要扫描的mapper包
在 SpringBoot 核心配置文件中配置 数据库链接信息
properties1spring.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=JCRMp3LHkrSZ5y6cpom.xml配置资源文件用于编译mapper.xml文件
mapper.xml存放位置,两种
和接口放在一起,需要在pom.xml中配置资源目录
直接将mapper.xml和接口分开,将mapper.xml放在 resource目录下
在资源目录下创建mapper文件夹,将mapper.xml文件放进去
在SpringBoot核心配置文件中指定mapper所在位置
properties1mybatis.mapper-locations=classpath:mapper/*.xml不需要在pom.xml中指定资源目录
集成redis ¶
SpringBoot2.x之后,原来使用的jedis被替换成了 lettuce
区别:
- jedis:采用直连,多个线程操作的话,是不安全的,如果要避免不安全,使用jedis pool连接池,像 BIO
- lettuce:采用netty,实例可以多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据,像 NIO
添加依赖
xml1<!-- SpringBoot集成redis起步依赖 --> 2<dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-data-redis</artifactId> 5</dependency>在SpringBoot核心配置文件中添加redis配置
properties1spring.redis.host=192.168.10.129 2spring.redis.port=6379 3spring.redis.password=123456redis起步依赖提供了一个操作redis的对象,注入操作redis的对象
java1@Autowired 2private RedisTemplate<Object,Object> redisTemplate;事务操作的实现,通过SessionCallback/RedisCallback;默认情况下:redisTemplate每次操作都是采用新的连接执行,而
redisTemplate.multi()是无效的(因为连接池连接默认自动提交),手动提交时会报错ERR EXEC without MULTISessionCallback实现事务操作
java1@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实现事务操作
java1public 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,注册中心,接口工程
- 配置服务消费者核心配置文件
集成步骤
创建接口工程(maven项目)、创建服务提供者(SpringBoot项目)、创建服务消费者(SpringBoot项目)
服务提供者
添加依赖
xml1<!--dubbo集成SpringBoot起步依赖--> 2dubbo-spring-boot-starter 3<!--注册中心依赖--> 4zkclient 5<!--接口工程-->核心配置文件中配置
properties1#内嵌tomcat端口号 2#设置上下文根 3#声明当前工程名字 4spring.application.name= 5 6#设置dubbo配置,注意,没有提示 7#声明当前工程是一个服务提供者工程 8spring.dubbo.server=true 9#指定注册中心 10spring.dubbo.registry=zookeeper://ip:2181
服务消费者
添加依赖
xml1<!--dubbo集成SpringBoot起步依赖--> 2dubbo-spring-boot-starter 3<!--注册中心依赖--> 4zkclient 5<!--接口工程-->核心配置文件中配置
properties1#内嵌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相关配置
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
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>- 日志级别从低到高:TRACE < DEBUG <WARN < ERROR < FATAL,设置了日志级别后,低于该级别的信息都不会输出
- scan:此属性为 true 时,配置文件如果发生改变,将会重新加载,默认为 true
- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间间隔,默认为毫秒。当 scan 为 true 时,此属性生效。默认时间间隔为 1 分钟
- debug :当此属性设置为true时,控制台将打印出 logback 内部日志信息,实时查看。默认值 false,通常不打印
使用swagger和lombok ¶
OAS规范,Linux基金会的一个项目,试图定义一种用来描述API格式或API定义语言,来规范RESTFUL服务开发过程
Swagger是目前最受欢迎的 OAS 规范开发工具框架;随着项目自动生成API文档(通过一些注解)
Swagger使用
pom.xml 添加起步依赖
xml1<!--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.x的老版本swagger除了需要导入相关依赖,还要编写一个配置类
java1@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}编写控制类,并使用相关注解说明,以便生成接口文档
java1@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}实体类相关注解
java1@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}浏览器访问
- 3.0访问地址:http://localhost:8080/swagger-ui/index.html#/
- 2.x访问地址:http://localhost:8080/swagger-ui.html
SpringBoot核心配置文件关闭swagger-ui访问
properties1springfox.documentation.swagger-ui.enabled=false
使用jasypt加密敏感信息 ¶
导入依赖
xml1<dependency> 2 <groupId>com.github.ulisesbocchio</groupId> 3 <artifactId>jasypt-spring-boot-starter</artifactId> 4 <version>3.0.3</version> 5</dependency>SpringBoot核心配置文件配置加密解密的 key
properties1jasypt.encryptor.password=sdjhauidasdw注入 jasypt bean,并使用加密解密功能生成 密文
java1package 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*/在SpringBoot核心配置文件配置;EVC(密文) 为默认识别密文的格式,可以通过配置文件修改
properties1jasypt.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 ¶
导入依赖
xml1<dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>fastjson</artifactId> 4 <version>2.0.7</version> 5 </dependency>配置
HttpMessageConvertersBeanjava1@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}查看
java1/* 2* AbstractMessageConverterMethodProcessor类 3* writeWithMessageConverters 写入响应消息的方法 4* genericConverter.write(body, targetType, selectedMediaType, outputMessage) 5* 可以查看消息转换器,fastjson在列表第一个 6* */
集成Thymeleaf ¶
- Thymeleaf 是一个流行的 java 模板引擎,以 html 为载体
- Thymeleaf模板引擎页面访问,必须经过中央调度器(不能直接访问,类似于页面放在 WEB-INF 目录下)
- http://www.thymeleaf.org
Thymeleaf起步
添加thymeleaf起步依赖
xml1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-thymeleaf</artifactId> 4</dependency>在 templates 目录下创建模板文件 hello.html,并引入命名空间
html1<!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>SpringBoot核心配置文件配置关闭缓存,方便开发,启动类需要设置动态更新资源
properties1spring.thymeleaf.cache=false 2spring.thymeleaf.prefix=classpath:templates/ 3spring.thymeleaf.suffix=.html
Thymeleaf表达式
${}:标准表达式,和el表达式一致,推荐使用*{}:选择变量表达式,必须使用 th:object 属性来绑定这个对象@{}:路径表达式,
Thymeleaf常见属性
- 标准表达式
th:textth:methodth:actionth:idth:nameth:onclick
th:each="user,userStat:${userList}":遍历,依赖于一个html元素- 条件判断
th:if="${sex} eq 1:满足条件显示th:unless="${sex} eq 1":满足条件不显示,和 th:if 相反th:switch/th:case:选择,满足显示,否则不显示
th:inline="":内敛表达式,有三个值- text:然后在修饰标签里面,可以通过 [[${}]] 取值,而不需要 th:text="${}" 取值
- javascript:可以在 js 中直接取得后台返回过来值,通过 [[${}]]
- 字面量:字符串的意思,分为字符字面量、数字字面量、boolean字面量
- 字符串拼接
th:text="|这里为需要拼接的${data}字符串|"
- 数学运算
后台发送过来的值时,才使用 Thymeleaf 属性
Thymeleaf对象
基本表达式对象
- #request
- #session
功能表达式对象
#dates:java.util.Date 对象的实用方法#calendars:和dates类似,但是是java.util.Calendar 对象#numbers:格式化数字对象的实用方法#strings:字符串对象的实用方法#objects:对 objects 操作的实用方法#arrays:数组的实用方法#bools:对布尔值求值的实用方法#lists:list 的实用方法,#sets:set 的实用方法#maps:map 的实用方法#aggregates:对数组或集合创建聚合的实用方法
实例
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")
配置文件
properties1my.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配置数据源相关
db1数据源
xml1<?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数据源
xml1<?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>启动类配置相关注解
java1@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,此时可以通过数据源配置文件导入先后顺序来控制
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
通过
PlatformTransactionManager实现编程式事务java1@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}通过
transactionTemplate实现编程式事务java1@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值即可开启自动提交功能,没有提供同时设置Connection和autoCommit的方法,这是因为 MyBatis 会依据传入的 Connection 来决定是否启用 autoCommit
SqlSessionTemplate
SqlSessionTemplate 是 MyBatis-Spring 的核心,作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession,SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用,SqlSessionTemplate 将会保证使用的 SqlSession 与当前 Spring 的事务相关
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
- 当里层事务抛错,标记为回滚,但是错误被捕获,外层未抛错出来,此时里层缺已经标记为只能rollbackOnly,外层提交事务就会抛错rollback-only
- 当外层为只读事务时,里层事务抛错,此时只读事务提交也会抛错rollback-only
业务代码执行时,记录错误
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("")注解
controller代码
java1@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}访问地址:127.0.0.1:8080/test/123/唐磊
注意请求路径不清楚的情况,restful风格,通常使用对应的方法请求mapping注解,例:
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}修改为以下代码无误,然后通过对应的请求方式发送请求
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程序,有两种方法
在SpringBoot启动方法中获取 ConfigurableApplicationContext
java1//获得容器,然后取得bean并执行 2ConfigurableApplicationContext context=SpringApplication.run(Application.class, args);在SpringBoot启动类实现CommandLineRunner接口,并重写run方法
java1//实现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 ¶
实现
HandlerInterceptor接口并实现相关方法java1@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}注册
WebMvcConfigurer bean,添加拦截器到SpringBoot容器,并配置需要拦截的请求java1@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}编写测试类
java1@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
方式一:注解方式实现
@WebServlet@ServletComponentScanservlet编写
java1//@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}SpringBoot启动类配置servlet扫描
java1@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}
方式二:配置类注册组件实现
编写servlet
java1public 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}编写配置类,并注册servlet
java1@Configuration 2public class MyConfig { 3 @Bean 4 public ServletRegistrationBean<HttpServlet> registrationBean(){ 5 return new ServletRegistrationBean<>(new TwoServlet(),"/twoServlet"); 6 } 7}
过滤器 @WebListenner
过滤器属于servlet的,声明过滤器,注册到SpringBoot容器中
SpringBoot有两种实现方式:
方式一:注解方式
@WebFilter、@创建过滤器,实现 servlet 下的 Filter 接口,并重写方法
java1//定义该类为一个过滤器 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}SpringBoot启动类,配置启动servlet扫描
java1@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}
方式二:配置类注册组件方式
创建过滤器,实现 servlet 下的 Filter 接口,并重写方法
java1public 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}编写配置类,注册过滤器到SpringBoot
java1@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包装类处理空字符
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}创建过滤器
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
properties1server.servlet.encoding.enabled=true 2server.servlet.encoding.force=true 3server.servlet.encoding.charset=utf-8设置浏览器编码
java1@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自带编码过滤器注册
java1@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自带编码
properties1server.servlet.encoding.enabled=false设置浏览器编码
java1@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
修改pom.xml
- 修改打包形式
- 移除内嵌tomcat
- 添加servlet依赖
- 指定web资源文件夹,指定配置资源文件夹
配置类继承 SpringBootServletInitializer 并重写 configure 方法
java1@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配置为主
热部署插件 ¶
- JRebel插件 或者 devtools 或者 启动配置更新 resource 和 class
- 代码修改后 build一下
数据库连接池 ¶
SpringBoot2.x后默认使用 HikariCP 作为数据源,因此使用其他数据源需要进行配置
pom.xml 排除默认的 HikariCP 数据库连接池,并添加 数据库连接池
xml1<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>SpringBoot核心配置文件指定数据源
properties1#指定数据源 2spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
也可以直接使用起步依赖,不需要在SpringBoot核心配置文件中指定数据源,并且可以在SpringBoot核心配置文件中配置该连接池相关参数
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都相同,则视为同源
使用
CorsRegistry,注册 WebMvcConfigurer bean,在该bean中配置可以跨域访问的请求java1@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}创建配置类,实现接口WebMvcConfigurer ,并重写 addCorsMappings() 方法
java1@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}使用注解
@CrossOrigin(origins = "*",methods = {RequestMethod.GET,RequestMethod.POST})使用jsonp方式,只支持get请求,局限性大,但是支持老式浏览器
静态资源的处理 ¶
SpringBoot默认静态资源位于 classpath 目录下,且满足如下命名
/static/public/resources/META-INF/resources
以上目录编译后在classpath下,可以直接访问,不需要加目录名
可以通过资源类查看
1private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};可以通过核心配置文件修改默认的静态资源放置位置,修改后原先默认的失效
1spring.web.resources.static-locations=classpath:/mystatic/线程池异步调用 ¶
SpringBoot框架自动装配提供了一个线程池,用于异步提交任务
有了线程池,只需要编写任务即可,采用注解@Async提交任务
SpringBoot自动装配的线程池还可以根据需要配置
创建配置类,注入线程池 bean,覆盖默认的线程池,会覆盖SpringBoot创建的默认线程池
java1@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}SpringBoot核心配置文件也可以配置线程池
properties1spring.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实例一:通过
@Async注解,需要在启动类开启异步执行@EnableAsync,@Async标注的方法会放入线程池中执行(通过AOP方式)java1@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}实例二:通过注入的线程池执行对象
java1@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服务器,导入其他服务器的起步依赖
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支持 ¶
生成证书,JDK生成(也可以采用openssl生成)
bash1#生成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.jksSpringBoot核心配置文件配置
properties1#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浏览器访问:https://127.0.0.1:8088/web/t1
HTTPS和HTTP不能同时配置,可以通过配置一个HTTPS,编写类实现一个HTTP
HTTPS和HTTP共存
方法一
核心配置文件中启动 HTTPS支持
在配置类中注册 servletContainer bean
java1@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
全局异常拦截 ¶
方式一:
创建一个拦截异常的控制器
java1//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}此时当控制器出现异常时,会返回指定信息;也可以配置为返回指定错误页,不能多次定义一个异常
方式二
注册一个 WebServerFactory Bean,并重写其中的 factory方法,会拦截SpringBoot的错误响应,并根据状态码返回不同的页面
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}注意:错误页放在以下目录中都可:
1[Classpath [META-INF/resources/], Classpath [resources/], Classpath [static/], Classpath [public/], ServletContext [/]]方式三
在控制器中创建@ExceptionHandler注解标记的方法,该异常处理方法没有固定的方法签名,可以传入Exception、HttpServletRequest等,返回值可以是void,也可以是ModelAndView等
ControllerA定义的ExceptionHandler对ControllerB不生效
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}统一切面日志打印 ¶
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 ¶
定义缓存管理器
java1// 这里使用简单的本地缓存 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}启动添加注解
java1@SpringBootApplication 2@EnableCaching 3public class Application implements ApplicationRunner { 4 //............... 5}在需要缓存的方法上使用注解开启缓存
java1/* 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 表达式常用元数据

配置元数据 ¶
spring-configuration-metadata.json用于配置元数据,主要作用是当在配置文件中尝试编写配置时,IDE 可以根据这个元数据信息进行提示
配置元数据文件位于jars中 META-INF/spring-configuration-metadata.json 下。 它们使用JSON格式,项目分类为 “groups” 或 “properties”,附加值提示分类为 “hints”
Group 属性 ¶
包含在 groups 数组中的JSON对象可以包含下表中的属性。
| Name | 类型 | 目的 |
|---|---|---|
name | String | 该组的全名。 这个属性是强制性的。 |
type | String | 该组的数据类型的类名。 例如,如果该组是基于一个用 @ConfigurationProperties 注解的类,该属性将包含该类的完全限定名称。 如果它是基于一个 @Bean 方法,它将是该方法的返回类型。 如果类型不详,该属性可以省略。 |
description | String | 对该组的简短描述,可以显示给用户。 如果没有描述,可以省略。 建议描述是简短的段落,第一行提供一个简洁的摘要。 描述中的最后一行应以句号(.)结束。 |
sourceType | String | 贡献这个组的来源的类名。 例如,如果这个组是基于一个 @Bean 方法,用 @ConfigurationProperties 注释,这个属性将包含包含该方法的 @Configuration 类的完全合格名称。 如果不知道源类型,该属性可以省略。 |
sourceMethod | String | 贡献这个组的方法的全名(包括括号和参数类型)(例如,一个 @ConfigurationProperties 注释的 @Bean 方法的名称)。 如果源方法不知道,可以省略。 |
Property 属性 ¶
properties 数组中包含的JSON对象可以包含下表中描述的属性。
| Name | 类型 | 目的 |
|---|---|---|
name | String | 属性的全名。 名称采用小写的句号分隔形式(例如,server.address)。 这个属性是强制性的。 |
type | String | 该属性的数据类型的完整签名(例如,java.lang.String),但也有完整的通用类型(例如 java.util.Map<java.lang.String,com.example.MyEnum>)。 你可以使用此属性来指导用户可以输入的值的类型。 为了保持一致性,基元的类型是通过使用其包装类型来指定的(例如,boolean 变成 java.lang.Boolean)。 请注意,这个类可能是一个复杂的类型,当值被绑定时,会从 String 转换过来。 如果该类型不知道,可以省略。 |
description | String | 可以显示给用户的该property的简短描述。 如果没有描述,可以省略。 建议描述是简短的段落,第一行提供一个简洁的摘要。 描述中的最后一行应以句号(.)结束。 |
sourceType | String | 贡献此属性的来源的类名。 例如,如果该属性来自于一个用 @ConfigurationProperties 注解的类,该属性将包含该类的完全限定名称。 如果源类型未知,可以省略。 |
defaultValue | Object | 默认值,如果没有指定该属性,则使用该值。 如果该属性的类型是一个数组,它可以是一个数组的值。 如果默认值是未知的,它可以被省略。 |
deprecation | Deprecation | 指定该属性是否被废弃。 如果该字段没有被废弃,或者不知道该信息,可以省略。 下表提供了关于 deprecation 属性的更多细节。 |
注解处理器(Annotation Processor)生成元数据 ¶
该处理器同时拾取带有 @ConfigurationProperties 注解的类和方法。
导入依赖
1<dependency>
2 <groupId>org.springframework.boot</groupId>
3 <artifactId>spring-boot-configuration-processor</artifactId>
4 <optional>true</optional>
5</dependency>
实例 ¶
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 通过注解的方式完成数据脱敏
新建自定义序列化类
DesensitizationSerializejava1 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}新建常用的脱敏类型
DesensitizationTypeEnumjava1public 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}新建注解,用于标记需要脱敏字段
Desensitizationjava1@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}在需要脱敏的实体类字段上使用该注解标注即可

方式2(@JsonComponent) ¶
不是用注解,在配置文件配置需要脱敏的字段和脱敏方式
新增配置信息
yaml1lei: 2 security: 3 desensitization: 4 enabled: true 5 fields: 6 - field: email 7 sensitive-strategy: email 8 - field: idCard 9 sensitive-strategy: id_card新建配置读取文件,可以使用
spring-boot-configuration-processor生成配置元数据,方便尝试编写配置时 IDE 可以提示java1@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}自定义 String 类型的
JsonSerializerjava1@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}新建脱敏枚举类
SensitiveStrategyjava1public 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}
综合案例 ¶
创建父工程 ¶

- 创建 parent 工程,用于管理依赖
- 修改 pom.xml 中package为 pom,指定JDK版本号以及相关依赖,声明SpringBoot父依赖,因为后续子工程包含有SpringBoot工程
- pom.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>接口工程 ¶

- 创建接口工程 interface,包含业务接口、实体bean
- 声明父依赖
- pom.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>提供者 ¶

创建服务提供者 provider 工程
pom.xml依赖,指定父工程,主要有 redis起步依赖、mybatis起步依赖、mysql驱动、dubbo、dubbo起步依赖、zookeeper客户端、接口工程
xml1<?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>mybatis逆向工程文件 generatorConfig.xml
xml1<?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&useUnicode=true&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>SpringBoot核心配置文件
properties1spring.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=debugSpringBoot启动类上配置 mapper 包扫描
java1@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}创建接口工程业务的实现,并使用
@DubboService暴露服务java1@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}
消费者 ¶

创建服务消费者 consumer,并指定父工程
pom.xml 文件添加依赖,dubbo起步依赖、dubbo、zookeeper客户端、SpringBoot-web起步依赖、SpringBoot-thymeleaf起步依赖、接口工程
xml1<?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>SpringBoot核心配置文件
properties1spring.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编写控制类,并远程注入 服务bean
java1@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}编写模板文件 t1.html
xml1<!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>