携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
AOP
Pointcut家族
它是Spring AOP对切点的一个顶层首相,非常的重要。
首先得看看这个顶级接口抽象的图谱:
这里面有一个非常重要得子接口:ExpressionPointcut,它是用于解析String类型的切点表达式的接口(这也是我们使用得最最最多的)
Pointcut接口分析
主要负责对系统的相应的Joinpoint进行捕捉,对系统中所有的对象进行Joinpoint所定义的规则进行匹配。提供了一个TruePointcut实例,当Pointcut为TruePointcut类型时,则会忽略所有的匹配条件,永远返回true
显然可以看出,这个接口和ClassFilter和MethodMatcher有关系
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
/**
* Canonical Pointcut instance that always matches.
* 意思是:用于匹配上的一个实例(意思是永远返回true嘛)
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
ClassFilter与MethodMatcher分别用于在不同的级别上限定Joinpoint的匹配范围,满足不同粒度的匹配
ClassFilter限定在类级别上,MethodMatcher限定在方法级别上
SpringAop主要支持在方法级别上的匹配,所以对类级别的匹配支持相对简单一些
ClassFilter
@FunctionalInterface
public interface ClassFilter {
// true表示能够匹配。那就会进行织入的操作
boolean matches(Class<?> clazz);
// 常量 会匹配所有的类 TrueClassFilter不是public得class,所以只是Spring内部自己使用的
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
Spring给他的实现类也比较多,如下:
RootClassFilter
public class RootClassFilter implements ClassFilter, Serializable {
private Class<?> clazz;
public RootClassFilter(Class<?> clazz) {
this.clazz = clazz;
}
// 显然,传进来的candidate必须是clazz的子类才行
@Override
public boolean matches(Class<?> candidate) {
return clazz.isAssignableFrom(candidate);
}
}
AnnotationClassFilter
public class AnnotationClassFilter implements ClassFilter {
...
public AnnotationClassFilter(Class<? extends Annotation> annotationType) {
// 默认情况下checkInherited给的false:不去看它继承过来的注解
this(annotationType, false);
}
// checkInherited true:表示继承过来得注解也算
public AnnotationClassFilter(Class<? extends Annotation> annotationType, boolean checkInherited) {
Assert.notNull(annotationType, "Annotation type must not be null");
this.annotationType = annotationType;
this.checkInherited = checkInherited;
}
...
@Override
public boolean matches(Class<?> clazz) {
return (this.checkInherited ?
// 继承的注解也会找出来
(AnnotationUtils.findAnnotation(clazz, this.annotationType) != null) :
// 只会看自己本类的注解
clazz.isAnnotationPresent(this.annotationType));
}
}
AspectJExpressionPointcut
它既是个Pointcut,它也是个ClassFilter,下面会详细分析本类
MethodMatcher
public interface MethodMatcher {
// 这个称为静态匹配:在匹配条件不是太严格时使用,可以满足大部分场景的使用
boolean matches(Method method, @Nullable Class<?> targetClass);
// 这个称为动态匹配(运行时匹配): 它是严格的匹配。在运行时动态的对参数的类型进行匹配
boolean matches(Method method, @Nullable Class<?> targetClass, Object... args);
//两个方法的分界线就是boolean isRuntime()方法,步骤如下
// 1、先调用静态匹配,若返回true。此时就会继续去检查isRuntime()的返回值
// 2、若isRuntime()还返回true,那就继续调用动态匹配
// (若静态匹配都匹配上,动态匹配那铁定更匹配不上得~~~~)
// 是否需要执行动态匹配
boolean isRuntime();
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
应用场景:比如需要统计用户登录次数时,那么登录传入的参数就是可以忽略的,则静态匹配就足够了
但是若要在登陆时对用户账号执行特殊的操作**(如赋予特殊的操作权限)**,就需要对参数进行一个类似于检验的操作,因此需要动态匹配
它有两个非常重要的抽象实现:StaticMethodMatcher和DynamicMethodMatcher
StaticMethodMatcher 静态匹配
public abstract class StaticMethodMatcher implements MethodMatcher {
// 永远返回false表示只会去静态匹配
@Override
public final boolean isRuntime() {
return false;
}
// 三参数matches抛出异常,使其不被调用
@Override
public final boolean matches(Method method, @Nullable Class<?> targetClass, Object... args) {
// should never be invoked because isRuntime() returns false
throw new UnsupportedOperationException("Illegal MethodMatcher usage");
}
}
作用:它表示不会考虑具体 方法参数。因为不用每次都检查参数,那么对于同样的类型的方法匹配结果,就可以在框架内部缓存以提高性能。比如常用的实现类:AnnotationMethodMatcher
DynamicMethodMatcher 动态匹配
public abstract class DynamicMethodMatcher implements MethodMatcher {
// 永远返回true
@Override
public final boolean isRuntime() {
// 永远返回true,去匹配动态匹配的方法即可
@O return true;
}verride
public boolean matches(Method method, @Nullable Class<?> targetClass) {
return true;
}
}
说明:因为每次都要对方法参数进行检查,无法对匹配结果进行缓存,所以,匹配效率相对 StatisMethodMatcher 来说要差,但匹配度更高。(实际使用得其实较少)
JdkRegexpMethodPointcut:基于正则的Pointcut
Spring官方为我们提供了一个基于正则表达式来匹配方法名的Pointcut,JdkRegexpMethodPointcut。
它提供了最重要的4个属性(patterns和excludedPatterns来自于父类AbstractRegexpMethodPointcut):
这里昂个属性来自于父类,相对来说就是比较简单的匹配signatureString(方法的全路径名称)
- String[] patterns:匹配的正则表达式。如find.*表示所有方法名以find开始的方法
- String[] excludedPatterns:排除的正则表达式们
下面两个是子类,也就是JdkRegexpMethodPointcut自己提供的属性
- Pattern[] compiledPatterns:相当于把正则字符串,Pattern.compile()成正则对象
- Pattern[] compiledExclusionPatterns:同上
都是数组,正则表达式都可以多个哟~~
需要注意的是,这两组含义相同,请不要同时跨组使用,没有意义,没必要深究。
这种切点表达式,在早期Spring中的使用较多,一般这么使用:
<!-- 自己书写的日志切面 -->
<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" />
<!-- 使用JDK的正则切点~~~~~~ -->
<bean id="regexPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="patterns">
<list>
<value>find.*</value><!-- 拦截所有方法名以find开始的方法 -->
</list>
</property>
</bean>
<!-- 切面+切点 组合成一个增强器即可~~~~~~ -->
<aop:config>
<aop:advisor advice-ref="logBeforeAdvice" pointcut-ref="regexPointcut"/>
</aop:config>
其实Spring为我们提供了一个简便的Advisor定义,可以方便的让我们同时指定一个JdkRegexpMethodPointcut和其需要对应的Advice,它就是RegexpMethodPointcutAdvisor,这样配置起来非常的方便
<bean id="logBeforeAdvice" class="com.fsx.aop.LogBeforeAdvice" />
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="logBeforeAdvice"/>
<property name="pattern" value="find.*"/>
</bean>
这个举例事基于XML的,之前我们都是这么来用的。那么现在用Java代码的方式也实现一遍(不需要Spring容器):
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new Person());
//声明一个aspectj切点,一张切面
JdkRegexpMethodPointcut cut = new JdkRegexpMethodPointcut();
//cut.setPattern("com.fsx.maintest.Person.run"); //它会拦截Person类下所有run的方法(无法精确到方法签名)
//cut.setPattern(".*run.*");//.号匹配除"\r\n"之外的任何单个字符。*号代表零次或多次匹配前面的字符或子表达式 所以它拦截任意包下任意类的run方法
cut.setPatterns(new String[]{".*run.*", ".*say.*"}); //可以配置多个正则表达 式... sayHi方法也会被拦截
// 声明一个通知(此处使用环绕通知 MethodInterceptor )
Advice advice = (MethodInterceptor) invocation -> {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
};
//切面=切点+通知
// 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
// Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
// addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别 相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
factory.addAdvisor(advisor);
Person p = (Person) factory.getProxy();
// 执行方法
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
}
}
class Person {
public int run() {
System.out.println("我在run...");
return 0;
}
public void run(int i) {
System.out.println("我在run...<" + i + ">");
}
public void say() {
System.out.println("我在say...");
}
public void sayHi(String name) {
System.out.println("Hi," + name + ",你好");
}
public int say(String name, int i) {
System.out.println(name + "----" + i);
return 0;
}
}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...
============>放行前拦截...
Hi,Jack,你好
============>放行后拦截...
============>放行前拦截...
Tom----666
============>放行后拦截...
最后需要注意的是:RegexpMethodPointcutAdvisor没有提供不匹配的正则表达式注入方法,即没有excludedPatterns注入,如果需要该功能请还是使用JdkRegexpMethodPointcut。
AspectJExpressionPointcut详细使用和分析
AspectJExpressionPointcut如字面,它和Expression有关,而这个切点表达式,最终还是依赖于AspectJ的jar包去解析的~~~~ Spring在使用@Aspect注解时,会大量的用到它
用AspectJExpressionPointcut实现的切点比JdkRegexpMethodPointcut实现切点的好处就是,在设置切点的时候可以用切点语言来更加精确的表示拦截哪个方法。(可以精确到返回参数,参数类型,方法名,当然,也可以模糊匹配)
纯Java方式Demo:
下面我先用一个纯Java的方式的例子,先体验一把:
public static void main(String[] args) {
//String pointcutExpression = "execution( int com.fsx.maintest.Person.run() )"; // 会拦截Person.run()方法
//String pointcutExpression = "args()"; // 所有没有入参的方法会被拦截。 比如:run()会拦截,但是run(int i)不会被拦截
// ... AspectJExpressionPointcut支持的表达式 一共有11种(也就是Spring全部支持的切点表达式类型)
String pointcutExpression = "@annotation(org.springframework.test.context.transaction.AfterTransaction)"; // 拦截上方法标有@AfterTransaction此注解的任意方法们
// =============================================================
ProxyFactory factory = new ProxyFactory(new Person());
//声明一个aspectj切点,一张切面
AspectJExpressionPointcut cut = new AspectJExpressionPointcut();
cut.setExpression(pointcutExpression); // 设置切点表达式
// 声明一个通知(此处使用环绕通知 MethodInterceptor )
Advice advice = (MethodInterceptor) invocation -> {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
};
//切面=切点+通知
// 它还有个构造函数:DefaultPointcutAdvisor(Advice advice); 用的切面就是Pointcut.TRUE,所以如果你要指定切面,请使用自己指定的构造函数
// Pointcut.TRUE:表示啥都返回true,也就是说这个切面作用于所有的方法上/所有的方法
// addAdvice();方法最终内部都是被包装成一个 `DefaultPointcutAdvisor`,且使用的是Pointcut.TRUE切面,因此需要注意这些区别 相当于new DefaultPointcutAdvisor(Pointcut.TRUE,advice);
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
factory.addAdvisor(advisor);
Person p = (Person) factory.getProxy();
// 执行方法
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
}
}
class Person {
@AfterTransaction
public int run() {
System.out.println("我在run...");
return 0;
}
public void run(int i) {
System.out.println("我在run...<" + i + ">");
}
public void say() {
System.out.println("我在say...");
}
public void sayHi(String name) {
System.out.println("Hi," + name + ",你好");
}
public int say(String name, int i) {
System.out.println(name + "----" + i);
return 0;
}
}
如上面的图,其实Spring也我们提供了AspectJExpressionPointcutAdvisor来专门处理基于AspectJ的通知+切点的
XML方式
public class Person {
@AfterTransaction
public int run() {
System.out.println("我在run...");
return 0;
}
public void run(int i) {
System.out.println("我在run...<" + i + ">");
}
public void say() {
System.out.println("我在say...");
}
public void sayHi(String name) {
System.out.println("Hi," + name + ",你好");
}
public int say(String name, int i) {
System.out.println(name + "----" + i);
return 0;
}
}
// 相当于准备了一个Advice
public class MyMethodInteceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
}
}
书写Spring的xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 作为示例,这是需要被切入的目标类 -->
<bean class="com.fsx.bean.Person"/>
<!-- 切面=切点+通知 (采用面向切点语言进行配置切面) 此处为了便捷 直接使用 AspectJExpressionPointcutAdvisor -->
<bean id="advisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<property name="expression"
value="@annotation(org.springframework.test.context.transaction.AfterTransaction)"></property>
<!-- 一个Advisor里面对应一个advice~~~ -->
<property name="advice">
<bean class="com.fsx.aop.MyMethodInteceptor"/>
</property>
</bean>
</beans>
把该xml配置文件导入Config配置类,让它生效
...
@Configuration
@ImportResource(locations = "classpath:spring.xml")
public class RootConfig { ... }
启动Spring容器(采用JUnit测试):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class})
public class TestSpringBean {
@Autowired
private Person p;
@Test
public void test1() {
System.out.println(p.getClass()); //class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
}
}
输出:
class com.fsx.bean.Person$$EnhancerBySpringCGLIB$$cba1d735 // 说明它是CGLIB的代理
============>放行前拦截...
我在run...
============>放行后拦截...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
备注:此xml的配置方式其实是最原始的Spring AOP的使用。并没有使用到aop:config、aop:pointcut这种标签注解,关于使用它们的xml配置方式,此处不做过多介绍了~~~因为也比较简单~
AspectJExpressionPointcut源码分析
// 很容易发现,自己即是ClassFilter,也是MethodMatcher
// 它是子接口:ExpressionPointcut的实现类
public class AspectJExpressionPointcut extends AbstractExpressionPointcut
implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
...
private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
// 从此处可以看出,Spring支持的AspectJ的切点语言表达式一共有10中(加上后面的自己的Bean方式一共11种)
// AspectJ框架本省支持的非常非常多,详解枚举类:org.aspectj.weaver.tools.PointcutPrimitive
static {
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
}
...
// 它持有BeanFactory 的引用,但是是可以为null的,也就是说它脱离容器也能够正常work
@Nullable
private BeanFactory beanFactory;
...
// PointcutExpression是org.aspectj.weaver.tools.PointcutExpression是AspectJ的类
// 它最终通过一系列操作,由org.aspectj.weaver.tools.PointcutParser#parsePointcutExpression从字符串表达式解析出来
@Nullable
private transient PointcutExpression pointcutExpression;
...
// 由此可见,我们不仅仅可议写&& || !这种。也支持 and or not这种哦~~~
private String replaceBooleanOperators(String pcExpr) {
String result = StringUtils.replace(pcExpr, " and ", " && ");
result = StringUtils.replace(result, " or ", " || ");
result = StringUtils.replace(result, " not ", " ! ");
return result;
}
...
// 这是ClassFilter 匹配类。借助的PointcutExpression#couldMatchJoinPointsInType 去匹配
public boolean matches(Class<?> targetClass) { ... }
// MethodMatcher 匹配方法,借助的PointcutExpression和ShadowMatch去匹配的
public boolean matches(Method method, @Nullable Class<?> targetClass, boolean hasIntroductions) { ... }
@Override
public boolean isRuntime() {
//mayNeedDynamicTest 相当于由AspectJ框架去判断的(是否有动态内容)
return obtainPointcutExpression().mayNeedDynamicTest();
}
...
// 初始化一个Pointcut的解析器。我们发现最后一行,新注册了一个BeanPointcutDesignatorHandler 它是准们处理Spring自己支持的bean() 的切点表达式的
private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {
PointcutParser parser = PointcutParser
.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
SUPPORTED_PRIMITIVES, classLoader);
parser.registerPointcutDesignatorHandler(new BeanPointcutDesignatorHandler());
return parser;
}
// 真正的解析,依赖于Spring自己实现的这个内部类(主要是ContextBasedMatcher 这个类,就会使用到BeanFactory了)
private class BeanPointcutDesignatorHandler implements PointcutDesignatorHandler {
private static final String BEAN_DESIGNATOR_NAME = "bean";
@Override
public String getDesignatorName() {
return BEAN_DESIGNATOR_NAME;
}
// ContextBasedMatcher由Spring自己实现,对容器内Bean的匹配
@Override
public ContextBasedMatcher parse(String expression) {
return new BeanContextMatcher(expression);
}
}
}
NameMatchMethodPointcut
如果创建切入点时候,我们往往只需要方法名字匹配,无需理会方法的签名和返回类型,这种情况下,我们可以使用 NameMatchMethodPointCut方法名字匹配切入点。(这种功能最弱,但显然效率是最高的)
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new Person());
// 声明一个通知(此处使用环绕通知 MethodInterceptor )
Advice advice = (MethodInterceptor) invocation -> {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
};
声明一个aspectj切点,一张切面
//NameMatchMethodPointcut cut = new NameMatchMethodPointcut();
//cut.setMappedName("run"); //会匹配所有的方法名为run的方法
切点+通知
//Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedName("run");
advisor.setAdvice(advice);
factory.addAdvisor(advisor);
Person p = (Person) factory.getProxy();
// 执行方法
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
}
}
输出:
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在run...<10>
============>放行后拦截...
我在say...
Hi,Jack,你好
Tom----666
其它Pointcut
上面已经介绍了Spring中使用得比较多的Pointcut,接下来简单的讲述一下稍微偏门些的Pointcut。
从顶部的pointcut的继承图中可以看出,有很多实现类。
ControlFlowPointCut:流程切入点
如果有这样的特殊需求:我们对一个方法进行切入通知,但只有这个方法在一个特定方法中被调用的时候执行通知(即存在流程上行的依赖关系),我们可以使用ControlFlowPointCut流程切入点
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new Person());
// 声明一个通知(此处使用环绕通知 MethodInterceptor )
Advice advice = (MethodInterceptor) invocation -> {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
};
声明一个aspectj切点,一张切面
// 含义:Main类里面,方法名为funabc执行时,内部调用的任何代理的方法都会被拦截~~~ 它控制的是整个流程
ControlFlowPointcut cut = new ControlFlowPointcut(Main.class, "funabc");
// 切点+通知
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
factory.addAdvisor(advisor);
Person p = (Person) factory.getProxy();
// 执行方法
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
// 此处调用Main类,方法名为funabc的方法。内部代理对象的方法就都会被拦截上了
funabc(p);
}
private static void funabc(Person person) {
person.run();
person.say();
}
}
输出:
我在run...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
============>放行前拦截...
我在run...
============>放行后拦截...
============>放行前拦截...
我在say...
============>放行后拦截...
使用流程切入点有时候可以解决不少问题,但值得注意的是:
使用流程切入点在jdk1.4中比其他切入点要慢5倍,在1.3上则要慢10倍,追求高性能的要慎重使用
ComposablePointcut 组合切入点
从上面的例子中,每次我们只能定义一个切入点(切点表达式)。有的时候,一个切点可能难以描述目标连接点的信息,而是需要同时满足两个切入点才行,那么ComposablePointcut就派上了用场(aspectJ里面的&& ||等其实也能达到类似的效果)。
但是更好的方式是使用Spring提供的ComposalbePointcut把两个切点组合起来,通过切点的复合运行算表示,ComposalbePointcut可以将多个切点以并集或者交集的方式组合起来,提供切点之间复合运算的功能。
先看一个Demo:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new Person());
// 声明一个通知(此处使用环绕通知 MethodInterceptor )
Advice advice = (MethodInterceptor) invocation -> {
System.out.println("============>放行前拦截...");
Object obj = invocation.proceed();
System.out.println("============>放行后拦截...");
return obj;
};
// 先创建一个流程切入点
ControlFlowPointcut controlFlowPointcut = new ControlFlowPointcut(Main.class, "funabc");
// 再创建一个方法名切入点
NameMatchMethodPointcut nameMatchMethodPointcut = new NameMatchMethodPointcut();
nameMatchMethodPointcut.addMethodName("say");
// 创建一个复合切点 把上面两者并且进来
ComposablePointcut cut = new ComposablePointcut();
cut.intersection((Pointcut) controlFlowPointcut).intersection((Pointcut)nameMatchMethodPointcut);
// 切点+通知(注意:此处放的是复合切面)
Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
factory.addAdvisor(advisor);
Person p = (Person) factory.getProxy();
// 执行方法
p.run();
p.run(10);
p.say();
p.sayHi("Jack");
p.say("Tom", 666);
funabc(p);
}
private static void funabc(Person person) {
person.run();
person.say();
}
}
输出:
我在run...
我在run...<10>
我在say...
Hi,Jack,你好
Tom----666
我在run...
============>放行前拦截...
我在say...
============>放行后拦截...
从结果中和上面对比我们能看出,两个切入点有并且的效果。(只有say方法被拦截了,run方法并没有被拦截)
ComposablePointcut 源码分析
public class ComposablePointcut implements Pointcut, Serializable {
// 它持有ClassFilter 和 MethodMatcher ,最终通过它去组合匹配
private ClassFilter classFilter;
private MethodMatcher methodMatcher;
// 构造函数一个共5个
// 匹配所有类所有方法的复合切点
public ComposablePointcut() {
this.classFilter = ClassFilter.TRUE;
this.methodMatcher = MethodMatcher.TRUE;
}
// 匹配特定切点的复合切点(相当于把这个节点包装了一下而已)
public ComposablePointcut(Pointcut pointcut) {
Assert.notNull(pointcut, "Pointcut must not be null");
this.classFilter = pointcut.getClassFilter();
this.methodMatcher = pointcut.getMethodMatcher();
}
// 匹配特定类**所有方法**的复合切点
public ComposablePointcut(ClassFilter classFilter) {
Assert.notNull(classFilter, "ClassFilter must not be null");
this.classFilter = classFilter;
this.methodMatcher = MethodMatcher.TRUE;
}
// 匹配**所有类**特定方法的复合切点
public ComposablePointcut(MethodMatcher methodMatcher) {
Assert.notNull(methodMatcher, "MethodMatcher must not be null");
this.classFilter = ClassFilter.TRUE;
this.methodMatcher = methodMatcher;
}
// 匹配特定类特定方法的复合切点(这个是最为强大的)
public ComposablePointcut(ClassFilter classFilter, MethodMatcher methodMatcher) {
Assert.notNull(classFilter, "ClassFilter must not be null");
Assert.notNull(methodMatcher, "MethodMatcher must not be null");
this.classFilter = classFilter;
this.methodMatcher = methodMatcher;
}
// 匹配特定类特定方法的复合切点(这个是最为强大的)
public ComposablePointcut union(ClassFilter other) {
this.classFilter = ClassFilters.union(this.classFilter, other);
return this;
}
// ==========3个并集(union) / 3个交集(intersection) 运算的方法========
public ComposablePointcut intersection(ClassFilter other) {
this.classFilter = ClassFilters.intersection(this.classFilter, other);
return this;
}
public ComposablePointcut union(MethodMatcher other) {
this.methodMatcher = MethodMatchers.union(this.methodMatcher, other);
return this;
}
public ComposablePointcut intersection(MethodMatcher other) {
this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other);
return this;
}
public ComposablePointcut union(Pointcut other) {
this.methodMatcher = MethodMatchers.union(
this.methodMatcher, this.classFilter, other.getMethodMatcher(), other.getClassFilter());
this.classFilter = ClassFilters.union(this.classFilter, other.getClassFilter());
return this;
}
public ComposablePointcut intersection(Pointcut other) {
this.classFilter = ClassFilters.intersection(this.classFilter, other.getClassFilter());
this.methodMatcher = MethodMatchers.intersection(this.methodMatcher, other.getMethodMatcher());
return this;
}
...
}
ComposablePointcut没有提供直接对两个切点类型并集交集的运算的方法。若需要,请参照org.springframework.aop.support.Pointcuts这个工具类里面有对两个Pointcut进行并集、交集的操作(后面再介绍)
AnnotationMatchingPointcut 注解切入点
根据对象是否有指定类型的注解来匹配Pointcut
有两种注解,类级别注解和方法级别注解。
//仅指定类级别的注解, 标注了 ClassLevelAnnotation 注解的类中的**所有方法**执行的时候,将全部匹配。
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class);
// === 还可以使用静态方法创建 pointcut 实例
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forClassAnnotation(ClassLevelAnnotation.class);
//仅指定方法级别的注解,标注了 MethodLeavelAnnotaion 注解的**方法(忽略类匹配)都将匹配**
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(MethodLevelAnnotation.class);
==========这个是同时想限定:===============
//同时限定类级别和方法级别的注解,只有标注了 ClassLevelAnnotation 的类中 ***同时***标注了 MethodLevelAnnotation 的方法才会匹配
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(ClassLevelAnnotation.class, MethodLevelAnnotation.class);
Demo:略
总结
其实,这些基础的知识也是为了去更好的理解Spring的自动代理创建器铺路
Advisor介绍
Advisor是Spring AOP的顶层抽象,用来管理Advice和Pointcut(PointcutAdvisor和切点有关,但IntroductionAdvisor和切点无关)
注意:Advice是aopalliance对通知(增强器)的顶层抽象,请注意区分~~
Pointcut是Spring AOP对切点的抽象。切点的实现方式有多种,其中一种就是AspectJ
public interface Advisor {
//@since 5.0 Spring5以后才有的 空通知 一般当作默认值
Advice EMPTY_ADVICE = new Advice() {};
// 该Advisor 持有的通知器
Advice getAdvice();
// 这个有点意思:Spring所有的实现类都是return true(官方说暂时还没有应用到)
// 注意:生成的Advisor是单例还是多例不由isPerInstance()的返回结果决定,而由自己在定义bean的时候控制
// 理解:和类共享(per-class)或基于实例(per-instance)相关 类共享:类比静态变量 实例共享:类比实例变量
boolean isPerInstance();
}
它的继承体系主要有如下两个:PointcutAdvisor和IntroductionAdvisor
IntroductionAdvisor与PointcutAdvisor最本质上的区别就是,IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。
而不能像PointcutAdvisor那样,可以使用任何类型的Pointcut,以及几乎任何类型的Advice。
PointcutAdvisor:和切点有关的Advisor
其实我们已经介绍了好几个PointcutAdvisor。比如:RegexpMethodPointcutAdvisor和NameMatchMethodPointcutAdvisor,它哥俩都位于org.springframework.aop.support此包。当然还要介绍过AspectJExpressionPointcutAdvisor…
PointcutAdvisor它的实现类非常的多:
public interface PointcutAdvisor extends Advisor {
Pointcut getPointcut();
}
AbstractPointcutAdvisor:抽象实现
// 实现了 Ordered接口
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable {
// 调用者可以手动来指定Order
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
if (this.order != null) {
return this.order;
}
// 若调用者没有指定Order,那就拿advice的order为准(若有),否则LOWEST_PRECEDENCE表示最后执行
Advice advice = getAdvice();
if (advice instanceof Ordered) {
return ((Ordered) advice).getOrder();
}
return Ordered.LOWEST_PRECEDENCE;
}
// Spring还没有使用该属性 永远返回true了
@Override
public boolean isPerInstance() {
return true;
}
...
}
AbstractGenericPointcutAdvisor 一般的、通用的PointcutAdvisor
public abstract class AbstractGenericPointcutAdvisor extends AbstractPointcutAdvisor {
private Advice advice = EMPTY_ADVICE;
public void setAdvice(Advice advice) {
this.advice = advice;
}
@Override
public Advice getAdvice() {
return this.advice;
}
...
}
DefaultPointcutAdvisor 通用的,最强大的Advisor
它是Spring提供的通用的,也被认为是最强大的Advisor。它可以把任意的两个Advice和Pointcut放在一起:
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {
private Pointcut pointcut = Pointcut.TRUE;
public DefaultPointcutAdvisor() {
}
// 若没有指定advice,默认Pointcut.TRUE,也就是说会匹配所有的方法的执行
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}
// 显然,这个构造函数式非常强大的~~
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}
}
AbstractBeanFactoryPointcutAdvisor:和bean工厂有关的PointcutAdvisor
从命名也能看出来,它和BeanFactory有关。
// 实现了BeanFactoryAware接口,若在Bean容器里注册可议注入BeanFactory~~~从而访问里面的实例
public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
// 我们发现这两个都是@Nullable,所以他们脱离容器使用也是可以的
@Nullable
private String adviceBeanName;
@Nullable
private BeanFactory beanFactory;
@Nullable
private transient volatile Advice advice;
public void setAdviceBeanName(@Nullable String adviceBeanName) {
this.adviceBeanName = adviceBeanName;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
// 若在Spring环境下,会给AdviceMonitor重新赋值为:getSingletonMutex()
resetAdviceMonitor();
}
// 此处加锁
public void setAdvice(Advice advice) {
synchronized (this.adviceMonitor) {
this.advice = advice;
}
}
// 这是它最重要的方法,获取增强器
@Override
public Advice getAdvice() {
Advice advice = this.advice;
// 非Spring环境一般手动set进来,所以就直接返回吧
if (advice != null) {
return advice;
}
// 显然进来Spring容器环境了,bean工厂和beanName都是不能为null的
Assert.state(this.adviceBeanName != null, "'adviceBeanName' must be specified");
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
// 若bean是单例的 那就没什么好说的 直接去工厂里拿出来就完事了(Advice.class) 有可能返回null哦
if (this.beanFactory.isSingleton(this.adviceBeanName)) {
advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);
this.advice = advice;
return advice;
}
// 若是多例的,就加锁 然后调用getBean()给他生成一个新的实例即可
else {
synchronized (this.adviceMonitor) {
//这步赋值和判断不能省~~~确保万无一失
advice = this.advice;
if (advice == null) {
advice = this.beanFactory.getBean(this.adviceBeanName, Advice.class);
this.advice = advice;
}
return advice;
}
}
}
}
DefaultBeanFactoryPointcutAdvisor:通用的BeanFactory的Advisor
这个是和Bean工厂关联的,通用的PointcutAdvisor
public class DefaultBeanFactoryPointcutAdvisor extends AbstractBeanFactoryPointcutAdvisor {
private Pointcut pointcut = Pointcut.TRUE;
// 若传进来为null,还是选择 Pointcut.TRUE 匹配所有
public void setPointcut(@Nullable Pointcut pointcut) {
this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
在Spring事务相关里,你会看到这个类
位于org.springframework.aop.support包内
BeanFactoryCacheOperationSourceAdvisor:和Cache有关
Spring Cache的@Cachable等注解的拦截,就是采用了它。该类位于:org.springframework.cache.interceptor,显然它和cache相关了。Jar包属于:Spring-context.jar
// @since 3.1 毕竟Spring的整个org.springframework.cache.Cache体系都是从这里开始的。(@Cacheable...等等)
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
// 显然它最重要的是持有这个引用(Cache章节详细介绍了它)
@Nullable
private CacheOperationSource cacheOperationSource;
// Pointcut使用的是CacheOperationSourcePointcut
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
AsyncAnnotationAdvisor:和@Async有关
位于包为:org.springframework.scheduling.annotation,所属jar包为spring-context.jar
public class AsyncAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
// 处理异步发生的异常的====
private AsyncUncaughtExceptionHandler exceptionHandler;
private Advice advice;
private Pointcut pointcut;
// 构造函数们
public AsyncAnnotationAdvisor() {
this(null, null);
}
// executor:可以自己指定异步任务的执行器
// exceptionHandler:异步异常的处理器
public AsyncAnnotationAdvisor(@Nullable Executor executor, @Nullable AsyncUncaughtExceptionHandler exceptionHandler) {
Set<Class<? extends Annotation>> asyncAnnotationTypes = new LinkedHashSet<>(2);
asyncAnnotationTypes.add(Async.class);
// 支持EJB的注解:@Asynchronous
try {
asyncAnnotationTypes.add((Class<? extends Annotation>)
ClassUtils.forName("javax.ejb.Asynchronous", AsyncAnnotationAdvisor.class.getClassLoader()));
} catch (ClassNotFoundException ex) {
// If EJB 3.1 API not present, simply ignore.
}
if (exceptionHandler != null) {
this.exceptionHandler = exceptionHandler;
} else {
// SimpleAsyncUncaughtExceptionHandler:只是一个简单的logger.error的输入打印
this.exceptionHandler = new SimpleAsyncUncaughtExceptionHandler();
}
// buildAdvice: new AnnotationAsyncExecutionInterceptor(executor, exceptionHandler) 它是个MethodInterceptor 环绕通知器
this.advice = buildAdvice(executor, this.exceptionHandler);
// 把asyncAnnotationTypes交给buildPointcut,它最终是个ComposablePointcut,会把这两种注解都支持。union起来 或者的关系
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
public void setTaskExecutor(Executor executor) {
this.advice = buildAdvice(executor, this.exceptionHandler);
}
...
}
AbstractAspectJAdvice的实现类如下:这5个实现类完完整整的对应着我们AspectJ的那5个注解。
AspectJPointcutAdvisor
显然是和AspectJ相关的,使用得很是广泛。注意它和AspectJExpressionPointcutAdvisor的区别。有名字也能看出来,AspectJExpressionPointcutAdvisor和表达式语言的切点相关的,而AspectJPointcutAdvisor是无关的。它哥俩都位于包org.springframework.aop.aspectj里。
public class AspectJPointcutAdvisor implements PointcutAdvisor, Ordered {
// AbstractAspectJAdvice通知:它的子类看下面截图,就非常清楚了
private final AbstractAspectJAdvice advice;
// 可以接受任意的Pointcut,可谓非常的通用(当然也包含切点表达式啦)
private final Pointcut pointcut;
@Nullable
private Integer order;
// 只有这一个构造函数,包装一个advice
public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
// 然后pointcut根据advice直接给生成了一个。这是AbstractAspectJAdvice#buildSafePointcut的方法
this.pointcut = advice.buildSafePointcut();
}
}
InstantiationModelAwarePointcutAdvisor
它是PointcutAdvisor的一个子接口。
// 由SpringAOP顾问包装AspectJ实现的接口 可能具有延迟初始化策略的方面。
// 例如,一个PerThis实例化模型意味着对建议的初始化太慢
public interface InstantiationModelAwarePointcutAdvisor extends PointcutAdvisor {
// 该Advisor是否需要懒加载
boolean isLazy();
// 判断此Advisor它所拥有的Advice是否已经初始化了
boolean isAdviceInstantiated();
}
它的唯一实现类:InstantiationModelAwarePointcutAdvisorImpl
// 默认的访问权限,显然是Spring内部自己用的
class InstantiationModelAwarePointcutAdvisorImpl
implements InstantiationModelAwarePointcutAdvisor, AspectJPrecedenceInformation, Serializable {
private static final Advice EMPTY_ADVICE = new Advice() {};
// 和AspectJExpression
private final AspectJExpressionPointcut declaredPointcut;
..
// 通知方法
private transient Method aspectJAdviceMethod;
private final AspectJAdvisorFactory aspectJAdvisorFactory;
private final MetadataAwareAspectInstanceFactory aspectInstanceFactory;
@Nullable
private Advice instantiatedAdvice;
@Nullable
private Boolean isBeforeAdvice;
@Nullable
private Boolean isAfterAdvice;
...
@Override
public boolean isPerInstance() {
return (getAspectMetadata().getAjType().getPerClause().getKind() != PerClauseKind.SINGLETON);
}
@Override
public synchronized Advice getAdvice() {
if (this.instantiatedAdvice == null) {
this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);
}
return this.instantiatedAdvice;
}
// advice 由aspectJAdvisorFactory去生产 懒加载的效果
private Advice instantiateAdvice(AspectJExpressionPointcut pointcut) {
Advice advice = this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pointcut,
this.aspectInstanceFactory, this.declarationOrder, this.aspectName);
return (advice != null ? advice : EMPTY_ADVICE);
}
@Override
public boolean isBeforeAdvice() {
if (this.isBeforeAdvice == null) {
determineAdviceType();
}
return this.isBeforeAdvice;
}
@Override
public boolean isAfterAdvice() {
if (this.isAfterAdvice == null) {
determineAdviceType();
}
return this.isAfterAdvice;
}
// 这里解释根据@Aspect方法上标注的注解,来区分这两个字段的值的
private void determineAdviceType() {
AspectJAnnotation<?> aspectJAnnotation =
AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(this.aspectJAdviceMethod);
if (aspectJAnnotation == null) {
this.isBeforeAdvice = false;
this.isAfterAdvice = false;
}
else {
switch (aspectJAnnotation.getAnnotationType()) {
case AtAfter:
case AtAfterReturning:
case AtAfterThrowing:
this.isAfterAdvice = true;
this.isBeforeAdvice = false;
break;
case AtAround:
case AtPointcut:
this.isAfterAdvice = false;
this.isBeforeAdvice = false;
break;
case AtBefore:
this.isAfterAdvice = false;
this.isBeforeAdvice = true;
}
}
}
}
这个Advisor是在Spring解析被 @AspectJ注解注释的类时生成的 Advisor
而这个 Advisor中的 Pointcut与Advice都是由ReflectiveAspectJAdvisorFactory 来解析生成的(与之对应的 Advice 是 AspectJMethodBeforeAdvice, AspectJAfterAdvice, AspectJAfterReturningAdvice, AspectJAfterThrowingAdvice, AspectJAroundAdvice,
Pointcut 则是AspectJExpressionPointcut), 解析的步骤是:
自动代理创建器:AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors() ->
Bean工厂相关的Advisor构建器:BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors() ->
ReflectiveAspectJAdvisorFactory.getAdvisors() ->
ReflectiveAspectJAdvisorFactory.getAdvisor() 最终生成了InstantiationModelAwarePointcutAdvisorImpl(当然包括里面的 Pointcut与 advice 也都是由 ReflectiveAspectJAdvisorFactory 解析生成的)
IntroductionAdvisor:引介切面
Spring中有五种增强:BeforeAdvide(前置增强)、AfterAdvice(后置增强)、ThrowsAdvice(异常增强)、RoundAdvice(环绕增强)、IntroductionAdvice(引入增强)
RoundAdvice(环绕增强):就是BeforeAdvide(前置增强)、AfterAdvice(后置增强)的组合使用叫环绕增强。前四种都比较简单。。。
引入增强(Introduction Advice)的概念:一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。(非常强大有木有,A不需要动代码,就能有别的功能,吊炸天有木有)
IntroductionAdvisor纯粹就是为Introduction而生的。
IntroductionAdvisor 和 PointcutAdvisor接口不同,它仅有一个类过滤器ClassFilter 而没有 MethodMatcher,这是因为 `引介切面 的切点是类级别的,而 Pointcut 的切点是方法级别的(细粒度更细,所以更加常用)。
为了更好的了解IntroductionAdvisor,我先有必要讲解下IntroductionInfo和IntroductionInterceptor;
Introduction可以在不改动目标类定义的情况下,为目标类增加新的属性和行为。
IntroductionInfo:引介信息
IntroductionInfo 接口描述了目标类需要实现的新接口。
// 提供描述引言所需信息的接口
// IntroductionAdvisor必须实现这个接口。若`org.aopalliance.aop.Advice`直接实现了此接口,
// 它可议独立的当作introduction来使用而不用依赖IntroductionAdvisor。这种情况下,这个advice可议自描述,不仅提供。。。
public interface IntroductionInfo {
//Return the additional interfaces introduced by this Advisor or Advice.
// 返回额外给Advisor 或者 advice实现的接口们
Class<?>[] getInterfaces();
}
它的继承结构如下:
IntroductionInterceptor:引介拦截器
在Spring中,为目标对象添加新的属性和行为必须声明相应的接口以及相应的实现。这样,再通过特定的拦截器将新的接口定义以及实现类中的逻辑附加到目标对象上。然后,目标对象(确切的说,是目标对象的代理对象)就拥有了新的状态和行为
这里面介绍这个非常强大的拦截器:IntroductionInterceptor
// IntroductionInterceptor它是对MethodInterceptor的一个扩展,同时他还继承了接口DynamicIntroductionAdvice
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}
DynamicIntroductionAdvice:
public interface DynamicIntroductionAdvice extends Advice {
boolean implementsInterface(Class<?> intf);
}
通过DynamicIntroductionAdvice,可以界定当前的 IntroductionInterceptor为哪些接口提供相应的拦截功能。通过MethodInterceptor,IntroductionInterceptor 就可以处理新添加的接口上的方法调用了
打个比方
如果把每个目标对象实例看作盒装牛奶生产线上的那一盒盒牛奶的话,那么生产合格证就是新的Introduction逻辑,而introductionInterceptor 就是把这些生产合格证贴到一盒盒牛奶上的那个人。
要对目标对象进行拦截并添加Introduction的逻辑,我们可以直接扩展IntroductionInterceptor,然后在子类的invoke方法中实现所有的拦截逻辑
除非特殊情况下需要直接扩展IntroductionInterceptor,大多数时候,直接使用Spring提供的两个现成的实现类就可以了:DelegatingIntroductionInterceptor 和 DelegatePerTargetObjectIntroductionInterceptor
例子
下面使用一个例子,加深一下对引介增强的了解:
// 定义一个新的行为接口,这个行为准备作用在目标对象上
public interface IOtherInte {
void doOther();
}
// 自己定义一个IntroductionInterceptor来实现IntroductionInterceptor接口
// 注意:此处也实现了接口IOtherInte(这是类似于增强器部分) 相当于这个interptor目前就只处理 IOtherInte
public class SomeInteIntroductionInterceptor implements IntroductionInterceptor, IOtherInte {
/**
* 判断调用的方法是否为指定类中的方法
* 如果Method代表了一个方法 那么调用它的invoke就相当于执行了它代表的这个方法
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (implementsInterface(invocation.getMethod().getDeclaringClass())) {
System.out.println("我是引介增强的方法体~~~invoke");
return invocation.getMethod().invoke(this, invocation.getArguments());
}
return invocation.proceed();
}
/**
* 判断clazz是否为给定接口IOtherBean的实现
*/
@Override
public boolean implementsInterface(Class clazz) {
return clazz.isAssignableFrom(IOtherInte.class);
}
@Override
public void doOther() {
System.out.println("给人贴标签 doOther...");
}
}
// 方法测试
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new Person());
factory.setProxyTargetClass(true); // 强制私用CGLIB 以保证我们的Person方法也能正常调用
// 此处采用IntroductionInterceptor 这个引介增强的拦截器
Advice advice = new SomeInteIntroductionInterceptor();
// 切点+通知(注意:此处放的是复合切面)
Advisor advisor = new DefaultIntroductionAdvisor((DynamicIntroductionAdvice) advice, IOtherInte.class);
//Advisor advisor = new DefaultPointcutAdvisor(cut, advice);
factory.addAdvisor(advisor);
IOtherInte otherInte = (IOtherInte) factory.getProxy();
otherInte.doOther();
System.out.println("===============================");
// Person本身自己的方法 也得到了保留
Person p = (Person) factory.getProxy();
p.run();
p.say();
}
输出:
我是引介增强的方法体~~~invoke
给人贴标签 doOther...
===============================
我在run...
我在say...
我们发现,我们没有更改过Person类的任何代码,它竟然就有了doOther()方法的功能,这就是引介增强的强大功能。此处使用的Advisor为DefaultIntroductionAdvisor。它也是我们最常用的Advisor:
// 它是一个Advisor,同时也是一个IntroductionInfo
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
// 它只有ClassFilter,因为它只能作用在类层面上
ClassFilter getClassFilter();
// 判断这些接口,是否真的能够增强。 DynamicIntroductionAdvice#implementsInterface()方法
void validateInterfaces() throws IllegalArgumentException;
}
// 它直接事IntroductionAdvisor的实现类。同时也是一个ClassFilter
public class DefaultIntroductionAdvisor implements IntroductionAdvisor, ClassFilter, Ordered, Serializable {
private final Advice advice;
private final Set<Class<?>> interfaces = new LinkedHashSet<>();
private int order = Ordered.LOWEST_PRECEDENCE;
// 构造函数们
public DefaultIntroductionAdvisor(Advice advice) {
this(advice, (advice instanceof IntroductionInfo ? (IntroductionInfo) advice : null));
}
// 如果IntroductionInfo 不等于null,就会把接口都add进去/
// IntroductionInfo 的实现类有常用的:DelegatingIntroductionInterceptor和DelegatePerTargetObjectIntroductionInterceptor
public DefaultIntroductionAdvisor(Advice advice, @Nullable IntroductionInfo introductionInfo) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
if (introductionInfo != null) {
Class<?>[] introducedInterfaces = introductionInfo.getInterfaces();
if (introducedInterfaces.length == 0) {
throw new IllegalArgumentException("IntroductionAdviceSupport implements no interfaces");
}
for (Class<?> ifc : introducedInterfaces) {
addInterface(ifc);
}
}
}
//当然你也可以不使用IntroductionInfo,而自己手动指定了这个接口
public DefaultIntroductionAdvisor(DynamicIntroductionAdvice advice, Class<?> intf) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
addInterface(intf);
}
...
@Override
public void validateInterfaces() throws IllegalArgumentException {
for (Class<?> ifc : this.interfaces) {
if (this.advice instanceof DynamicIntroductionAdvice &&
!((DynamicIntroductionAdvice) this.advice).implementsInterface(ifc)) {
throw new IllegalArgumentException("DynamicIntroductionAdvice [" + this.advice + "] " +
"does not implement interface [" + ifc.getName() + "] specified for introduction");
}
}
}
...
}
DelegatingIntroductionInterceptor和DelegatePerTargetObjectIntroductionInterceptor
这两个类是 Spring AOP 中为 IntroductionInterceptor 介面所提供的实作类别,我们可以直接继承他俩,然后扩展我们自己的行为状态。
public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
implements IntroductionInterceptor {
// 需要被代理的那个对象。因为这个类需要子类继承使用,所以一般都是thid
@Nullable
private Object delegate;
/**
* Construct a new DelegatingIntroductionInterceptor.
* The delegate will be the subclass, which must implement
* additional interfaces.
* 访问权限事protected,显然就是说子类必须去继承这个类,然后提供空构造函数。代理类就是this
*/
protected DelegatingIntroductionInterceptor() {
init(this);
}
// 当然,你也可以手动指定delegate
public DelegatingIntroductionInterceptor(Object delegate) {
init(delegate);
}
private void init(Object delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
implementInterfacesOnObject(delegate);
// 移除调这些内部标记的接口们
// We don't want to expose the control interface
suppressInterface(IntroductionInterceptor.class);
suppressInterface(DynamicIntroductionAdvice.class);
}
// 如果你要自定义一些行为:比如环绕通知之类的,子类需要复写此方法(否则没有必要了)
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
// 判断是否是引介增强
if (isMethodOnIntroducedInterface(mi)) {
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
// 如果返回值就是delegate 本身,那就把本身返回出去
if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
Object proxy = ((ProxyMethodInvocation) mi).getProxy();
if (mi.getMethod().getReturnType().isInstance(proxy)) {
retVal = proxy;
}
}
return retVal;
}
return doProceed(mi);
}
...
}
因此,上面的例子Demo,我用DelegatingIntroductionInterceptor改造一下(只需要改造SomeInteIntroductionInterceptor即可):
// 因为我们继承自DelegatingIntroductionInterceptor,所以若我们不做环绕通知个性化,只需要实现接口的方法即可
public class SomeInteIntroductionInterceptor extends DelegatingIntroductionInterceptor implements IOtherInte {
@Override
public void doOther() {
System.out.println("给人贴标签 doOther...");
}
}
继承此类,确实少了不少事呢。
DelegatePerTargetObjectIntroductionInterceptor
与DelegatingIntroductionInterceptor不同,DelegatePerTargetObjectIntroductionInterceptor会在内部持有一个目标对象与相应Introduction逻辑实现类之间的映射关系。
当每个目标对象上的新定义的接口方法被调用的时候,它会拦截这些调用。然后以目标对象实例作为键,到它持有的那个映射关系中取得对应当前目标对象实例的Introduction实现类实例。 使用起来和DelegatingIntroductionInterceptor没有太大区别,主要在构造函数上:
1: 可以删除掉SomeInteIntroductionInterceptor类了
2:单独实现引介接口
public class OtherImpl implements IOtherInte {
@Override
public void doOther() {
System.out.println("我是OtherImpl");
}
}
main方法里修改如下:这样就ok了
Advice advice = new DelegatePerTargetObjectIntroductionInterceptor(OtherImpl.class, IOtherInte.class);
备注:若你需要复写invoke方法的逻辑,请扩展它即可~~~(它的优点是:每次执行目标对象的时候,都可议缓存起来,提高一点效率吧)
总结
Advisor是Spring AOP中非常重要的一个概念,最终代理对象进行执行什么的,都是适配到此处,因此需要重点掌握。
其中,引介增强平时使用得较少,但是在特殊的场景下,它能够解决某一类问题,还是非常不错的~