SpringSecurity与Redis(分解与整合)

  • Post author:
  • Post category:其他


一、SpringSecurity

SpringSecurity是一个强大且高效的安全框架,能够提供用户验证和访问控制服务,能够很好地整合到以Spring为基础的项目中。SpringBoot对SpringSecurity进行了大量的自动配置,使开发者通过少量的代码和配置就能完成很强大的验证和授权功能,下面我们就体验下SpringSecurity的基本使用。

引入依赖:引入spring security依赖

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

springSecurity可以基于内存验证,也可以基于数据库作登录验证,在此就仅展示做数据库的验证

做登录验证,首先要有一个UserDetailServiceImpl来实现一个接口


@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IUserService userService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //按用户名查询用户信息
        User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUsername, s));
        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        //通过用户名查询权限
        List<String> list = userService.selectAuthoritiesByUsername(s);
        //将集合转换为字符串,每个权限以逗号相隔
        String s1=String.join(",",list);
        //把用户信息包装到UserDetails的实现类User中
        return new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),
                AuthorityUtils.commaSeparatedStringToAuthorityList(s1));
    }
}

这个方法就是Security自带的登录验证功能,实现它就可以做登录验证

当然,还要做一些Security的配置工作,比如密码的加密算法啊,合成token字符串啊之类的,所以在做配置工作之前,还要自定义一些工具类,这里直接拿来用,复制粘贴即可

package com.blb.test.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * JWT工具类
 */
public class JwtUtils {

    public static final String JWT_KEY_USERNAME = "username";
    public static final int EXPIRE_MINUTES = 120;

    /**
     * 私钥加密token
     */
    public static String generateToken(String username, PrivateKey privateKey, int expireMinutes) {

        return Jwts.builder()
                .claim(JWT_KEY_USERNAME, username)
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
    }

    /**
     * 从token解析用户
     *
     * @param token
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static String getUsernameFromToken(String token, PublicKey publicKey){
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        Claims body = claimsJws.getBody();
        String username = (String) body.get(JWT_KEY_USERNAME);
        return username;
    }
}

主要是用来生成token字符串

还要有一个加密算法

package com.blb.test.util;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA工具类
 */
public class RsaUtils {

    public static final String RSA_SECRET = "blbweb@#$%"; //秘钥
    public static final String RSA_PATH = System.getProperty("user.dir")+"/rsa/";//秘钥保存位置
    public static final String RSA_PUB_KEY_PATH = RSA_PATH + "pubKey.rsa";//公钥路径
    public static final String RSA_PRI_KEY_PATH = RSA_PATH + "priKey.rsa";//私钥路径

    public static PublicKey publicKey; //公钥
    public static PrivateKey privateKey; //私钥

    /**
     * 类加载后,生成公钥和私钥文件
     */
    static {
        try {
            File rsa = new File(RSA_PATH);
            if (!rsa.exists()) {
                rsa.mkdirs();
            }
            File pubKey = new File(RSA_PUB_KEY_PATH);
            File priKey = new File(RSA_PRI_KEY_PATH);
            //判断公钥和私钥如果不存在就创建
            if (!priKey.exists() || !pubKey.exists()) {
                //创建公钥和私钥文件
                RsaUtils.generateKey(RSA_PUB_KEY_PATH, RSA_PRI_KEY_PATH, RSA_SECRET);
            }
            //读取公钥和私钥内容
            publicKey = RsaUtils.getPublicKey(RSA_PUB_KEY_PATH);
            privateKey = RsaUtils.getPrivateKey(RSA_PRI_KEY_PATH);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }

    /**
     * 从文件中读取公钥
     *
     * @param filename 公钥保存路径,相对于classpath
     * @return 公钥对象
     * @throws Exception
     */
    public static PublicKey getPublicKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPublicKey(bytes);
    }

    /**
     * 从文件中读取密钥
     *
     * @param filename 私钥保存路径,相对于classpath
     * @return 私钥对象
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String filename) throws Exception {
        byte[] bytes = readFile(filename);
        return getPrivateKey(bytes);
    }

    /**
     * 获取公钥
     *
     * @param bytes 公钥的字节形式
     * @return
     * @throws Exception
     */
    public static PublicKey getPublicKey(byte[] bytes) throws Exception {
        X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePublic(spec);
    }

