首页 文章 精选 留言 我的

精选列表

搜索[SpringBoot],共4203篇文章
优秀的个人博客,低调大师

Springboot应用-具有Security特性的RestTemplate

版权声明:本文为CSDN博主「coding-now」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 [原文链接](https://blog.csdn.net/feiyingwang/article/details/96424019) 1、定义需要加解密的Annotation 2、服务端实现--数据加解密 1、证书生成 2、服务端证书读取配置 3、服务端SecurityServerRestTemplate 3、客户端实现-数据加解密 1、客户端证书读取配置 2、客户端SecurityClientRestTemplate 相关辅助类 1 、客户端数据加解密组件: 2、服务端数据加解密组件 4、使用说明 1 客户端实例化相关组件 2 定义需要向服务端加密传输的对象 3 加密 4 服务端实例化相关组件 5 服务端解密客户端加密的数据 附加工具类 1、定义需要加解密的Annotation @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public@interfaceEnableSecurity{ /** * *@return */ booleanignored()defaultfalse; /** * *@return */ booleanserverSide()defaulttrue; /** * *@return */ Classtarget()defaultObject.class; //方法参数加密字段 String[]encryptFields()default{}; //解密方法返回值字段 String[]decryptFields()default{}; } 2、服务端实现–数据加解密 1、证书生成 https://blog.csdn.net/iteye_7030/article/details/81965895 2、服务端证书读取配置 publicclassServerSecurityConfig{ privateStringpassword; privateStringalias; privateStringcertificatePath; privateStringkeyStorePath; @PostConstruct publicvoidafterPropertiesSet()throwsException{ initCfg(); } //@PostConstruct publicvoidinitCfg(){ password=ContextConfig.get("aits.security.server.pwd","passwd"); alias=ContextConfig.get("aits.security.server.alias","aabbcc.com"); certificatePath=ContextConfig.get("aits.security.client.file","/wls/envconfig/aits/server.cer"); keyStorePath=ContextConfig.get("aits.security.server.file","/wls/envconfig/aits/server.keystore"); } // //getset.... } 3、服务端SecurityServerRestTemplate publicclassSecurityServerRestTemplateextendsRestTemplate{ @Autowired(required=false) privateServerSecurityConfigconfig; privatestaticfinalLoggerlog=LoggerFactory.getLogger(SecurityServerRestTemplate.class); publicSecurityServerRestTemplate(){ super(); this.getMessageConverters().add(newStringHttpMessageConverter(){ @Override protectedStringreadInternal(Class<?extendsString>clazz,HttpInputMessageinputMessage) throwsIOException{ Stringdata=super.readInternal(clazz,inputMessage); try{ byte[]decrypt=CertificateCoder.decryptByPrivateKey( CertificateCoder.decryptBASE64(data), config.getKeyStorePath(),config.getAlias(),config.getPassword()); data=newString(decrypt); }catch(Exceptionex){ log.error("error-encode-data:{}",data,ex); } returndata; } @Override protectedvoidwriteInternal(Stringstr,HttpOutputMessageoutputMessage)throwsIOException{ //服务端加密str try{ byte[]encodedData=CertificateCoder.encryptByPrivateKey(str.getBytes(), config.getKeyStorePath(),config.getAlias(),config.getPassword()); str=CertificateCoder.encryptBASE64(encodedData); }catch(Exceptionex){ log.error("error-encode-data:{}",str,ex); } super.writeInternal(str,outputMessage); } }); }} 3、客户端实现-数据加解密 1、客户端证书读取配置 publicclassClientSecurityConfig{ privateStringcertificatePath; @PostConstruct publicvoidafterPropertiesSet()throwsException{ initCfg(); } publicvoidinitCfg(){ certificatePath=ContextConfig.get("aits.security.client.file", "/wls/envconfig/aits/server.cer"); } publicStringgetCertificatePath(){ returncertificatePath; } publicvoidsetCertificatePath(StringcertificatePath){ this.certificatePath=certificatePath; } } 2、客户端SecurityClientRestTemplate /** *response实体整个加密传输,读取后整体解密 *note传输过程中,必须base64加解密 *@authorWongBin *@date2019/2/26 */publicclassSecurityClientRestTemplateextendsRestTemplate{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(SecurityServerRestTemplate.class); publicSecurityClientRestTemplate(){ super(); this.getMessageConverters().clear(); this.getMessageConverters().add(0,newStringHttpMessageConverter(){ @Override publicStringreadInternal(Class<?extendsString>clazz,HttpInputMessageinputMessage)throwsIOException{ Stringdata=super.readInternal(clazz,inputMessage); //客户端解密 try{ log.info("==========data-size:{}",data.length()); byte[]bts=CertificateCoder.decryptBASE64(data); byte[]decrypt=CertificateCoder.decryptByPublicKey(bts,config.getCertificatePath()); data=newString(decrypt); log.info("==========read-decrypted"); }catch(Exceptionex){ log.error("error-decode-data:{}",data,ex); } returndata; } @Override protectedvoidwriteInternal(Stringdata,HttpOutputMessageoutputMessage)throwsIOException{ //客户端加密str try{ Stringd=CertificateCoder.encryptBASE64(data.getBytes()); byte[]encodedData=CertificateCoder.encryptByPublicKey(d.getBytes(),config.getCertificatePath()); //str=newString(encodedData); data=newString(encodedData); log.info("==========to-write-encrypted"); }catch(Exceptionex){ log.error("error-encode-data:{}","",ex); } super.writeInternal(data,outputMessage); } }); //this.getMessageConverters().add(newStringHttpMessageConverter()); } @Autowired(required=false) privateClientSecurityConfigconfig; } 相关辅助类 /** *RSA加密的数据,网络传输之前必须base64加密,本地获取后首先base64解密,再做后续解密操作 * *@authorWongBin *@date2019/2/26 */publicabstractclassCoder{ publicstaticStringencryptBASE64(byte[]data){ returnnewString(Base64Utils.encode(data)); } publicstaticbyte[]decryptBASE64(Stringdata){ returnBase64Utils.decode(data.getBytes()); } } CertificateCoder实现参考以下文章:https://blog.csdn.net/iteye_7030/article/details/819658951 、客户端数据加解密组件: /** *@authorWongBin *@date2019/2/27 *///@Component调用方负责实例化 publicclassClientDataResolver{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(ClientDataResolver.class); @Autowired(required=false) privateClientSecurityConfigconfig; /*** *获取服务端数据后解密 *@paramserverData *@return */ publicStringdecode(StringserverData){ try{ returnnewString(CertificateCoder.decryptByPublicKey( CertificateCoder.decryptBASE64(serverData),config.getCertificatePath())); }catch(Exceptionex){ log.error("decode-server-data-error:{}",serverData,ex); returnserverData; } } /** *发送给服务端之前加密 *@paramclientData *@return */ publicStringencode(StringclientData){ try{ returnnewString(CertificateCoder.encryptBASE64( CertificateCoder.encryptByPublicKey(clientData.getBytes(), config.getCertificatePath()))); }catch(Exceptionex){ log.error("encode-client-data-error:{}",clientData,ex); returnclientData; } } /*** *解密服务端返回的加密对象 * *@paramdata */ publicvoidresolveSecurityFields(Objectdata)throwsException{ if(data!=null&&data.getClass().isAnnotationPresent(EnableSecurity.class)){ EnableSecuritytag=data.getClass().getAnnotation(EnableSecurity.class); if(!tag.serverSide()){ Class<?>resultClz=data.getClass(); Field[]fieldInfo=resultClz.getDeclaredFields(); try{ for(Stringf:tag.decryptFields()){ for(Fieldfield:fieldInfo){ if(f.equals(field.getName())){ field.setAccessible(true); Stringt=(String)field.get(data); try{ byte[]bts=CertificateCoder.decryptBASE64(t); byte[]temp=CertificateCoder.decryptByPublicKey(bts,config.getCertificatePath()); field.set(data,newString(temp)); log.info("decrypt-server-data-done:...{}",f); }catch(Exceptionex){ //log.error("decrypt-server-data-error:{}",data,ex); throwex; } break; } } } }catch(Exceptionex){ log.error("解密服务端数据出错:{}",data,ex); throwex; } } } } } 2、服务端数据加解密组件 /** *@authorWongBin *@date2019/2/27 */ publicclassServerDataResolver{ privatestaticfinalLoggerlog=LoggerFactory.getLogger(ServerDataResolver.class); @Autowired privateSecurityServerRestTemplatetemplate; @Autowired privateServerSecurityConfigconfig; /*** *获取客户端数据后解密 *@paramdata *@return */ publicStringdecode(Stringdata){ try{ returnnewString(CertificateCoder.decryptByPrivateKey( CertificateCoder.decryptBASE64(data), config.getKeyStorePath(),config.getAlias(),config.getPassword())); }catch(Exceptionex){ log.error("decode-client-data-error:{}",data,ex); returndata; } } /** *发送给客户端之前加密 *@paramdata *@return */ publicStringencode(Stringdata){ try{ returnnewString(CertificateCoder.encryptBASE64( CertificateCoder.encryptByPrivateKey(data.getBytes(), config.getKeyStorePath(),config.getAlias(),config.getPassword()))); }catch(Exceptionex){ log.error("encode-server-data-error:{}",data,ex); returndata; } } /*** *解密客户端返回的加密对象 * *@paramdata */ publicvoidresolveSecurityFields(Objectdata){ if(data!=null&&data.getClass().isAnnotationPresent(EnableSecurity.class)){ EnableSecuritytag=data.getClass().getAnnotation(EnableSecurity.class); if(!tag.serverSide()){ Class<?>resultClz=data.getClass(); Field[]fieldInfo=resultClz.getDeclaredFields(); try{ for(Stringf:tag.decryptFields()){ for(Fieldfield:fieldInfo){ if(f.equals(field.getName())){ field.setAccessible(true); Stringt=(String)field.get(data); try{ byte[]bts=CertificateCoder.decryptBASE64(t); byte[]temp=CertificateCoder.decryptByPrivateKey(bts, config.getKeyStorePath(),config.getAlias(),config.getPassword()); field.set(data,newString(temp)); log.info("decrypt-client-data-done:...{}",f); }catch(Exceptionex){ log.error("decrypt-client-data-error:{}",data,ex); } break; } } } }catch(Exceptionex){ log.error("解密客户端返回的数据出错:{}",data,ex); } } } } } 4、使用说明 1 客户端实例化相关组件 @Bean @Lazy publicClientSecurityConfigclientSecurityConfig(){ returnnewClientSecurityConfig(); } @Bean //@DependsOn({"clientSecurityConfig"}) @Lazy publicClientDataResolverclientDataResolver(){ returnnewClientDataResolver(); } @Bean //@DependsOn({"clientSecurityConfig"}) @Lazy publicSecurityClientRestTemplatesecurityClientRestTemplate(){ returnnewSecurityClientRestTemplate(); } @Lazy @Primary @Bean publicRestTemplaterestTemplate(){ returnnewRestTemplate(); } 定义需要向服务端加密传输的对象 @EnableSecurity(serverSide=false,decryptFields={"srcDbIp","srcDbPort","srcDbUsername","srcDbPasswd","srcDbname"}) publicclassDbConfigVO{ privateStringdbType; privateStringdbFile; privateStringprojectCode; privateStringsrcDbIp; //getsettoString... } 加密 @AutowiredprivateClientDataResolverclientDataResolver; ....clientDataResolver.resolveSecurityFields(vo);.... 服务端实例化相关组件 @ConfigurationpublicclassSecurityConfig{ /*数据加密相关组件*/ @Bean //@DependsOn({"serverSecurityConfig"}) @Lazy publicSecurityServerRestTemplatesecurityTemplate(){ returnnewSecurityServerRestTemplate(); } @Bean @Primary publicRestTemplaterestTemplate(){ returnnewRestTemplate(); } @Bean @Lazy publicServerDataResolverresolver(){ returnnewServerDataResolver(); } @Lazy @Bean publicServerSecurityConfigserverSecurityConfig(){ returnnewServerSecurityConfig(); } } 服务端解密客户端加密的数据 @AutowiredprivateServerDataResolverdataResolver; dataResolver.resolveSecurityFields(...) 当然,服务端加密数据给客户端,可以定义Aspect统一处理EnableSecurity标记的类,目前已实现内部项目,有需要留言沟通。 服务端加密传输,客户端解密 客户端加密传输,服务端解密 最终双向加密传输都可以实现了,有类似需求的可以参考实现之。 附加工具类 /** *String转公钥PublicKey *@paramkey *@return *@throwsException */ publicstaticPublicKeygetPublicKey(Stringkey){ byte[]keyBytes; try{ keyBytes=(newBASE64Decoder()).decodeBuffer(key); X509EncodedKeySpeckeySpec=newX509EncodedKeySpec(keyBytes); KeyFactorykeyFactory=KeyFactory.getInstance("RSA"); PublicKeypublicKey=keyFactory.generatePublic(keySpec); returnpublicKey; }catch(Exceptionex){ thrownewRuntimeException("getPublicKey",ex); } } /** *String转私钥PrivateKey *@paramkey *@return *@throwsException */ publicstaticPrivateKeygetPrivateKey(Stringkey){ byte[]keyBytes; try{ keyBytes=(newBASE64Decoder()).decodeBuffer(key); PKCS8EncodedKeySpeckeySpec=newPKCS8EncodedKeySpec(keyBytes); KeyFactorykeyFactory=KeyFactory.getInstance("RSA"); PrivateKeyprivateKey=keyFactory.generatePrivate(keySpec); returnprivateKey; }catch(Exceptionex){ thrownewRuntimeException("getPrivateKey-error",ex); } }

优秀的个人博客,低调大师

Springboot 系列(四)Spring Boot 日志框架

文章已经收录在 Github.com/niumoo/JavaNotes ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 欢迎关注我的公众号,文章每周更新。、 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 v2.1.1.RELEASE 进行学习分析,版本不同可能会有细微差别。 前言 Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack。那我们在项目中该使用哪种日志框架呢?在对于不同的第三方 jar 使用了不同的日志框架的时候,我们该怎么处理呢? <!-- more --> 1. 日志框架介绍 日志对于应用程序的重要性不言而喻,不管是记录运行情况还是追踪线上问题,都离不开对日志的分析,在 Java 领域里存在着多种日志框架,如 JUL, Log4j, Log4j2, Commons Loggin, Slf4j, Logback 等。关于 Log4j, Log4j2 和 Slf4j 直接的故事这里不做介绍,有兴趣可以自行百度。 2. SLF4 的使用 在开发的时候不应该直接使用日志实现类,应该使用日志的抽象层。具体参考 SLF4J 官方。 下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,直接应用与应用程序中。 同时 SLF4 官方给出了简单示例。 import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); } } 需要注意的是,要为系统导入 SLF4J 的 jar 和 日志框架的实现 jar. 由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4 之后,配置文件还是要使用实现日志框架的配置文件。 3. 统一日志框架的使用 一般情况下,在项目中存在着各种不同的第三方 jar ,且它们的日志选择也可能不尽相同,显然这样是不利于我们使用的,那么如果我们想为项目设置统一的日志框架该怎么办呢? 在 SLF4J 官方,也给了我们参考的例子。 从图中我们得到一种统一日志框架使用的方式,可以使用一种和要替换的日志框架类完全一样的 jar 进行替换,这样不至于原来的第三方 jar 报错,而这个替换的 jar 其实使用了 SLF4J API. 这样项目中的日志就都可以通过 SLF4J API 结合自己选择的框架进行日志输出。 统一日志框架使用步骤归纳如下: 排除系统中的其他日志框架。 使用中间包替换要替换的日志框架。 导入我们选择的 SLF4J 实现。 4. Spring Boot 的日志关系 4.1. 排除其他日志框架 根据上面总结的要统一日志框架的使用,第一步要排除其他的日志框架,在 Spring Boot 的 Maven 依赖里可以清楚的看到 Spring Boot 排除了其他日志框架。 我们自行排除依赖时也只需要按照图中的方式就好了。 4.2. 统一框架引入替换包 其实 Spring Boot 也是使用了 SLF4J+logback 的日志框架组合,查看 Spring Boot 项目的 Maven 依赖关系可以看到 Spring Boot 的核心启动器 spring-boot-starter 引入了 spring-boot-starter-logging. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> <version>2.1.1.RELEASE</version> <scope>compile</scope> </dependency> 而 spring-boot-starter-logging 的 Maven 依赖主要引入了 logback-classic (包含了日志框架 Logback 的实现),log4j-to-slf4j (在 log4j 日志框架作者开发此框架的时候还没有想到使用日志抽象层进行开发,因此出现了 log4j 向 slf4j 转换的工具),jul-to-slf4j ( Java 自带的日志框架转换为 slf4j). <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>2.11.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>1.7.25</version> <scope>compile</scope> </dependency> </dependencies> 从上面的分析,Spring Boot 对日志框架的使用已经是清晰明了了,我们使用 IDEA 工具查看 Maven 依赖关系,可以清晰的看到日志框架的引用。如果没有 IDEA 工具,也可以使用 Maven 命令查看依赖关系。 mvn dependency:tree 由此可见,Spring Boot 可以自动的适配日志框架,而且底层使用 SLF4 + LogBack 记录日志,如果我们自行引入其他框架,需要排除其日志框架。 5. Spring Boot 的日志使用 5.1. 日志级别和格式 从上面的分析,发现 Spring Boot 默认已经使用了 SLF4J + LogBack . 所以我们在不进行任何额外操作的情况下就可以使用 SLF4J + Logback 进行日志输出。 编写 Java 测试类进行测试。 import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * <p> * 测试日志输出, * SLF4J 日志级别从小到大trace,debug,info,warn,error * * @Author niujinpeng * @Date 2018/12/11 21:12 */ @RunWith(SpringRunner.class) @SpringBootTest public class LogbackTest { Logger logger = LoggerFactory.getLogger(getClass()); @Test public void testLog() { logger.trace("Trace 日志..."); logger.debug("Debug 日志..."); logger.info("Info 日志..."); logger.warn("Warn 日志..."); logger.error("Error 日志..."); } } 已知日志级别从小到大为 trace < debug < info < warn < error . 运行得到输出如下。由此可见 Spring Boot 默认日志级别为 INFO. 2018-12-11 23:02:58.028 [main] INFO n.c.boot.LogbackTest - Info 日志... 2018-12-11 23:02:58.029 [main] WARN n.c.boot.LogbackTest - Warn 日志... 2018-12-11 23:02:58.029 [main] ERROR n.c.boot.LogbackTest - Error 日志... 从上面的日志结合 Logback 日志格式可以知道 Spring Boot 默认日志格式是。 %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n # %d{yyyy-MM-dd HH:mm:ss.SSS} 时间 # %thread 线程名称 # %-5level 日志级别从左显示5个字符宽度 # %logger{50} 类名 # %msg%n 日志信息加换行 至于为什么 Spring Boot 的默认日志输出格式是这样? 我们可以在 Spring Boot 的源码里找到答案。 5.2 自定义日志输出 可以直接在配置文件编写日志相关配置。 # 日志配置 # 指定具体包的日志级别 logging.level.net.codingme=debug # 控制台和日志文件输出格式 logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n # 日志文件大小 logging.file.max-size=10MB # 保留的日志时间 logging.file.max-history=10 # 日志输出路径,默认文件spring.log logging.path=systemlog #logging.file=log.log 关于日志的输出路径,可以使用 logging.file 或者 logging.path 进行定义,两者存在关系如下表。 logging.file logging.path 例子 描述 (没有) (没有) 仅控制台记录。 具体文件 (没有) my.log 写入指定的日志文件,名称可以是精确位置或相对于当前目录。 (没有) 具体目录 /var/log 写入spring.log指定的目录,名称可以是精确位置或相对于当前目录。 6. 替换日志框架 因为 Log4j 日志框架已经年久失修,原作者都觉得写的不好,所以下面演示替换日志框架为 Log4j2 的方式。根据官网我们 Log4j2 与 logging 需要二选一,因此修改 pom如下。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springframework.boot</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> </dependency> 文章代码已经上传到 GitHub Spring Boot 日志系统。 最后的话 文章已经收录在 Github.com/niumoo/JavaNotes ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 Star 和完善,希望我们一起变得优秀。 文章有帮助可以点个「赞」或「分享」,都是支持,我都喜欢! 文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 未读代码 」公众号或者我的博客。

