SpringBoot高级技术


文章目录

  • SpringBoot高级技术
  • 1、缓存
  • Spring缓存
  • 缓存注解
  • 注解
  • @Cacheable/@CachePut/@CacheEvict 主要的属性
  • @EnableCaching
  • @Cacheable
  • @CachePut
  • @CacheEvict
  • @Caching
  • @CacheConfig()
  • 步骤
  • Redis缓存
  • 使用RestTemplate操作redis
  • 步骤
  • 2、消息(RabbitMQ)
  • 概念
  • RabbitMQ整合使用
  • 第一步:引入配置
  • 第二步:在 application.properties 中配置环境
  • 第三步:创建 Exchange,Queue 和 绑定
  • 在网页上创建
  • 在类中配置(AmqpAdmin:管理组件)
  • 第四步:使用
  • 消息监听
  • 发送、接收消息(RabbitTemplate:消息发送处理组件)
  • 3、异步、定时、邮件任务
  • 异步任务
  • 概念
  • 步骤
  • 定时任务
  • 概念
  • 步骤
  • 邮箱任务
  • 4、安全(Spring Security)
  • 概述
  • 使用
  • 第一步:引入依赖
  • 第二步:创建Spring Security配置
  • 第三步:登陆/注销
  • 第四步:Thymeleaf提供的SpringSecurity标签使用
  • 5、开发热部署
  • 方法一:模板引擎
  • 方法二:Spring Boot Devtools(推荐)


1、缓存

Spring缓存

缓存注解

作用

Cache

缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等

CacheManager

缓存管理器,管理各种缓存(Cache)组件

@EnableCaching

开启基于注解的缓存

@Cacheable

主要针对方法配置,能够根据方法的请求参数对其结果进行缓存

@CacheEvict

清空缓存

@CachePut

保证方法被调用,又希望结果被缓存

keyGenerator

缓存数据时key生成策略

serialize

缓存数据时value序列化策略

注解

@Cacheable/@CachePut/@CacheEvict 主要的属性

属性

作用

示例

value

缓存的名称,在 spring 配置文件中定义,必须指定至少一个

@Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}

key

缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合

@Cacheable(value=”testcache”,key=”#userName”)

condition

缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断

@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

allEntries

(@CacheEvict )

是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存

@CachEvict(value=”testcache”,allEntries=true)

beforeInvocation

(@CacheEvict)

是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存

@CachEvict(value=”testcache”,beforeInvocation=true)

unless

(@CachePut)

(@Cacheable)

用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存

@Cacheable(value=”testcache”,unless=”#result == null”)

@EnableCaching
  • 开启基于注解的缓存
@Cacheable
  • 可缓存的
  • 运行时机
  • 先查找缓存中是否有,若有,则使用,若没有,则运行方法并将结果缓存
  • 属性
  • value:指定缓存组件的名字,将方法的返回结果放在那个缓存中,是数组的方式,至少指定一个
  • cacheNames:相当于 value 属性
  • key:缓存数据使用的key,可以用它来指定
  • 默认是使用方法参数的值 方法的返回值
  • 编写SpEL:

名字

位置

描述

示例

methodName

root object

当前被调用的方法名

#root.methodName

method

root object

当前被调用的方法

#root.method.name

target

root object

当前被调用的目标对象

#root.target

targetClass

root object

当前被调用的目标对象类

#root.targetClass

args

root object

当前被调用的方法的参数列表

#root.args[0]

caches

root object

当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache

#root.caches[0].name

argument name

evaluation context

方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引;

#iban 、 #a0 、 #p0

result

evaluation context

方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false)

#result

  • 例如:
  • #参数值:参数id的值 等同于: #a0 #p0 #root.args[0]
  • keyGenerator
  • key的生成器:可以自己指定key的生成器的组件id
    自己写一个 keyGenerator 方法,添加到配置中 @Configuration,并加在组件中 @Bean
  • key/keyGenerator:二选一使用
  • cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
  • condition:指定符合条件的情况下才缓存
  • 例如
  • condition = “#uerId>0” :userId 大于零才缓存
  • unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
  • 例如:
  • unless = “#result == null” :当 result 为空不缓存
  • sync:是否使用异步模式
@CachePut
  • 更新缓存
  • 运行时机
  • 先运行方法,再将运行结果缓存
  • 属性:跟 @Cacheable 差不多
@CacheEvict
  • 清空缓存
  • 属性
  • @Cacheable 中所有属性
  • allEntries
  • allEntries=true :清除缓存中的所有数据
  • beforeInvocation
  • beforeInvocation=false :缓存的清除是否在方法之前执行,默认是方法之后执行
@Caching
  • 多个缓存规则
  • 可在 @Caching 使用 @Cacheable,@CachePut,@CacheEvict 中的一个或多个注解
@Caching(
    cacheable = {@Cacheable(value = "", key = "", ...)},
    put = {@CachePut(value = "", key = "", ...)},
    evict = {@CacheEvict(value = "", key = "", ...)}
)
public void delete(int userId) {
    userDaoCacheMyBatis.deleteMyBatis(userId);
}
@CacheConfig()
  • 缓存配置,在类上配置,表示该类的所有方法的属性为 @CacheConfig 中的属性值
  • 属性
  • 属性有四个,作用跟 @Cacheable 一样,可以用一个或多个属性
  • cacheNames
  • keyGenerator
  • cacheManager
  • cacheResolver
@CacheConfig(cacheNames = "", keyGenerator = "", cacheManager = "", cacheResolver = "")
public class UserServiceCacheMyBatisImpl {
}

步骤

第一步:开启注解缓存

  • 在 SpringbootinitializrApplication.java 中 添加 @EnableCaching
@SpringBootApplication 
@EnableCaching  //开启缓存注解功能
public class SpringbootinitializrApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootinitializrApplication.class, args);
    }
}