    /**
     * 获取密钥
     *
     * @param bytes 私钥的字节形式
     * @return
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
        KeyFactory factory = KeyFactory.getInstance("RSA");
        return factory.generatePrivate(spec);
    }

    /**
     * 根据密文,生存rsa公钥和私钥,并写入指定文件
     *
     * @param publicKeyFilename  公钥文件路径
     * @param privateKeyFilename 私钥文件路径
     * @param secret             生成密钥的密文
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(secret.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        // 获取公钥并写出
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        writeFile(publicKeyFilename, publicKeyBytes);
        // 获取私钥并写出
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        writeFile(privateKeyFilename, privateKeyBytes);
    }

    private static byte[] readFile(String fileName) throws Exception {
        return Files.readAllBytes(new File(fileName).toPath());
    }

    private static void writeFile(String destPath, byte[] bytes) throws IOException {
        File dest = new File(destPath);
        if (!dest.exists()) {
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

这里主要用RSA加密算法

当然,也可以自定义一些状态码,403啊500啊等,用AJAX流的方式来回调,也写在工具类中

package com.blb.test.util;

/**
 * 响应状态枚举
 */
public enum ResponseStatus {
    /**
     * 内置状态
     */
    OK(200,"操作成功"),
    INTERNAL_ERROR(500000,"系统错误"),
    BUSINESS_ERROR(500001,"业务错误"),
    LOGIN_ERROR(500002,"账号或密码错误"),
    NO_DATA_ERROR(500003,"没有找到数据"),
    PARAM_ERROR(500004,"参数格式错误"),
    AUTH_ERROR(401,"没有权限,需要登录");

    //响应代码
    private Integer code;
    //响应消息
    private String message;

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    ResponseStatus(Integer status, String message) {
        this.code = status;
        this.message = message;
    }
}
package com.blb.test.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 响应数据封装对象
 */
@Data
public class ResponseResult<T> {

    /**
     * 状态信息
     */
    private ResponseStatus status;

    /**
     * 数据
     */
    private T data;

    public ResponseResult(ResponseStatus status, T data) {
        this.status = status;
        this.data = data;
    }

    /**
     * 返回成功对象
     * @param data
     * @return
     */
    public static <T>  ResponseResult<T> ok(T data){
        return new ResponseResult<>(ResponseStatus.OK, data);
    }

    /**
     * 返回错误对象
     * @param status
     * @return
     */
    public static ResponseResult<String> error(ResponseStatus status){
        return new ResponseResult<>(status,status.getMessage());
    }

    /**
     * 返回错误对象
     * @param status
     * @return
     */
    public static ResponseResult<String> error(ResponseStatus status,String msg){
        return new ResponseResult<>(status,msg);
    }

    /**
     * 向流中输出结果
     * @param resp
     * @param result
     * @throws IOException
     */
    public static void write(HttpServletResponse resp, ResponseResult result) throws IOException {
        //设置返回数据的格式
        resp.setContentType("application/json;charset=UTF-8");
        //jackson是JSON解析包,ObjectMapper用于解析 writeValueAsString 将Java对象转换为JSON字符串
        String msg = new ObjectMapper().writeValueAsString(result);
        //用流发送给前端
        resp.getWriter().print(msg);
        resp.getWriter().close();
    }
}

这样基本的准备工作就完成了,接下来就是Security的配置文件工作

//package com.blb.springsecurity.config;
//
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.context.annotation.Bean;
//import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
//import org.springframework.security.core.userdetails.UserDetailsService;
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
//
///**
// * SpringSecurity的核心配置
// */
启动Security的验证
//@EnableWebSecurity
//public class SecurityConfig extends WebSecurityConfigurerAdapter {
//
//    //提供密码编码器
//    @Bean
//    public BCryptPasswordEncoder bCryptPasswordEncoder(){
//        return new BCryptPasswordEncoder();
//    }
//
//    @Autowired
//    private BCryptPasswordEncoder bCryptPasswordEncoder;
//
//    @Autowired
//    private UserDetailsService userDetailsService;
//
//    @Autowired
//    private PersistentTokenRepository persistentTokenRepository;
//
//    //配置验证用户的账号和密码
//    @Override
//    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //在内存中新建用户
        auth.inMemoryAuthentication()
                .withUser("zhangsan")
                .password(bCryptPasswordEncoder.encode("123"))
                .roles("ADMIN","USER") //角色
                .and()
                .withUser("lisi")
                .password(bCryptPasswordEncoder.encode("123"))
                .roles("USER"); //权限
//        //数据库用户验证
//        auth.userDetailsService(userDetailsService);
//    }
//
//    //配置访问控制
//    @Override
//    protected void configure(HttpSecurity http) throws Exception {
//        //给请求授权
//        http.authorizeRequests()
//                //给登录相关的请求放行
//                .antMatchers("/login","/login.html","/**/*.css").permitAll()
//                //访问控制
//                .antMatchers("/admin/**").hasAuthority("采购管理")
//                .antMatchers("/user/**").hasAuthority("销售管理")
//
//                //其余的都拦截
//                .anyRequest().authenticated()
//                .and()
//                //配置自定义登录
//                .formLogin()
//                .loginPage("/login.html") //登录页面
//                .loginProcessingUrl("/login") //处理登录的url
//                .successForwardUrl("/hello.html") //登录成功后跳转的url
//        ;
//                 http
//                //配置记住我
//                .rememberMe()
//                //表单中的名称
//                .rememberMeParameter("rememberMe")
//                //jdbc操作对象
//                .tokenRepository(persistentTokenRepository)
//                //记住我的时间为60秒
//                .tokenValiditySeconds(60);
//    }
//}

package com.blb.test.config;
import com.blb.test.filter.RequestAuthenticationFilter;
import com.blb.test.util.ResponseResult;
import com.blb.test.util.ResponseStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

/**
 * SpringSecurity的核心配置
 */
//启动权限控制的注解
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
//启动Security的验证
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //提供密码编码器
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    //配置验证用户的账号和密码
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //数据库用户验证
        auth.userDetailsService(userDetailsService);
    }

