• 自己定义 Advisor 实现自定义注解修饰的方法增强


    需求:这里我们想通过自己定义 Advisor 来实现自定义注解修饰的方法增强,直接开干

    1、先自定义注解
    /**
     * 定义方法级别的注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DBMasterAnno {
    	String value() default "on";
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2、实现 Advisor 的接口(实现其子类 PointcutAdvisor 即可)

    Advisor 必然包含两个重要元素,Pointcut 和 Advice、其中 Pointcut 使用来匹配某些类是否能够被我的这个 Advisor 增强,而实际增强逻辑是在 Advice 中实现,而最有名的 Advice 子类就是 MethodInterceptor。

    实现代码如下,里面包含了这两个重要的元素 Pointcut + Advice

    @Component
    public class MyAdvisor implements PointcutAdvisor {
    
    	/**
    	 * 匹配对象,专门用来匹配方法、类、参数是否需要被增强
    	 */
    	@Autowired
    	private MyDbMasterPointcut myDbMasterPointcut;
    
    	@Autowired
    	private DbMasterAdvice dbMasterAdvice;
    
    	@Override
    	public Pointcut getPointcut() {
    		return myDbMasterPointcut;
    	}
    
    	@Override
    	public Advice getAdvice() {
    		return dbMasterAdvice;
    	}
    
    	@Override
    	public boolean isPerInstance() {
    		return false;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    3、实现 Pointcut 接口

    Pointcut 接口作用是匹配和过滤作用的,那么这种匹配一般会有三种:类、方法、参数。类的匹配过程交给了 ClassFilter 类匹配器、方法和参数的交给了 MethodMatcher 方法匹配器,如下代码直接实现了这两个接口,并且精确到了参数级别的校验。

    这个 MyDbMasterPointcut 主要实现了功能:拦截被 @DBMasterAnno 注解修饰的方法、并且参数还必须要等于 “小明” 该方法才可以被 Advisor 增强,否则不增强。

    /**
     * 这里不直接在这里实现 MethodMatcher,ClassFilter 接口也行的
     */
    @Component
    public class MyDbMasterPointcut implements Pointcut, MethodMatcher,ClassFilter {
    	/**
    	 * 这里是核心匹配过程,可能是个非常复杂的匹配过程
    	 * 这个只能匹配到方法级别,也就是只能判断这个方法是否被什么修饰之类的
    	 */
    	@Override
    	public boolean matches(Method method, Class<?> targetClass) {
    		/**
    		 * 可以在这里拿到原始方法,判断这个方法上是否标注了注解才可以
    		 * method 这个方法对象是接口上的,注意了,接口上没有任何的注解修饰,拿到死都拿不到
    		 * 所以可以通过这个 AopUtils 工具类获取到实习类上的 method 对象
    		 * 或者可以通过 targetClass 字节码文件获取到都可以,方法很多
    		 */
    		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    		if (specificMethod.isAnnotationPresent(DBMasterAnno.class)) {
    			System.out.println("匹配到了有 @DBMaster 修饰的方法,可以对其进行拦截操作");
    			return true;
    		}
    		return false;
    	}
    
    
    	/**
    	 * 这里是核心匹配过程,可能是个非常复杂的匹配过程
    	 * 这个不仅可以匹配方法,还可以匹配参数级别
    	 */
    	@Override
    	public boolean matches(Method method, Class<?> targetClass, Object... args) {
    		System.out.println("-------> 方法匹配成功了,然后开始参数级别的 matches() ...");
    		// 先检验方法是否符合要求
    		if (method.getName().equalsIgnoreCase("crateOrder")) {
    			// 然后再检验参数是否符合要求
    			String name = (String) args[0];
    			if ("小明".equalsIgnoreCase(name)) {
    				return true;
    			}
    		}
    		return false;
    	}
    
    	@Override
    	public boolean isRuntime() {
    		/**
    		 * 这里返回 true 参数级别的校验方法才会执行
    		 * 这里方法 false 下面就不会被执行,看源码就知道了
    		 */
    		return true;
    	}
    
    
    	@Override
    	public ClassFilter getClassFilter() {
    		/**
    		 * 当前自己就是 ClassFilter 所以直接返回 this
    		 * 但是由于我们的 @DBMaster 注解是对方法起作用的,所以这里就没必要对类进行校验操作了
    		 * 或者没必要实现 ClassFilter 接口,直接在 getClassFilter() 方法上返回 ClassFilter.TRUE
    		 */
    		return this;
    		//return ClassFilter.TRUE;
    	}
    
    	@Override
    	public MethodMatcher getMethodMatcher() {
    		return this;
    	}
    
    	/**
    	 * 重写 ClassFilter 方法 对类的匹配
    	 * 但是由于我们的 @DBMaster 注解是对方法起作用的,所以这里就没必要对类进行校验操作了
    	 * 或者没必要实现 ClassFilter 接口,直接在 getClassFilter() 方法上返回 ClassFilter.TRUE
    	 */
    	@Override
    	public boolean matches(Class<?> clazz) {
    		return true;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    4、实现 Advice 接口

    这里实现的是 Advice 子类 MethodInterceptor 接口,注意这里执行完拦截逻辑,记得往下传递,否则到这里就结束了,类似 SpringMVC 拦截器功能

    @Component
    public class DbMasterAdvice implements MethodInterceptor {
    	@Nullable
    	@Override
    	public Object invoke(@Nonnull MethodInvocation invocation) throws Throwable {
    		System.out.println("DbMasterAdvice 被调用了,记得要火炬传递哦.....");
    		return invocation.proceed();
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    5、编写 Config 入口

    这里不用配置 @Aspect,因为我们不用 Spring 提供的 Advice 功能,都是用我们自定义的 Advice,可以更灵活的自己控制

    @Configuration
    @ComponentScan({"com.gwm.spring.pointcut"})
    @EnableAspectJAutoProxy
    public class AdviceConfig {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    6、Service 实现

    这里我们使用自定定义的注解 @DBMasterAnno 修饰 crateOrder() 方法,表示只有这个方法需要被 Advisor 增强。

    public interface AopProxyOrderService {
    	void crateOrder(String name);
    }
    @Service
    public class AopProxyOrderServiceImpl implements AopProxyOrderService {
    
    	/**
    	 * 这里使用自己定义的注解
    	 */
    	@DBMasterAnno
    	@Override
    	public void crateOrder(String name) {
    		System.out.println("我是目标方法 invoke crateOrder method...");
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    7、开始测试
    	public static void main(String[] args) {
    
    		ApplicationContext context = new AnnotationConfigApplicationContext(JavAspect.class);
    		AopProxyOrderService bean = context.getBean(AopProxyOrderService.class);
    		bean.crateOrder("小明");
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    运行结果如下:

    DbMasterAdvice 被调用了,记得要火炬传递哦.....
    我是目标方法 crateOrder,我被调用了...
    
    • 1
    • 2

    这里需要注意两个点:

    • 在 MethodMatcher 方法匹配过程中,传过来的 method 对象是接口的 method 对象,和具体实现了的 method 对象不是同一个,查看他们两个的 hashCode() 是不一样的,这点一定要注意,因为接口 method 没有任何注解修饰,所以判断不了注解存不存在。所以可以选择通过获取 targetClass 字节码文件获取具体实现类的 method、或者通过 AopUtils 工具类也可以(在上面的 MyDbMasterPointcut 代码中已经注释的非常清楚)
    • MyDbMasterPointcut 类中的 isRuntime() 开关要返回 true ,才会执行参数级别的匹配,否则不会。
  • 相关阅读:
    MySQL:锁机制
    强静态类型,真的无敌
    基于C++的通讯发报应用课程设计
    Less预处理——变量和嵌套
    redis 学习记录
    【看好了】如何使用fiddler实现手机抓包,Filters过滤器!
    总结篇:二叉树遍历
    智能热水器丨打造智能家居新体验
    Mysql的安装配置教程(详细)
    从C语言到C++_40(多线程相关)C++线程接口+线程安全问题加锁(shared_ptr+STL+单例)
  • 原文地址:https://blog.csdn.net/qq_35971258/article/details/126464592