第二步:标注缓存注解

  • 在需要缓存的地方使用注解 @Cacheable/@CachePut/@CacheEvict/@Caching/@CacheConfig 并添加相应的属性
@CacheConfig(cacheNames = "user") //@Cacheable/@CachePut/@CacheEvict 注解
@Service
public class UserServiceCacheMyBatisImpl {

    @Autowired
    private IUserDaoCacheMyBatis userDaoCacheMyBatis;

    /**
     * 根据 Id 查询用户信息
     * @Cacheable : 可缓存的
     */
    @Cacheable(value = "user")
    public User findById(int userId) {
        return userDaoCacheMyBatis.findByIdMyBatis(userId);
    }

    /**
     * 修改信息
     * @CachePut :更新缓存
     */
    @CachePut(value = "user", key = "#user.userId")
    public User update(User user) {
        userDaoCacheMyBatis.updateMyBatis(user);
        return user;
    }

    /**
     * 删除信息
     * @CacheEvict : 清除缓存
     */
    @CacheEvict(value = "user", key = "#userId")
    public void delete(int userId) {
        userDaoCacheMyBatis.deleteMyBatis(userId);
    }
}

Redis缓存

redis命令: http://www.redis.cn/commands.html#list

使用RestTemplate操作redis

//redis 操作
@Autowired
StringRedisTemplate stringRedisTemplate;

@Autowired
RedisTemplate redisTemplate;

@Test
public void redisTest(){
    stringRedisTemplate.opsForValue();//操作字符串
    stringRedisTemplate.opsForHash();//操作hash
    stringRedisTemplate.opsForList();//操作list
    stringRedisTemplate.opsForSet();//操作set
    stringRedisTemplate.opsForZSet();//操作有序set

    redisTemplate.opsForValue();//操作字符串
    redisTemplate.opsForHash();//操作hash
    redisTemplate.opsForList();//操作list
    redisTemplate.opsForSet();//操作set
    redisTemplate.opsForZSet();//操作有序set
}

步骤

第一步:引入spring-boot-starter-data-redis

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

第二步:application.properties 配置redis连接地址

spring.redis.host=ip地址

第三步:与 Spring 缓存的步骤一样

2、消息(RabbitMQ)

概念

  • Message
  • 消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
  • Publisher
  • 消息的生产者,也是一个向交换器发布消息的客户端应用程序。
  • Exchange
  • 交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
    Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
  • Queue
  • 消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
  • Binding
  • 绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
  • Exchange 和Queue的绑定可以是多对多的关系。
  • Connection
  • 网络连接,比如一个TCP连接。
  • Channel
  • 信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
  • Consumer
  • 消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
  • Virtual Host
  • 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
  • Broker
  • 表示消息队列服务器实体

RabbitMQ整合使用

第一步:引入配置

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

第二步:在 application.properties 中配置环境

spring.rabbitmq.host=116.62.20.166
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

第三步:创建 Exchange,Queue 和 绑定

在网页上创建
  • IP:15672 登录,创建
在类中配置(AmqpAdmin:管理组件)
@Autowired
AmqpAdmin amqpAdmin;  //自动导入配置

@Test
public void creatExchange(){
    //创建 Exchange
    amqpAdmin.declareExchange(new DirectExchange("fzk.direct"));  //创建类型为 direct 的 Exchange
    amqpAdmin.declareExchange(new FanoutExchange("fzk.fanout"));  //创建类型为 fanout 的 Exchange
    amqpAdmin.declareExchange(new TopicExchange("fzk.topic"));    //创建类型为 topic 的 Exchange
    
    //创建 Queue
    amqpAdmin.declareQueue(new Queue("fzk.queue"));
    
    //绑定 Exchange,Queue
    //"fzk.queue" :需要绑定的队列(queue)名称
    //Binding.DestinationType.QUEUE :绑定的类型
    //"fzk.direct" :绑定到 Exchange 为 fzk.direct 中
    //"fzk.queue" :Routing key:路由键
    amqpAdmin.declareBinding(new Binding("fzk.queue", Binding.DestinationType.QUEUE, "fzk.direct", "fzk.queue", null));
}

