首页 文章 精选 留言 我的

精选列表

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

【SpringBoot】AOP 概述 && Spring AOP 快速入门

# Ⅰ. AOP 概述 **什么是AOP?** - Aspect Oriented Programming(面向切面编程) **什么是面向切面编程呢?** - **切面就是指某一类特定问题**,所以 AOP 也可以理解为面向特定方法编程。 > **什么是面向特定方法编程呢?** 比如上个章节学习的 "登录校验",就是一类特定问题。登录校验拦截器,就是对 "登录校验" 这类问题的统一处理。所以,拦截器也是 AOP 的一种应用。AOP 是一种思想,拦截器是 AOP 思想的一种实现。Spring 框架实现了这种思想,提供了拦截器技术的相关接口。 同样的,统一数据返回格式和统一异常处理,也是 AOP 思想的一种实现。 简单来说:**AOP 是一种思想,是对某一类事情的集中处理**。 而 Spring AOP 是 Spring 框架提供的基于代理的面向切面编程实现,用于在**不修改源代码的前提下对程序进行增强**。比如说统一管理日志打印、权限控制、事务管理等重复、与业务无关的逻辑。 # Ⅱ. Spring AOP 快速入门 需求:统计图书系统各个接口方法的执行时间。 ## 1. 引入依赖 首先在 `pom.xml` 文件中添加配置: ``` org.springframework.boot spring-boot-starter-aop ``` ## 2. 编写 AOP 程序 记录 Controller 中每个方法的执行时间 ``` import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Slf4j @Aspect @Component public class TimeAspect { /** * 记录方法耗时 */ @Around("execution(* com.example.demo.controller.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable { // 记录方法执行开始时间 long begin = System.currentTimeMillis(); // 执行原始方法 Object result = pjp.proceed(); // 记录方法执行结束时间 long end = System.currentTimeMillis(); // 记录方法执行耗时 log.info(pjp.getSignature() + "执行耗时: {}ms", end - begin); return result; } } ``` 1. `@Aspect`:标识这是一个切面类 1. `@Around`:环绕通知,在目标方法的前后都会被执行,后面的表达式表示对哪些方法进行增强。 1. `ProceedingJoinPoint.proceed()`:执行原始方法,也就是指定要运行的那些方法 **💥注意事项**: 1. **如果没有返回 `pjp.proceed()` 的结果对象 `result`,则会导致程序死循环或功能异常**。 1. **在使用 `@Around` 时,必须调用 `pjp.proceed()` 方法**。 1. 这里是为了举例子,**通常不能直接拦截所有 `controller**`,否则浏览器访问的时候可能携带了其他访问资源,而后端因为拦截所有资源路径而导致不断重定向尝试,最后缓存炸了。 整个代码划分为三部分: ![](https://developer.qcloudimg.com/http-save/10147998/566e6d93b1b7c2c2cbd0aa64d26f8c99.png) 通过上面的程序,也可以感受到 AOP 面向切面编程的一些优势: - **代码无侵入**:不修改原始的业务方法,就可以对原始的业务方法进行了功能的增强或者是功能的改变 - **减少了重复代码** - **提高开发效率** - **维护方便**

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

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

资源下载

更多资源
Mario

Mario

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

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册