上一篇我们简单介绍了一下AOP中的一些相关术语、以及Advice接口下的一些增强实现,但是这里会有一个问题,那就是增强方法还会被应用到目标类的所有接口。修改一下上一节的测试类并运行。(本篇很多简介摘自Spring3.X企业应用开发实战,实在想不出来如何去介绍这些概念类的信息。。。)
1.Pointcut概念的引入及简介
@Test public void test5() { // 前置增强 // 1、实例化bean和增强 Animal dog = new Dog(); MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice(); // 2、创建ProxyFactory并设置代理目标和增强 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(dog); proxyFactory.addAdvice(advice); // 3、生成代理实例 Animal proxyDog = (Animal) proxyFactory.getProxy(); proxyDog.sayHello("二哈", 3); System.out.println("\n\n"); proxyDog.sayException("二哈", 3); }
==前置增强 ==方法名:sayHello ==第1参数:二哈 ==第2参数:3 ==目标类信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc ==名字:二哈 年龄:3 ==前置增强 ==方法名:sayException ==第1参数:二哈 ==第2参数:3 ==目标类信息:com.lyc.cn.v2.day04.advisor.Dog@65e579dc ==名字:二哈 年龄:3 java.lang.ArithmeticException: / by zero at com.lyc.cn.v2.day04.advisor.Dog.sayException(Dog.java:17) at com.lyc.cn.v2.day04.advisor.Dog$$FastClassBySpringCGLIB$$a974b1ec.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
从测试结果上看,只要我们调用了Animal类的接口,增强方法都会被应用到目标类的方法上,这样的增强效果肯定不能满足我们实际的应用,那么这个时候就需要引入一个概念----切入点(Pointcut)
。通过切入点就可以有选择的将增强应用到目标类的方法上,而目标类的方法就是我们上一节说的连接点,即sayHello和sayException方法,目标类就是要被增强的类,即Dog类,所以增强描述了连接点的方位信息,例如织入到方法之前、方法之后,而切入点则进一步的描述了织入到那些类的那些方法上。到这里相信大家对连接点、切入点、增强、目标对象等概念有了更为深刻的理解。
但是这又带了一个新的问题,那就是如何将切入点定位到连接点,换言之,就是切入点如何知道自己要被应用到那些连接点上呢?
接下来就有必要看一下Pointcut接口的源码了。
- Pointcut接口
public interface Pointcut { /** * 返回当前切点匹配的类 */ ClassFilter getClassFilter(); /** * 返回当前切点匹配的方法 */ MethodMatcher getMethodMatcher(); /** * Canonical Pointcut instance that always matches. */ Pointcut TRUE = TruePointcut.INSTANCE; }
Pointcut接口的定义非常简单,仅仅包含了ClassFilter和MethodMatcher的定义,ClassFilter可以定位到具体的类上,MethodMatcher可以定位到具体的方法上,这样通过Pointcut我们就可以将将增强织入到特定类的特定方法上了。再来看下ClassFilter和MethodMatcher的定义:
- ClassFilter接口
public interface ClassFilter { /** * 切入点应该应用于给定的接口还是目标类 * Should the pointcut apply to the given interface or target class? * @param clazz the candidate target class 候选目标类 * @return whether the advice should apply to the given target class 增强是否应用于目标类 */ boolean matches(Class<?> clazz); /** * Canonical instance of a ClassFilter that matches all classes. */ ClassFilter TRUE = TrueClassFilter.INSTANCE; }
- MethodMatcher接口
public interface MethodMatcher { /** * 静态方法匹配判断 */ boolean matches(Method method, Class<?> targetClass); /** * 判断静态方法匹配或动态方法匹配 * true:动态方法匹配 * false:静态方法匹配 */ boolean isRuntime(); /** * 动态方法匹配判断 */ boolean matches(Method method, Class<?> targetClass, Object... args); /** * Canonical instance that matches all methods. */ MethodMatcher TRUE = TrueMethodMatcher.INSTANCE; }
虽然还没有看到以上三个接口的具体实现,但是现在我们只要知道Pointcut接口提供了这样的功能就行了。在MethodMatcher接口中又引入了一个新的概念,方法匹配模式,Spring支持两种方法匹配器:
- 静态方法匹模式:所谓静态方法匹配器,仅对方法名签名(包括方法名和入参类型及顺序)进行匹配。
- 动态方法匹配器:动态方法匹配器会在运行期方法检查入参的值。 静态匹配仅会判断一次,而动态匹配因为每次调用方法的入参可能不一样,所以每次调用方法都必须判断。
接下来简单介绍一下Spring提供的切点类型:
- 静态方法切点-->org.springframework.aop.support.StaticMethodMatcherPointcut
静态方法切点的抽象基类,默认情况下匹配所有的类。最常用的两个子类NameMatchMethodPointcut和 AbstractRegexpMethodPointcut , 前者提供简单字符串匹配方法签名,后者使用正则表达式匹配方法签名。 - 动态方法切点-->org.springframework.aop.support.DynamicMethodMatcherPointcut
动态方法切点的抽象基类,默认情况下匹配所有的类 - 注解切点-->org.springframework.aop.support.annotation.AnnotationMatchingPointcut
- 表达式切点-->org.springframework.aop.support.ExpressionPointcut
提供了对AspectJ切点表达式语法的支持 - 流程切点-->org.springframework.aop.support.ControlFlowPointcut
该切点是一个比较特殊的节点,它根据程序执行的堆栈信息查看目标方法是否由某一个方法直接或间接发起调用,一次来判断是否为匹配的链接点 - 复合切点-->org.springframework.aop.support.ComposablePointcut
该类是为实现创建多个切点而提供的操作类
2.切面简介
由于增强包括横切代码,又包含部分连接点信息(方法前、方法后主方位信息),所以可以仅通过增强类生成一个切面。 但切点仅仅代表目标类连接点的部分信息(类和方法的定位),所以仅有切点无法制作出一个切面,必须结合增强才能制作出切面。Spring使用org.springframework.aop.Advisor接口标识切面概念,一个切面同时包含横切代码和连接点信息。
切面可以分为3类:一般切面、切点切面、引介切面
- 一般切面Advisor
org.springframework.aop.Advisor代表一般切面,仅包含一个Advice ,因为Advice包含了横切代码和连接点信息,所以Advice本身一个简单的切面,只不过它代表的横切的连接点是所有目标类的所有方法,因为这个横切面太宽泛,所以一般不会直接使用。 - 切点切面PointcutAdvisororg.springframework.aop.PointcutAdvisor ,代表具有切点的切面,包括Advice和Pointcut两个类,这样就可以通过类、方法名以及方位等信息灵活的定义切面的连接点,提供更具实用性的切面。PointcutAdvisor主要有6个具体的实现类:
- DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice定义一个切面,唯一不支持的就是引介的切面类型,一般可以通过扩展该类实现自定义的切面
- NameMatchMethodPointcutAdvisor:通过该类可以定义按方法名定义切点的切面
- AspectJExpressionPointcutAdvisor:用于AspectJ切点表达式定义切点的切面
- StaticMethodMatcherPointcutAdvisor:静态方法匹配器切点定义的切面,默认情况下匹配所有的的目标类
- AspectJPointcutAdvisor:用于AspectJ语法定义切点的切面
- 引介切面IntroductionAdvisor
org.springframework.aop.IntroductionAdvisor代表引介切面, 引介切面是对应引介增强的特殊的切面,它应用于类层上面,所以引介切点使用ClassFilter进行定义。
3.静态普通方法名匹配切面
上面已经对切入点、切面做了简介,下面通过几个例子来加深大家的印象。先来看静态普通方法名匹配切面,前面我们介绍切入点 通过ClassFilter可以定位到具体的类上,MethodMatcher可以定位到具体的方法上
,那么接下来通过定义一个接口、两个类。并通过实现类中的不同方法来验证我们之前的介绍。
- 接口和实现类(目标对象)
package com.lyc.cn.v2.day05; /** * @author: LiYanChao * @create: 2018-11-04 22:40 */ public interface Animal { void sayHello(); }
package com.lyc.cn.v2.day05; /** * @author: LiYanChao * @create: 2018-11-04 22:09 */ public class Cat implements Animal { @Override public void sayHello() { System.out.println("我是Cat类的sayHello方法。。。"); } public void sayHelloCat() { System.out.println("我是一只猫。。。"); } }
package com.lyc.cn.v2.day05; /** * @author: LiYanChao * @create: 2018-11-04 22:09 */ public class Dog implements Animal{ @Override public void sayHello() { System.out.println("我是Dog类的sayHello方法。。。"); } public void sayHelloDog() { System.out.println("我是一只狗。。。"); } }
- 增强(为了演示,这里只实现MethodBeforeAdvice前置增强接口)
package com.lyc.cn.v2.day05; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; /** * 前置增强 * @author: LiYanChao * @create: 2018-11-01 21:50 */ public class MyMethodBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("==前置增强"); System.out.println("==方法名:" + method.getName()); if (null != args && args.length > 0) { for (int i = 0; i < args.length; i++) { System.out.println("==第" + (i + 1) + "参数:" + args[i]); } } System.out.println("==目标类信息:" + target.toString()); } }
- 切面
package com.lyc.cn.v2.day05; import org.springframework.aop.ClassFilter; import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor; import java.lang.reflect.Method; /** * 静态普通方法名匹配切面 * @author: LiYanChao * @create: 2018-11-04 22:08 */ public class MyStaticPointcutAdvisor extends StaticMethodMatcherPointcutAdvisor { private static String METHOD_NAME = "sayHello"; /** * 静态方法匹配判断,这里只有方法名为sayHello的,才能被匹配 */ @Override public boolean matches(Method method, Class<?> targetClass) { return METHOD_NAME.equals(method.getName()); } /** * 覆盖getClassFilter,只匹配Dog类 * @return */ public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { return Dog.class.isAssignableFrom(clazz); } }; } }
StaticMethodMatcherPointcutAdvisor抽象类继承了StaticMethodMatcherPointcut类并实现了PointcutAdvisor接口。在MyStaticPointcutAdvisor类中我们实现了matches静态方法匹配判断,并且只有方法名为sayHello的,才能被匹配;覆盖了getClassFilter方法,并且只匹配Dog类。
- 测试一
为了大家能够更便捷的使用测试类,也为了减少大家书写配置文件的负担,我们还是采用编码的形式实现。新建MyTest类并书写测试方法。
@Test public void test1() { // 1、创建目标类、增强、切入点 Animal animal = new Dog(); MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice(); MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor(); // 2、创建ProxyFactory并设置目标类、增强、切面 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(animal); // 为切面类提供增强 advisor.setAdvice(advice); proxyFactory.addAdvisor(advisor); // 3、生成代理实例 Dog proxyDog = (Dog) proxyFactory.getProxy(); proxyDog.sayHelloDog(); System.out.println("\n\n"); proxyDog.sayHello(); }
我是一只狗。。。 ==前置增强 ==方法名:sayHello ==目标类信息:com.lyc.cn.v2.day05.Dog@65e579dc 我是Dog类的sayHello方法。。。
之前我们在代码里配置了,在类一级只匹配Dog类,在方法一级只匹配sayHello方法。运行结果与我们的设置符合。只有Dog类的sayHello被增强了。
- 测试二
@Test public void test2() { // 1、创建目标类、增强、切入点 Animal animal = new Cat(); MyMethodBeforeAdvice advice = new MyMethodBeforeAdvice(); MyStaticPointcutAdvisor advisor = new MyStaticPointcutAdvisor(); // 2、创建ProxyFactory并设置目标类、增强、切面 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(animal); // 为切面类提供增强 advisor.setAdvice(advice); proxyFactory.addAdvisor(advisor); // 3、生成代理实例 Cat proxyDog = (Cat) proxyFactory.getProxy(); proxyDog.sayHelloCat(); System.out.println("\n\n"); proxyDog.sayHello(); }
我是一只猫。。。 我是Cat类的sayHello方法。。。
测试二的结果没有一个方法被增强,虽然在Cat类中也有sayHello方法,但是我们设置的是只匹配Dog类,所以虽然在Cat类中有sayHello方法,但是它也是无法被增强的。
至于其他的切点和切面,这里就不一一演示了,这里特别感谢《Spring3.X企业应用开发实战》这本书,本篇很多介绍均摘自本书,哈哈!
4.总结
本篇主要介绍了切点和切面的概念,并通过实际的例子为大家演示了切点是如何匹配类和方法的。概念性的东西大家只看简介是不行的,需要自己动手写代码,才能更深刻的理解AOP的相关概念,在接下来的源码分析中才不会陷入迷茫。
上一篇和本篇的测试类中,我们都是通过ProxyFactory创建的代理,这样的实现肯定无法满足我们的实际需要,那么接下来的篇幅,我们就要介绍Spring的自动代理机制。