    //配置访问控制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //给请求授权
        http.authorizeRequests()
                //给登录相关的请求放行
                .antMatchers("/login","/logout").permitAll()
                //访问控制
                //其余的都拦截
                .anyRequest().authenticated()
                .and()
                //配置自定义登录
                .formLogin()
                .successHandler(loginSuccessHandler)//成功处理器
                .failureHandler(((httpServletRequest, httpServletResponse, e) -> { //登录失败处理器
                    ResponseResult.write(httpServletResponse,ResponseResult.error(ResponseStatus.LOGIN_ERROR));
                }))
                .and()
                .logout() //配置注销
                .logoutSuccessHandler(((httpServletRequest, httpServletResponse, authentication) -> { //注销成功
                    ResponseResult.write(httpServletResponse,ResponseResult.ok(ResponseStatus.OK));
                }))
                .clearAuthentication(true) //清除验证信息
                .and()
                .cors() //配置跨域
                .configurationSource(corsConfigurationSource())
                .and()
                .csrf().disable() //停止csrf
                .sessionManagement() //session管理
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //无状态,不使用session
                .and()
                .addFilter(new RequestAuthenticationFilter(authenticationManager())) //添加自定义验证过滤器
        ;
    }

    /**
     * 跨域配置对象
     * @return
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        //配置允许访问的服务器域名
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowCredentials(true);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

这里有一个问题就是我们都是将对象交给IOC容器来管理,在添加过滤的时候生成一个new RequestAuthenticationFilter 这个对象,这个对象是没有无参构造方法的,是不能自动注入交给IOC来管理的,因此我们要手动来生成对象来交给IOC管理,那么如何实现呢?记得刚接触Spring的时候吗?

有一个ApplicationContext,它继承了BeanFactory,所以知道了吧……因此,在工具类的包下建一个生成对象交给IOC管理的容器

package com.blb.test.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * 应用程序上下文工具
 * 程序启动后,会创建ApplicationContext对象
 * ApplicationContextAware能感知到ApplicationContext对象
 * 自动调用setApplicationContext方法
 */
@Component
public class ApplicationContextUtils implements ApplicationContextAware {

    //系统的IOC容器
    private static ApplicationContext applicationContext = null;

    //感知到上下文后,自动调用,获得上下文
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ApplicationContextUtils.applicationContext = applicationContext;
    }

    //返回对象
    public static <T> T getBean(Class<T> tClass){
        return applicationContext.getBean(tClass);
    }
}

还缺一个配置,大家想想是什么呢?没错,就是登录成功的处理器,登录成功该干啥呢?这个处理器就是来完成这个的配置的

package com.blb.test.config;


import com.blb.test.entity.vo.UserTokenVO;
import com.blb.test.util.JwtUtils;
import com.blb.test.util.ResponseResult;
import com.blb.test.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录成功处理器
 */
@Slf4j
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    //登录成功的回调
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //获得用户名
        User user = (User) authentication.getPrincipal();
        //生成token字符串
        String token = JwtUtils.generateToken(user.getUsername(), RsaUtils.privateKey, JwtUtils.EXPIRE_MINUTES);
        log.info("生成token:{}",token);
        //发送token给前端
        ResponseResult.write(httpServletResponse,ResponseResult.ok(new UserTokenVO(user.getUsername(),token)));
    }
}

这里仅仅是把token字符串回调给前端显示

Config配置完成后,大家想想接下来干嘛呢?那就是要做过滤的配置,就像之前做SpringWeb项目一样,登录要用过滤器进行过滤与放行,所以还要添加一个过滤器,Security的底层就是用各种过滤器来完成的,因此过滤器是十分重要的

package com.blb.test.filter;

import com.blb.test.service.IUserService;
import com.blb.test.util.ApplicationContextUtils;
import com.blb.test.util.JwtUtils;
import com.blb.test.util.RsaUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * 请求验证过滤器
 */
@Slf4j
public class RequestAuthenticationFilter extends BasicAuthenticationFilter {

    public static final String AUTH_HEADER = "Authorization";