第四步:使用

消息监听

在主类中开启消息注解功能

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

编写消息监听类

@Service 
public class RabbitMQListener {
    //消息监听注解
    //queues = "fzk" :监听消息队列名称为 fzk 的队列(类型为数组:可监听多可消息队列) 
    @RabbitListener(queues = {"fzk"}) 
    /**
     * 监听消息方法
     * @param object  接收到的消息
     */
    public void rabbitMQListener(String  str){
        System.out.println("收到消息");
        System.out.println(str);
    }
}
发送、接收消息(RabbitTemplate:消息发送处理组件)
@Autowired
RabbitTemplate rabbitTemplate;

//发送消息
@Test
public void sendTest(){
    //"itfzk.direct" :Exchanges:交换器
    //"fzk" :Routing key:路由键
    //"hello fzk" :发送的内容
    rabbitTemplate.convertAndSend("itfzk.direct", "fzk", "hello fzk");
}

//接收消息
@Test
public void receiveTest(){
    //"fzk"  :Routing key:路由键
    Object fzk = rabbitTemplate.receiveAndConvert("fzk");
    System.out.println(fzk);
}

3、异步、定时、邮件任务

异步任务

概念

  • 当有 Thread.sleep(3000) 时,若使用同步需要等待 3s,使用异步就可以不用等待 3s
  • 在Java应用中,绝大多数情况下都是通过同步的方式来实现交互处理的;但是在处理与第三方系统交互的时候,容易造成响应迟缓的情况,之前大部分都是使用多线程来完成此类任务
  • 两个注解
  • @EnableAysnc、@Aysnc

步骤

第一步:开启异步注解功能

  • 在主类上使用 @EnableAysnc 注解
@EnableAsync  //开启异步注解功能
@SpringBootApplication
public class SpringbootTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootTestApplication.class, args);
    }
}

第二步:编写异步类

  • 在需要异步的方法前使用 @Async 注解
@Service
public class AsyncServiceImpl {
    @Async  //告诉 Spring 这是个异步方法
    public void async(){
        //模拟网络延迟
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //三秒后执行
        System.out.println("fzk");
    }
}

定时任务

概念

  • 项目开发中经常需要执行一些定时任务,比如需要在每天凌晨时候,分析一次前一天的日志信息。Spring为我们提供了异步执行任务调度的方式,提供TaskExecutor 、TaskScheduler 接口。
  • 两个注解
  • @EnableScheduling :开启定时注解功能
  • @Scheduled :定时
  • 属性:cron
  • “* * * * * *” :second, minute, hour, day of month, month, day of week
  • “0 * * * * MON-FRI” 表示:星期一到星期五秒为 0 时启动一次

字段

允许值

允许的特殊字符


0-59

, - * /


0-59

, - * /

小时

0-23

, - * /

日期

1-31

, - * ? / L W C

月份

1-12

, - * /

星期

0-7或SUN-SAT 0,7是SUN

, - * ? / L C #

特殊字符

代表含义

,

枚举

-

区间

*

任意

/

步长

?

日/星期冲突匹配

L

最后

W

工作日

C

和calendar联系后计算过的值

#

星期,4#2,第2个星期四

步骤

第一步:开启定时注解功能

  • 在主类上使用 @EnableScheduling 注解
@EnableScheduling  //开启定时注解功能
@SpringBootApplication
public class SpringbootTestApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootTestApplication.class, args);
    }
}

第二步:编写异步类

  • 在需要定时的方法前使用 @Scheduled 注解
@Service
public class SchedulingServiceImpl {
    //second, minute, hour, day of month, month, day of week
    @Scheduled(cron = "0 * * * * *")  //定时,每当秒为 0 时运行一次
    public void acheduling(){
        System.out.println("hello fzk");
    }
}

邮箱任务

第一步:引入配置

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

第二步:配置 mail

spring.mail.username=QQ邮箱  //QQ邮箱
spring.mail.password=授权码  //授权码
spring.mail.host=smtp.qq.com  //QQ邮箱地址

第三步:编写邮件发送

  • 发送简单邮件
@Autowired
JavaMailSenderImpl javaMailSender;

@Test
public void mailTest(){
    //发送简单的邮件
    SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
    simpleMailMessage.setSubject("通知学习");  //标题
    simpleMailMessage.setText("每天学习");  //内容
    simpleMailMessage.setTo("15727229185@163.com");  //发给谁
    simpleMailMessage.setFrom("1820713352@qq.com"); //谁发的
    javaMailSender.send(simpleMailMessage);
}
  • 发送复杂邮件