优秀的个人博客,低调大师

SpringBoot使用redis进行发布订阅消息

redis不仅是一个非常强大的非关系型数据库,它同时还拥有消息中间件的pub/sub功能,在spring boot中进行如下设置就可以使用redis的pub/sub功能 1、新建一个消息发布者,设置主题为topic /** * @author Gjing * * 消息发送者 **/ @RestController public class PubController { @Resource private StringRedisTemplate stringRedisTemplate; private static AtomicInteger count = new AtomicInteger(); @PostMapping("/message") public void send

优秀的个人博客,低调大师

Springboot整合redis从安装到FLUSHALL

语言: java+kotlin windows下安装redis 参考 https://www.cnblogs.com/jaign/articles/7920588.html 安装redis可视化工具 Redis Desktop Manager 参考 https://www.cnblogs.com/zheting/p/7670154.html 依赖 compile('org.springframework.boot:spring-boot-starter-data-redis') application.yml配置 spring: # redis redis: database: 0 host: localhost port: 6379 password: 12345 jedis: pool: max-active: 10 min-idle: 0 max-idle: 8 timeout: 10000 redis配置类 new RedisConfiguration package com.futao.springmvcdemo.foundation.configuration import org.springframework.cache.CacheManager import org.springframework.cache.annotation.CachingConfigurerSupport import org.springframework.cache.annotation.EnableCaching import org.springframework.cache.interceptor.KeyGenerator import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.data.redis.cache.RedisCacheManager import org.springframework.data.redis.connection.RedisConnectionFactory import org.springframework.data.redis.core.RedisTemplate import org.springframework.data.redis.serializer.StringRedisSerializer import javax.annotation.Resource /** * redis配置类 * * @author futao * Created on 2018/10/16. * * redisTemplate.opsForValue();//操作字符串 * redisTemplate.opsForHash();//操作hash * redisTemplate.opsForList();//操作list * redisTemplate.opsForSet();//操作set * redisTemplate.opsForZSet();//操作有序set * */ @Configuration @EnableCaching open class RedisConfiguration : CachingConfigurerSupport() { /** * 自定义redis key的生成规则 */ @Bean override fun keyGenerator(): KeyGenerator { return KeyGenerator { target, method, params -> val builder = StringBuilder() builder.append("${target.javaClass.simpleName}-") .append("${method.name}-") for (param in params) { builder.append("$param-") } builder.toString().toLowerCase() } } /** * 自定义序列化 * 这里的FastJsonRedisSerializer引用的自己定义的 */ @Bean open fun redisTemplate(factory: RedisConnectionFactory): RedisTemplate<String, Any> { val redisTemplate = RedisTemplate<String, Any>() val fastJsonRedisSerializer = FastJsonRedisSerializer(Any::class.java) val stringRedisSerializer = StringRedisSerializer() return redisTemplate.apply { defaultSerializer = fastJsonRedisSerializer keySerializer = stringRedisSerializer hashKeySerializer = stringRedisSerializer valueSerializer = fastJsonRedisSerializer hashValueSerializer = fastJsonRedisSerializer connectionFactory = factory } } @Resource lateinit var redisConnectionFactory: RedisConnectionFactory override fun cacheManager(): CacheManager { return RedisCacheManager.create(redisConnectionFactory) } } 自定义redis中数据的序列化与反序列化 new FastJsonRedisSerializer package com.futao.springmvcdemo.foundation.configuration import com.alibaba.fastjson.JSON import com.alibaba.fastjson.serializer.SerializerFeature import com.futao.springmvcdemo.model.system.SystemConfig import org.springframework.data.redis.serializer.RedisSerializer import java.nio.charset.Charset /** * 自定义redis中数据的序列化与反序列化 * * @author futao * Created on 2018/10/17. */ class FastJsonRedisSerializer<T>(java: Class<T>) : RedisSerializer<T> { private val clazz: Class<T>? = null /** * Serialize the given object to binary data. * * @param t object to serialize. Can be null. * @return the equivalent binary data. Can be null. */ override fun serialize(t: T?): ByteArray? { return if (t == null) { null } else { JSON.toJSONString(t, SerializerFeature.WriteClassName).toByteArray(Charset.forName(SystemConfig.UTF8_ENCODE)) } } /** * Deserialize an object from the given binary data. * * @param bytes object binary representation. Can be null. * @return the equivalent object instance. Can be null. */ override fun deserialize(bytes: ByteArray?): T? { return if (bytes == null || bytes.isEmpty()) { null } else { val string = String(bytes, Charset.forName(SystemConfig.UTF8_ENCODE)) JSON.parseObject(string, clazz) as T } } } 使用 1. 基于注解的方式 @Cacheable() redis中的key会根据我们的keyGenerator方法来生成,比如对应下面这个例子,如果曾经以mobile,pageNum,pageSize,orderBy的值执行过list这个方法的话,方法返回的值会存在redis缓存中,下次如果仍然以相同的mobile,pageNum,pageSize,orderBy的值来调用这个方法的话会直接返回缓存中的值 @Service public class UserServiceImpl implements UserService { @Override @Cacheable(value = "user") public List<User> list(String mobile, int pageNum, int pageSize, String orderBy) { PageResultUtils<User> pageResultUtils = new PageResultUtils<>(); final val sql = pageResultUtils.createCriteria(User.class.getSimpleName()) .orderBy(orderBy) .page(pageNum, pageSize) .getSql(); return userDao.list(sql); } } 测试 第一次请求(可以看到执行了sql,数据是从数据库中读取的) 通过redis desktop manager查看redis缓存中已经存储了我们刚才list返回的值 后续请求(未执行sql,直接读取的是redis中的值) 2. 通过java代码手动set与get package com.futao.springmvcdemo.controller import com.futao.springmvcdemo.model.entity.User import org.springframework.data.redis.core.RedisTemplate import org.springframework.http.MediaType import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import javax.annotation.Resource /** * @author futao * Created on 2018/10/17. */ @RestController @RequestMapping(path = ["kotlinTest"], produces = [MediaType.APPLICATION_JSON_UTF8_VALUE]) open class KotlinTestController { @Resource private lateinit var redisTemplate: RedisTemplate<Any, Any> /** * 存入缓存 */ @GetMapping(path = ["setCache"]) open fun cache( @RequestParam("name") name: String, @RequestParam("age") age: Int ): User { val user = User().apply { username = name setAge(age.toString()) } redisTemplate.opsForValue().set(name, user) return user } /** * 获取缓存 */ @GetMapping(path = ["getCache"]) open fun getCache( @RequestParam("name") name: String ): User? { return if (redisTemplate.opsForValue().get(name) != null) { redisTemplate.opsForValue().get(name) as User } else null } } 测试结果 请求(序列化) redis desktop manager中查看 读取(反序列化) 坑 使用注解的方式存入的数据使用redis desktop manager或者redis-cli --raw查看显示的是编码之后的,但是使用java代码手动set并不会出现这样的问题(后期需要检查使用注解的方式是不是走了自定义的序列化) TODO redis数据的持久化

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册