    //通过工具类获得service对象
    private IUserService userService = ApplicationContextUtils.getBean(IUserService.class);

    public RequestAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    //请求的过滤
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //从请求头获得token
        String token = request.getHeader(AUTH_HEADER);
        if(StringUtils.isEmpty(token)){
            //从请求参数获得token
            token = request.getParameter(AUTH_HEADER);
        }
        //如果读取不到,就拦截
        if(StringUtils.isEmpty(token)){
            log.info("读取不到token,请求{}被拦截",request.getRequestURL());
            chain.doFilter(request,response);
            return;
        }
        try {
            //对token进行解析
            String username = JwtUtils.getUsernameFromToken(token, RsaUtils.publicKey);
            //将用户的权限查询出来
            List<String> authList = userService.getAuthoritiesByUsername(username);
            List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", authList));
            //创建通行证
            UsernamePasswordAuthenticationToken authToken = new
                    UsernamePasswordAuthenticationToken(username,"",authorities);
            //把通行证交给Security
            SecurityContextHolder.getContext().setAuthentication(authToken);
        }catch (Exception ex){
            log.error("解析token失败",ex);
        }
        chain.doFilter(request,response);
    }
}

这样基本的Security的登录验证就完成了,大家看看效果,用APIPost7

Authority

Security除了登录验证,还有一个核心就是权限,简单来说就是登录完成后有些页面的访问是需要权限的,比如管理员和普通用户的区别,这个很简单,只需要在Controller层的方法上加个注解即可,注解里写的是权限名称

@RestController
public class PermissionsController {
    @Autowired
    private IPermissionService permissionService;
    @PreAuthorize("hasAuthority('系统功能')")
    @RequestMapping("/permissions")
    public List<Permission> selectAllPermission(){
        List<Permission> permissions = permissionService.selectALlPermission();
        return permissions;
    }

这里的系统功能就是数据库管理员独有的权限,因此这个方法只能管理员来访问,如果没有相应的权限,那么就会报403的错误,当然,我们在访问的时候要在请求头上携带登录生成的token来确认你是哪个用户登录的,有没有这个权限

好了,Security就讲到这里,接下来是Redis

二、Redis

Redis(Remote Dictionary Server )远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存也可持久化的日志型、Key-Value(NoSQL)数据库。

Redis的特点

  • 性能极高,基于内存,读的速度是110000次/s,写的速度是81000次/s

  • 丰富的数据类型,支持string、hash、list、set及zset多种数据类型

  • 原子性,所有操作都是原子性的,支持事务

  • 丰富的特性,支持发布订阅、通知、过期策略等

  • 支持持久化,可以将内存中的数据保存在磁盘中,重启后再次加载

  • 支持分布式,理论上可以无限扩展

  • 单线程,没有线程并发问题,Redis5.0后也支持多线程

应用场景:

1. 做为缓存,保存热点数据

2. 分布式锁、分布式ID、分布式Session

3. 消息队列

4. …

安装等步骤就不在此展示了,可以去看看其他教程安装,也很简单

Redis主要用来作缓存来使用,分为编程式缓存与声明式缓存,推荐使用声明式缓存,比较方便简单

声明式缓存的配置:

配置环境

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-wait=100ms
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10

导入依赖

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

//启动缓存
@EnableCaching
@Configuration
public class RedisConfig {

    //向IOC容器提供一个Redis操作对象,配置键和值的序列化
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        //创建Redis模板对象
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        //设置连接
        template.setConnectionFactory(factory);
        // 配置JSON序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置字符串序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson序列化器
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 完成配置
        template.afterPropertiesSet();
        return template;
    }

声明式缓存的配置:

 //配置声明式缓存
    @Bean
    public RedisCacheConfiguration provideRedisCacheConfiguration(){
        //加载默认配置
        RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig();
        //返回Jackson序列化器
        return conf.serializeValuesWith(
                RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }

使用步骤

缓存相关注解:

  • @CacheConfig 使用在Service类上,可以配置缓存名称,如:@CacheConfig(cacheNames = “books”)

  • @Cacheable 使用在查询方法上,让方法优先查询缓存

  • @CachePut 使用在更新和添加方法上,数据库更新和插入数据后同时保存到缓存里

  • @CacheEvict 使用在删除方法上,数据库删除后同时删除缓存

注意:缓存的实体类必须实现序列化接口

在ServiceImpl中方法上添加注解即可,如:

  @Override
    @Cacheable(cacheNames = "Permission",key = "T(String).valueOf(#id)")
    public Permission selectById(Integer id) {
        return permissionMapper.selectById(id);
    }

这样就完成了Redis的声明式缓存,接下来看看效果,打开虚拟机,Linux中的Redis服务

运行服务后测试就可以看到缓存中的数据库

好了,今天的分享就到此结束,希望对大家有所帮助!



版权声明:本文为m0_57816620原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。