https://blog.csdn.net/lmb55/article/details/82470388 一、示例应用场景:对所有的web请求做切面来记录日志。 1、pom中引入SpringBoot的web模块和使用AOP相关的依赖: 其中: cglib包是用来动态代理用的,基于类的代理; aspectjrt和aspectjweaver是与aspectj相关的包,用来支持切面编程的; aspectjrt包是aspectj的runtime包; aspectjweaver是aspectj的织入包; 2、实现一个简单的web请求入口(实现传入name参数,返回“hello xxx”的功能): 这里写图片描述 注意:在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。使用过Spring注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。 因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。 3、定义切面类,实现web层的日志切面 要想把一个类变成切面类,需要两步, ① 在类上使用 @Component 注解 把切面类加入到IOC容器中 ② 在类上使用 @Aspect 注解 使之成为切面类 package com.example.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; /** * Created by lmb on 2018/9/5. */ @Aspect @Component public class WebLogAcpect { private Logger logger = LoggerFactory.getLogger(WebLogAcpect.class); /** * 定义切入点,切入点为com.example.aop下的所有函数 */ @Pointcut("execution(public * com.example.aop..*.*(..))") public void webLog(){} /** * 前置通知:在连接点之前执行的通知 * @param joinPoint * @throws Throwable */ @Before("webLog()") public void doBefore(JoinPoint joinPoint) throws Throwable { // 接收到请求,记录请求内容 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录下请求内容 logger.info("URL : " + request.getRequestURL().toString()); logger.info("HTTP_METHOD : " + request.getMethod()); logger.info("IP : " + request.getRemoteAddr()); logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret",pointcut = "webLog()") public void doAfterReturning(Object ret) throws Throwable { // 处理完请求,返回内容 logger.info("RESPONSE : " + ret); } } 以上的切面类通过 @Pointcut定义的切入点为com.example.aop包下的所有函数做切人,通过 @Before实现切入点的前置通知,通过 @AfterReturning记录请求返回的对象。 访问http://localhost:8004/hello?name=lmb得到控制台输出如下: 这里写图片描述 详细代码参见本人的Github:SpringBoot整合AOP 二、AOP支持的通知 1、前置通知@Before:在某连接点之前执行的通知,除非抛出一个异常,否则这个通知不能阻止连接点之前的执行流程。 /** * 前置通知,方法调用前被调用 * @param joinPoint/null */ @Before(value = POINT_CUT) public void before(JoinPoint joinPoint){ logger.info("前置通知"); //获取目标方法的参数信息 Object[] obj = joinPoint.getArgs(); //AOP代理类的信息 joinPoint.getThis(); //代理的目标对象 joinPoint.getTarget(); //用的最多 通知的签名 Signature signature = joinPoint.getSignature(); //代理的是哪一个方法 logger.info("代理的是哪一个方法"+signature.getName()); //AOP代理类的名字 logger.info("AOP代理类的名字"+signature.getDeclaringTypeName()); //AOP代理类的类(class)信息 signature.getDeclaringType(); //获取RequestAttributes RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //从获取RequestAttributes中获取HttpServletRequest的信息 HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST); //如果要获取Session信息的话,可以这样写: //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION); //获取请求参数 Enumeration<String> enumeration = request.getParameterNames(); Map<String,String> parameterMap = Maps.newHashMap(); while (enumeration.hasMoreElements()){ String parameter = enumeration.nextElement(); parameterMap.put(parameter,request.getParameter(parameter)); } String str = JSON.toJSONString(parameterMap); if(obj.length > 0) { logger.info("请求的参数信息为:"+str); } } 注意:这里用到了JoinPoint和RequestContextHolder。 1)、通过JoinPoint可以获得通知的签名信息,如目标方法名、目标方法参数信息等; 2)、通过RequestContextHolder来获取请求信息,Session信息; 2、后置通知@AfterReturning:在某连接点之后执行的通知,通常在一个匹配的方法返回的时候执行(可以在后置通知中绑定返回值)。 /** * 后置返回通知 * 这里需要注意的是: * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息 * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数 * returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行, * 对于returning对应的通知方法参数为Object类型将匹配任何目标返回值 * @param joinPoint * @param keys */ @AfterReturning(value = POINT_CUT,returning = "keys") public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){ logger.info("第一个后置返回通知的返回值:"+keys); } @AfterReturning(value = POINT_CUT,returning = "keys",argNames = "keys") public void doAfterReturningAdvice2(String keys){ logger.info("第二个后置返回通知的返回值:"+keys); } 3、后置异常通知@AfterThrowing:在方法抛出异常退出时执行的通知。 /** * 后置异常通知 * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法; * throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行, * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。 * @param joinPoint * @param exception */ @AfterThrowing(value = POINT_CUT,throwing = "exception") public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){ //目标方法名: logger.info(joinPoint.getSignature().getName()); if(exception instanceof NullPointerException){ logger.info("发生了空指针异常!!!!!"); } } 4、后置最终通知@After:当某连接点退出时执行的通知(不论是正常返回还是异常退出)。 /** * 后置最终通知(目标方法只要执行完了就会执行后置通知方法) * @param joinPoint */ @After(value = POINT_CUT) public void doAfterAdvice(JoinPoint joinPoint){ logger.info("后置最终通知执行了!!!!"); } 5、环绕通知@Around:包围一个连接点的通知,如方法调用等。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或者直接返回它自己的返回值或抛出异常来结束执行。 环绕通知最强大,也最麻烦,是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行几次方法等。环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed()方法也可能会被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的入参。 /** * 环绕通知: * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 */ @Around(value = POINT_CUT) public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){ logger.info("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName()); try { Object obj = proceedingJoinPoint.proceed(); return obj; } catch (Throwable throwable) { throwable.printStackTrace(); } return null; } 6、有时候我们定义切面的时候,切面中需要使用到目标对象的某个参数,如何使切面能得到目标对象的参数呢?可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对象的参数值将会被传递进来。 @Before("execution(* findById*(..)) &&" + "args(id,..)") public void twiceAsOld1(Long id){ System.err.println ("切面before执行了。。。。id==" + id); } 注意:任何通知方法都可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型,它是 JoinPoint 的一个子类)。JoinPoint接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。 三、切入点表达式 定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop...(..)) 切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常]) 其中[]内的是可选的,其它的还支持通配符的使用: 1) *:匹配所有字符 2) ..:一般用于匹配多个包,多个参数 3) +:表示类及其子类 4)运算符有:&&,||,! 切入点表达式关键词用例: 1)execution:用于匹配子表达式。 //匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意 @Pointcut(“execution(* com.cjm.model...(..))”) public void before(){} 2)within:用于匹配连接点所在的Java类或者包。 //匹配Person类中的所有方法 @Pointcut(“within(com.cjm.model.Person)”) public void before(){} //匹配com.cjm包及其子包中所有类中的所有方法 @Pointcut(“within(com.cjm..*)”) public void before(){} 3) this:用于向通知方法中传入代理对象的引用。 @Before(“before() && this(proxy)”) public void beforeAdvide(JoinPoint point, Object proxy){ //处理逻辑 } 4)target:用于向通知方法中传入目标对象的引用。 @Before(“before() && target(target) public void beforeAdvide(JoinPoint point, Object proxy){ //处理逻辑 } 5)args:用于将参数传入到通知方法中。 @Before(“before() && args(age,username)”) public void beforeAdvide(JoinPoint point, int age, String username){ //处理逻辑 } 6)@within :用于匹配在类一级使用了参数确定的注解的类,其所有方法都将被匹配。 @Pointcut(“@within(com.cjm.annotation.AdviceAnnotation)”) - 所有被@AdviceAnnotation标注的类都将匹配 public void before(){} 7)@target :和@within的功能类似,但必须要指定注解接口的保留策略为RUNTIME。 @Pointcut(“@target(com.cjm.annotation.AdviceAnnotation)”) public void before(){} 8)@args :传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。 @Before(“@args(com.cjm.annotation.AdviceAnnotation)”) public void beforeAdvide(JoinPoint point){ //处理逻辑 } 9)@annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。 @Pointcut(“@annotation(com.cjm.annotation.AdviceAnnotation)”) public void before(){} 10)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。 @Pointcut(“bean(person)”) public void before(){} 参考资料:https://www.cnblogs.com/lic309/p/4079194.html
https://www.cnblogs.com/javazhiyin/p/9993299.html
Spring全家桶系列–SpringBoot之AOP详解
- //本文作者:cuifuan
- //本文将收录到菜单栏:《Spring全家桶》专栏中
面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。
OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。
准备工作
首先,使用AOP要在build.gradle中加入依赖
//引入AOP依赖 compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"
然后在application.yml中加入
spring: aop: proxy-target-class: true
[email protected] 切入点
定义一个切点。
例如我们要在一个方法加上切入点,根据方法的返回的对象,方法名,修饰词来写成一个表达式或者是具体的名字
我们现在来定义一个切点
package com.example.aop; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 类定义为切面类 */ @Aspect @Component public class AopTestController { private static final Logger logger = LoggerFactory.getLogger(AopTestController.class); /** * 定义一个切点 */ @Pointcut(value = "execution(public String test (..))") public void cutOffPoint() { } }
这里的切点定义的方法是
@GetMapping("hello") public String test(){ logger.info("欢迎关注Java知音"); return "i love java"; }
如果你想写个切入点在所有返回对象为Area的方法,如下
@Pointcut("execution(public com.example.entity.Area (..))")
等很多写法,也可以直接作用在某些包下
注意:private修饰的无法拦截
[email protected]前置通知
在切入点开始处切入内容
在之前的AopTestController类中加入对test方法的前置通知
@Before("cutOffPoint()") public void beforeTest(){ logger.info("我在test方法之前执行"); }
这里@Before里的值就是切入点所注解的方法名
在方法左侧出现的图标跟过去以后就是所要通知的方法 这里就是配置正确了,我们来浏览器调用一下方法
联想一下,这样的效果可以用在哪里,想像如果要扩展一些代码,在不需要动源代码的基础之上就可以进行拓展,美滋滋
[email protected] 后置通知
和前置通知相反,在切入点之后执行
@After("cutOffPoint()") public void doAfter(){ logger.info("我是在test之后执行的"); }
控制台执行结果
这里定义一个通知需要重启启动类,而修改通知方法的内容是可以热部署的
[email protected]环绕通知
和前两个写法不同,实现的效果包含了前置和后置通知。
当使用环绕通知时,proceed方法必须调用,否则拦截到的方法就不会再执行了
环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的
ThreadLocal<Long> startTime = new ThreadLocal<>(); @Around("cutOffPoint()") public Object doAround(ProceedingJoinPoint pjp){ startTime.set(System.currentTimeMillis()); logger.info("我是环绕通知执行"); Object obj; try{ obj = pjp.proceed(); logger.info("执行返回值 : " + obj); logger.info(pjp.getSignature().getName()+"方法执行耗时: " + (System.currentTimeMillis() - startTime.get())); } catch (Throwable throwable) { obj=throwable.toString(); } return obj; }
执行结果:
1.环绕通知可以项目做全局异常处理
2.日志记录
3.用来做数据全局缓存
4.全局的事物处理 等
[email protected]
切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了
/** * 执行完请求可以做的 * @param result * @throws Throwable */ @AfterReturning(returning = "result", pointcut = "cutOffPoint()") public void doAfterReturning(Object result) throws Throwable { logger.info("大家好,我是@AfterReturning,他们都秀完了,该我上场了"); }
执行结果
应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等
[email protected]
这个是在切入执行报错的时候执行
// 声明错误e时指定的抛错类型法必会抛出指定类型的异常 // 此处将e的类型声明为Throwable,对抛出的异常不加限制 @AfterThrowing(throwing = "e",pointcut = "cutOffPoint()") public void doAfterReturning(Throwable e) { logger.info("大家好,我是@AfterThrowing,他们犯的错误,我来背锅"); logger.info("错误信息"+e.getMessage()); }
在其他切入内容中随意整个错误出来,制造一个环境。
下面是@AfterThrowing的执行结果
7.AOP用在全局异常处理
定义切入点拦截ResultBean或者PageResultBean
@Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))") public void handlerPageResultBeanMethod() { } @Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))") public void handlerResultBeanMethod() { }
下面是AopController.java
package com.example.aop; import com.example.beans.PageResultBean; import com.example.beans.ResultBean; import com.example.entity.UnloginException; import com.example.exception.CheckException; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 使用@Aspect注解将此类定义为切面类 * 根据晓风轻著的ControllerAOP所修改 * 晓风轻大佬(很大的佬哥了):https://xwjie.github.io/ */ @Aspect @Component public class AopController { private static final Logger logger = LoggerFactory.getLogger(AopController.class); ThreadLocal<ResultBean> resultBeanThreadLocal = new ThreadLocal<>(); ThreadLocal<PageResultBean<?>> pageResultBeanThreadLocal = new ThreadLocal<>(); ThreadLocal<Long> start = new ThreadLocal<>(); /** * 定义一个切点 */ @Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))") public void handlerPageResultBeanMethod() { } @Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))") public void handlerResultBeanMethod() { } @Around("handlerPageResultBeanMethod()") public Object handlerPageResultBeanMethod(ProceedingJoinPoint pjp) { start.set(System.currentTimeMillis()); try { pageResultBeanThreadLocal.set((PageResultBean<?>)pjp.proceed()); logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get())); } catch (Throwable e) { ResultBean<?> resultBean = handlerException(pjp , e); pageResultBeanThreadLocal.set(new PageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode())); } return pageResultBeanThreadLocal.get(); } @Around("handlerResultBeanMethod()") public Object handlerResultBeanMethod(ProceedingJoinPoint pjp) { start.set(System.currentTimeMillis()); try { resultBeanThreadLocal.set((ResultBean<?>)pjp.proceed()); logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get())); } catch (Throwable e) { resultBeanThreadLocal.set(handlerException(pjp , e)); } return resultBeanThreadLocal.get(); } /** * 封装异常信息,注意区分已知异常(自己抛出的)和未知异常 */ private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) { ResultBean<?> result = new PageResultBean(); logger.error(pjp.getSignature() + " error ", e); // 已知异常 if (e instanceof CheckException) { result.setMsg(e.getLocalizedMessage()); result.setCode(ResultBean.FAIL); } else if (e instanceof UnloginException) { result.setMsg("Unlogin"); result.setCode(ResultBean.NO_LOGIN); } else { result.setMsg(e.toString()); result.setCode(ResultBean.FAIL); } return result; } }
用上面的环绕通知可以对所有返回ResultBean或者PageResultBean的方法进行切入,这样子就不用在业务层去捕捉错误了,只需要去打印自己的info日志。
看下面一段代码
@Transactional @Override public int insertSelective(Area record) { record.setAddress("test"); record.setPostalcode(88888); record.setType(3); int i=0; try { i = areaMapper.insertSelective(record); }catch (Exception e){ logger.error("AreaServiceImpl insertSelective error:"+e.getMessage()); } return i; }
假如上面的插入操作失败出错了? 你认为会回滚吗?
答案是:不会。
为什么?
因为你把错误捕捉了,事物没检测到异常就不会回滚。
那么怎么才能回滚呢?
在catch里加throw new RuntimeException().
可是那么多业务方法每个设计修改的操作都加,代码繁琐,怎么进行处理呢?
在这里用到上面的AOP切入处理,错误不用管,直接抛,抛到控制层进行处理,这样的话,接口调用的时候,出错了,接口不会什么都不返回,而是会返回给你错误代码,以及错误信息,便于开发人员查错。
8.以上用的是log4j2的日志处理
先移除springboot自带的log日志处理
在build.gradle中增加
configurations { providedRuntime // 去除SpringBoot自带的日志 all*.exclude group: ‘org.springframework.boot‘, module: ‘spring-boot-starter-logging‘ } ext { springBootVersion = ‘2.0.1.RELEASE‘ } dependencies { compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}" }
然后在application.yml中增加
logging: level: com: example: dao: debug config: classpath:log4j2-spring.xml
log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="INFO" monitorInterval="30"> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用--> <File name="Test" fileName="logs/test.log" append="false"> <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/> </File> <RollingFile name="RollingFileInfo" fileName="logs/log.log" filePattern="logs/info.log.%d{yyyy-MM-dd}"> <!-- 只接受level=INFO以上的日志 --> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/> <Policies> <TimeBasedTriggeringPolicy modulate="true" interval="1"/> <SizeBasedTriggeringPolicy/> </Policies> </RollingFile> <RollingFile name="RollingFileError" fileName="logs/error.log" filePattern="logs/error.log.%d{yyyy-MM-dd}"> <!-- 只接受level=WARN以上的日志 --> <Filters> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /> </Filters> <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/> <Policies> <TimeBasedTriggeringPolicy modulate="true" interval="1"/> <SizeBasedTriggeringPolicy/> </Policies> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.springframework" level="INFO"></logger> <logger name="org.mybatis" level="INFO"></logger> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="Test"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>
之后在你要打印日志的类中增加
private static final Logger logger = LoggerFactory.getLogger(你的类名.class); public static void main(String[] args) { logger.error("error级别日志"); logger.warn("warning级别日志"); logger.info("info级别日志"); }
有了日志后就很方便了,在你的方法接收对象时打印下,然后执行了逻辑之后打印下, 出错之后很明确了,就会很少去Debug的,养成多打日志的好习惯,多打印一点info级别的日志,用来在开发环境使用,在上线的时候把打印的最低级别设置为warning,这样你的info级别日志也不会影响到项目的重要Bug的打印
写这个博客的时候我也在同时跑着这个项目,有时候会出现一些错误,例如jar包版本,业务层引用无效,AOP设置不生效等等,也同时在排查解决,如果你遇到了同样的错误,可以去我的GitHub联系我,如小弟有时间或许也能帮到你,谢谢
Github地址:https://github.com/cuifuan
原文地址:https://www.cnblogs.com/kelelipeng/p/11389391.html