朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP (5)

朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP


把配置改为true重新观察可以看到变为了CglibMethodInvocation:

朱晔和你聊Spring系列S1E6:容易犯错的Spring AOP


我们把开关改为false,然后切换到注入实现,运行程序会得到如下错误提示,意思就是我我们走JDK代理的话不能注入实现,需要注入接口:

The bean 'myServiceImpl' could not be injected as a 'me.josephzhu.spring101aop.MyServiceImpl' because it is a JDK dynamic proxy that implements: me.josephzhu.spring101aop.MyService

我们修改我们的MyServiceImpl,去掉实现接口的代码和@Override注解,使之成为一个普通的类,重新运行程序可以看到我们的代理方式自动降级为了CGLIB方式(虽然spring.aop.proxy-target-class参数我们现在设置的是false)。

使用AOP无缝实现日志+异常+打点

现在我们来实现一个复杂点的切面的例子。我们知道,出错记录异常信息,对于方法调用记录打点信息(如果不知道什么是打点可以参看《朱晔的互联网架构实践心得S1E4:简单好用的监控六兄弟》),甚至有的时候为了排查问题需要记录方法的入参和返回,这三个事情是我们经常需要做的和业务逻辑无关的事情,我们可以尝试使用AOP的方式一键切入这三个事情的实现,在业务代码无感知的情况下做好监控和打点。
首先实现我们的注解,通过这个注解我们可以细化控制一些功能:

package me.josephzhu.spring101aop; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Metrics { /** * 是否在成功执行方法后打点 * @return */ boolean recordSuccessMetrics() default true; /** * 是否在执行方法出错时打点 * @return */ boolean recordFailMetrics() default true; /** * 是否记录请求参数 * @return */ boolean logParameters() default true; /** * 是否记录返回值 * @return */ boolean logReturn() default true; /** * 是否记录异常 * @return */ boolean logException() default true; /** * 是否屏蔽异常返回默认值 * @return */ boolean ignoreException() default false; }

下面我们就来实现这个切面:

package me.josephzhu.spring101aop; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.time.Duration; import java.time.Instant; @Aspect @Component @Slf4j public class MetricsAspect { private static ObjectMapper objectMapper = new ObjectMapper(); @Around("@annotation(me.josephzhu.spring101aop.Metrics) || @within(org.springframework.stereotype.Controller)") public Object metrics(ProceedingJoinPoint pjp) throws Throwable { //1 MethodSignature signature = (MethodSignature) pjp.getSignature(); Metrics metrics; String name; if (signature.getDeclaringType().isInterface()) { Class implClass = pjp.getTarget().getClass(); Method method = implClass.getMethod(signature.getName(), signature.getParameterTypes()); metrics = method.getDeclaredAnnotation(Metrics.class); name = String.format("【%s】【%s】", implClass.toString(), method.toString()); } else { metrics = signature.getMethod().getAnnotation(Metrics.class); name = String.format("【%s】【%s】", signature.getDeclaringType().toString(), signature.toLongString()); } //2 if (metrics == null) metrics = new Metrics() { @Override public boolean logException() { return true; } @Override public boolean logParameters() { return true; } @Override public boolean logReturn() { return true; } @Override public boolean recordFailMetrics() { return true; } @Override public boolean recordSuccessMetrics() { return true; } @Override public boolean ignoreException() { return false; } @Override public Class<? extends Annotation> annotationType() { return Metrics.class; } }; RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes != null) { HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); if (request != null) name += String.format("【%s】", request.getRequestURL().toString()); } //3 if (metrics.logParameters()) log.info(String.format("【入参日志】调用 %s 的参数是:【%s】", name, objectMapper.writeValueAsString(pjp.getArgs()))); //4 Object returnValue; Instant start = Instant.now(); try { returnValue = pjp.proceed(); if (metrics.recordSuccessMetrics()) log.info(String.format("【成功打点】调用 %s 成功,耗时:%s", name, Duration.between(Instant.now(), start).toString())); } catch (Exception ex) { if (metrics.recordFailMetrics()) log.info(String.format("【失败打点】调用 %s 失败,耗时:%s", name, Duration.between(Instant.now(), start).toString())); if (metrics.logException()) log.error(String.format("【异常日志】调用 %s 出现异常!", name), ex); if (metrics.ignoreException()) returnValue = getDefaultValue(signature.getReturnType().toString()); else throw ex; } //5 if (metrics.logReturn()) log.info(String.format("【出参日志】调用 %s 的返回是:【%s】", name, returnValue)); return returnValue; } private static Object getDefaultValue(String clazz) { if (clazz.equals("boolean")) { return false; } else if (clazz.equals("char")) { return '\u0000'; } else if (clazz.equals("byte")) { return 0; } else if (clazz.equals("short")) { return 0; } else if (clazz.equals("int")) { return 0; } else if (clazz.equals("long")) { return 0L; } else if (clazz.equals("flat")) { return 0.0F; } else if (clazz.equals("double")) { return 0.0D; } else { return null; } } }

看上去代码量很多,其实实现比较简单:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzfgzw.html