需求
因为想写一个通用的 log 日志拦截组件,所以写了一个统一 aspect 切面。
aspect 代码
package com.github.houbb.auto.log.spring.aop;
import com.github.houbb.aop.spring.util.SpringAopUtil;
import com.github.houbb.auto.log.annotation.AutoLog;
import com.github.houbb.auto.log.api.IAutoLogContext;
import com.github.houbb.auto.log.core.bs.AutoLogBs;
import com.github.houbb.auto.log.spring.context.SpringAopAutoLogContext;
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.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 这是一种写法
* 自动日志输出 aop
* @author binbin.hou
* @since 0.0.3
*/
@Aspect
@Component
@EnableAspectJAutoProxy
public class AutoLogAop {
/**
*
* 切面方法:
*/
@Pointcut("@within(com.github.houbb.auto.log.annotation.AutoLog)" +
"|| @annotation(com.github.houbb.auto.log.annotation.AutoLog)")
public void autoLogPointcut() {
}
/**
* 执行核心方法
*
* 相当于 MethodInterceptor
*
* @param point 切点
* @return 结果
* @throws Throwable 异常信息
* @since 0.0.3
*/
@Around("autoLogPointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = SpringAopUtil.getCurrentMethod(point);
AutoLog autoLog = AnnotationUtils.getAnnotation(method, AutoLog.class);
//获取当前类注解信息
if(autoLog == null) {
autoLog = SpringAopUtil.getClassAnnotation(point, AutoLog.class);
}
// 如果不存在
if(autoLog == null) {
return point.proceed();
}
// 如果存在,则执行切面的逻辑
IAutoLogContext logContext = SpringAopAutoLogContext.newInstance()
.method(method)
.autoLog(autoLog)
.point(point);
return AutoLogBs.newInstance()
.context(logContext)
.execute();
}
}
pointcut 的不足
一开始把扫描的范围配置的很大,但是这样存在一定的性能问题。
于是改成了基于注解的方式,指定了注解的类和方法才会拦截。
但是这依然是一个问题,这样用户使用的时候会依然觉得不够遍历。
那么,如何可以让用户动态指定呢?
注解的属性必须为常量
第一个想法就是让 pointcut 的切面信息直接动态读取配置。
但是发现注解的信息必须是一个常量,这个要如何动态修改呢?
pointcut 基础知识
格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
修饰符匹配(modifier-pattern?)
返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
类路径匹配(declaring-type-pattern?)
方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
异常类型匹配(throws-pattern?)
其中后面跟着“?”的是可选项
1)execution(* *(..))
//表示匹配所有方法
2)execution(public * com. savage.service.UserService.*(..))
//表示匹配com.savage.server.UserService中所有的公有方法
3)execution(* com.savage.server..*.*(..))
//表示匹配com.savage.server包及其子包下的所有方法
实现方式 1
如何将aop中的pointcut值从配置文件中读取
首先,我们可以先创建一个类来实现 MethodInterceptor 类 :
class LogAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before method");//这里做你的before操作
Object result = invocation.proceed();
System.out.println("After method");//这里做你的after操作
return result;
}
}
然后创建一个Configuration类,创建Bean:
@Configuration
public class ConfigurableAdvisorConfig {
@Value("${pointcut.property}")
private String pointcut;
@Bean
public AspectJExpressionPointcutAdvisor configurabledvisor() {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression(pointcut);
advisor.setAdvice(new LogAdvice ());
return advisor;
}
}
这里面的 pointcut.property值来自于你的application.properties 等配置文件。
这样,各项目只须要引用该jar,然后在配置文件中指定要拦截的pointcut就可以了。
spring 中的 pointcut-实现方式2
//Pointcut表示式(可以使用&& || ! 这三个运算)
@Pointcut("execution(* com.github.houbb.*(..))")
//Point签名
private void log(){}
实现动态 pointcut 的思路
- 关于SpringAop
AspectJ方式织入的核心,是一个 BeanPostProcess(会扫描所有的Pointcut与遍历所有Bean,并对需要的Bean进行织入-自动代理,当对象实例化的时候,为其生成代理对象并返回)
- 思路
在Aop的BeanPostProcess执行之前( springApplication.run之前),使用javassist修改目标Aop类字节码,动态设置@Pointcut,设置 value为我们自己动态查询到的值。
- 其他
由于Spring boot的类加载机制,运行时javassist会扫描不到包,要通过insertClassPath添加扫描路径
修改 @Pointcut
的切点值后,通过toClass覆盖原有类,需要通过类加载器重新加载。
实现
springboot
@SpringBootApplication
public class UidServerApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
AspectPoincutScan();
SpringApplication springApplication = new SpringApplication(UidServerApplication.class);
springApplication.addInitializers(new UidApplicationContextInitializer());
springApplication.run(args);
}
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
AspectPoincutScan();
return builder.sources(UidServerApplication.class)
.initializers(new UidApplicationContextInitializer())
.listeners(new UidApplicationRefreshedListener())
.listeners(new UidApplicationCloseListener());
}
private static void AspectPoincutScan() {
try {
ClassPool pool = ClassPool.getDefault();
// 添加包的扫描路径
ClassClassPath classPath = new ClassClassPath(UidServerApplication.class);
pool.insertClassPath(classPath);
//获取要修改的Class
CtClass ct = pool.get("com.github.houbb.auto.log.spring.aop.AutoLogAop");
CtMethod[] cms = ct.getDeclaredMethods();
for (CtMethod cm : cms) {
//找到@pointcut 注解的方法
if (cm.getName().equals("pointcut")) {
MethodInfo methodInfo = cm.getMethodInfo();
ConstPool cPool = methodInfo.getConstPool();
AnnotationsAttribute attribute = new AnnotationsAttribute(cPool, AnnotationsAttribute.visibleTag);
//获取@pointcut 注解,修改其value值
Annotation annotation = new Annotation("org.aspectj.lang.annotation.Pointcut", cPool);
annotation.addMemberValue("value", new StringMemberValue("execution(xxxx", cPool));
attribute.setAnnotation(annotation);
methodInfo.addAttribute(attribute);
//覆盖原有类
ct.toClass();
//使用类加载器重新加载Aop类
pool.getClassLoader().loadClass("com.github.houbb.auto.log.spring.aop.AutoLogAop");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
aop
编程式AOP实现动态PointCut
PonitCut
public static Pointcut getAdapterServicePointcut(){
AspectJExpressionPointcut adapterPointcut = new AspectJExpressionPointcut();
//从配置文件中获取PointCut表达式
adapterPointcut.setExpression(MonitorPropertyConfig.getPoinitcutAdapter());
return adapterPointcut;}
//扩展Spring中AbstractBeanFactoryPointcutAdvisor
public class AdapterServiceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Override
public Pointcut getPointcut() {
return getAdapterServicePointcut();
}
}
- Advice
public class AdapterServiceMonitorInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//做一些操作...
}
}
- 配置Advisor Bean
@Configuration
public class MonitorProxyConfiguration {
@Bean(name = "adapterServiceAdvisor")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AdapterServiceAdvisor adapterServiceAdvisor() {
AdapterServiceAdvisor advisor = new AdapterServiceAdvisor();
advisor.setAdviceBeanName("adapterServiceAdvice");
advisor.setAdvice(new AdapterServiceMonitorInterceptor());
advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
return advisor;
}
}