@Autowired
JavaMailSenderImpl javaMailSender;

@Test
public void mailTest(){
    //发送复杂的邮件(图片,文件。。。)
    MimeMessage mimeMessage = javaMailSender.createMimeMessage();
    MimeMessageHelper mimeMessageHelper = null;
    try {
        mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
        mimeMessageHelper.setSubject("通知学习");  //标题
        mimeMessageHelper.setText("每天学习");  //内容
        mimeMessageHelper.addAttachment("1.jpg", new File("C:\\Users\\dell\\Pictures\\景色\\1.jpg"));  //发送文件(图片)
        mimeMessageHelper.setTo("15727229185@163.com");  //发给谁
        mimeMessageHelper.setFrom("1820713352@qq.com"); //谁发的
    } catch (MessagingException e) {
        e.printStackTrace();
    }
    javaMailSender.send(mimeMessage);
}

4、安全(Spring Security)

SpringBoot和Spring Security整合

概述

  • Spring Security是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型。他可以实现强大的web安全控制。对于安全控制,我们仅需引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理
  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
  • 应用程序的两个主要区域是“认证”和“授权”
  • “认证”(Authentication),是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。
  • “授权”(Authorization)指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。
  • Thymeleaf提供的SpringSecurity标签支持
  • sec:authorize=“isAuthenticated()” : 当前用户已通过身份验证
  • sec:authentication=“name” :当前用户的用户名
  • sec:authentication=“principal.authorities” : 当前用户的权限
  • sec:authorize=“hasRole(‘ADMIN’)” :当前用户是否拥有ADMIN权限

使用

第一步:引入依赖

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

第二步:创建Spring Security配置

@EnableWebSecurity  //开启WebSecurity模式
public class MySecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll();  //如何权限都能访问
        http.authorizeRequests().antMatchers("/vip1/**").hasRole("VIP1");  //只有 VIP1 权限才能访问
        http.authorizeRequests().antMatchers("/vip2/**").hasRole("VIP2");  //只有 VIP2 权限才能访问
        http.authorizeRequests().antMatchers("/vip3/**").hasRole("VIP3");  //只有 VIP3 权限才能访问
    }

    //授权
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //该用户权限为 VIP1,VIP2
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("fff")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2");
        //该用户权限为 VIP1,VIP3
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("zzz")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP3");
        //该用户权限为 VIP3,VIP2
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("kkk")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP3", "VIP2");

    }
}

第三步:登陆/注销

@EnableWebSecurity
public class MySecurity extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll();
        http.authorizeRequests().antMatchers("/vip1/**").hasRole("VIP1");
        http.authorizeRequests().antMatchers("/vip2/**").hasRole("VIP2");
        http.authorizeRequests().antMatchers("/vip3/**").hasRole("VIP3");
        
        http.formLogin();  //登录验证
        http.logout().logoutSuccessUrl("/");  //退出登录
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("fff")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP2");
        
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("zzz")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP1", "VIP3");
        
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("kkk")
                .password(new BCryptPasswordEncoder().encode("123")).roles("VIP3", "VIP2");

    }
}

第四步:Thymeleaf提供的SpringSecurity标签使用

<!DOCTYPE html>
<!-- 导入需要的配置 -->
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    
    <!--当前用户没通过身份验证 -->
    <div sec:authorize="!isAuthenticated()">
        <a th:href="@{/login}">登录</a><br/>
    </div>

    <!-- 当前用户已通过身份验证 -->
    <div sec:authorize="isAuthenticated()">
        <!-- 当前用户的用户名 -->
        <span sec:authentication="name"></span> |
        <!-- 当前用户的权限 -->
        <span sec:authentication="principal.authorities"></span>
        <a href="/logout">注销</a><br/>
    </div>

    <!-- 当前用户必须拥有 VIP1 权限时才会显示标签内容 -->
    <div sec:authorize="hasRole('VIP1')">
        <a href="vip1/success">vip1</a><br/>
    </div>

    <!-- 当前用户必须拥有 VIP2 权限时才会显示标签内容 -->
    <div sec:authorize="hasRole('VIP2')">
        <a href="vip2/success">vip2</a><br/>
    </div>

    <!-- 当前用户必须拥有 VIP3 权限时才会显示标签内容 -->
    <div sec:authorize="hasRole('VIP3')">
        <a href="vip3/success">vip3</a>
    </div>

</body>
</html>

5、开发热部署

方法一:模板引擎

  • 在Spring Boot中开发情况下禁用模板引擎的cache页面模板
  • spring.thymeleaf.cache=false
  • ctrl+F9可以重新编译当前页面并生效

方法二:Spring Boot Devtools(推荐)

  • 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>
  • ctrl+F9可以重新编译