您现在的位置是:首页 >技术交流 >Spring aop 详解网站首页技术交流
Spring aop 详解
一,概念
Spring aop 是Spring核心组件之一,通过aop可以简化编程。Spring Aop核心架构是基于代理模式,spring中提供了两种代理模式的创建。一是使用ProxyFactory纯程序方法创建AOP代理另一种就是通过使用借助于@Aspect注解或者xml进行声明式创建代理,Spring底层可以使用两种代理方法即JDK动态代理、CGLIB动态代理。默认情况下当被通知的目标实现了接口时,Spring将会采用JDK动态代理,若目标对象没有实现任何接口将会采用CGLIB动态代理。这是因为JDK仅提供了基于接口的代理方法实现。
(Aspect) 切面:切面是封装在类中通知与切入点的集合,在Spring中可以由 @Aspect 注解或者xml配置定义一个切面类。
(joinPoint) 连接点:应用程序中定义的一个点,对于Spring而言每一个执行的方法就是一个连接点。
(advice) 通知:在连接点可以执行特定的代码逻辑,spring定义了before、after、around通知。
(pointcut) 切点:切入点可以理解为一种表达式去匹配在特定的位置上执行运行特定的代码。
(weaing) 织入:就是在适当的位置上将切面插入到应用程序代码的过程,然后织入的时候可以完成通知的执行。
二,ProxyFactory
Spring中实现织入、代理创建过程。创建代理之前需指定被通知对象,在底层ProxyFactory将代理过程委托给DefaultAopProxyFactory,然后该类又将代理委托给 CglibAopProxy 或 JdkDynamicAopProxy 实现。
三,Advice
spring提供的通知由如下几种:
before:在某个连接点之前执行程序逻辑。
after returning:连接点正常后执行的程序逻辑,需要注意的是如果程序抛出异常该通知并不会执行。
after throwing :当程序出现异常时候执行的程序逻辑。
after:当连接点结束执行的程序逻辑(无论是否出现异常都会执行)
around:spring中最强大的通知功能,它可以完成并实现上面4种功能的实现。
Interceptor 为环绕通知 BeforeAdvice为前置通知 AfterAdvice 为后置通知
public interface EmployeeService {
String getEmployeeName(int id);
}
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Override
public String getEmployeeName(int id) {
if (id == 0) {
return "张三";
}
if (id == 1) {
return "李四";
}
if (id == 3) {
throw new RuntimeException();
}
return null;
}
}
后置返回通知
public class SimpleAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行方法的返回值:" + returnValue);
}
}
环绕通知
public class SimpleAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch stopWatch = new StopWatch();
stopWatch.start(invocation.getMethod().getName());
Object proceed = invocation.proceed();
System.out.println("执行任务共耗时:" + stopWatch.getTotalTimeSeconds() + "秒");
return proceed;
}
}
前置通知
public class SimpleBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行的方法是:" + method.getName());
System.out.println("执行的参数是:" + Arrays.asList(args));
System.out.println("执行的对象是:" + target);
}
}
异常通知
public class SimpleThrowing implements ThrowsAdvice {
/**
* 需要注意此方法的参数顺序必须是如下顺序,否则会报java.lang.IllegalArgumentException: argument type mismatch
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception e) {
System.out.println("
" + "执行的方法为:" + method.getName());
System.out.println("抛出的异常是:" + e.getClass().getName());
System.out.println("错误消息:" + e.getMessage());
System.out.println("导致的原因是:" + e.getCause());
}
}
测试
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test() {
//构建通知
SimpleThrowing simpleThrowing = new SimpleThrowing();
SimpleAfterReturningAdvice simpleAfterReturningAdvice = new SimpleAfterReturningAdvice();
SimpleAroundAdvice simpleAroundAdvice = new SimpleAroundAdvice();
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvice(simpleThrowing);
proxyFactory.addAdvice(simpleAfterReturningAdvice);
proxyFactory.addAdvice(simpleAroundAdvice);
proxyFactory.addAdvice(simpleBeforeAdvice);
proxyFactory.setTarget(bean);
// 获取代理对象
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
System.out.println("=============================");
System.out.println(proxy.getEmployeeName(3));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@554f0dfb
执行任务共耗时:0.0秒
执行方法的返回值:李四
李四
=============================
执行的方法是:getEmployeeName
执行的参数是:[3]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@554f0dfb执行的方法为:getEmployeeName
抛出的异常是:java.lang.RuntimeException
错误消息:null
导致的原因是:null
查看addAdvice
四,Pointcut 切点
上面默认创建都是DefaultPointAdvisor
实例,其通知默认将会适用于代理目标对象的所有方法。但有时候我们并不想让通知对一个类的所有方法都进行适配。这个时候我们就可以使用Pointcut
这个接口的实现去决定哪些类的那些方法将会适配通知。
public interface Pointcut {
/**
* Return the ClassFilter for this pointcut.
* @return the ClassFilter (never {@code null})
*/
ClassFilter getClassFilter();
/**
* Return the MethodMatcher for this pointcut.
* @return the MethodMatcher (never {@code null})
*/
MethodMatcher getMethodMatcher();
}
可以看到ClassFilter接口的 match方法传入一个Class实例用来检查该实例是否适用于通知,如果方法返回true则表示该类适用。
@FunctionalInterface
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);
}
Spring支持两种类型的MethodMatcher
,如上面isRuntime
确定MethodMatcher
是静态的还是动态,若方法返回值为true则表示动态的否则将会是静态的。对于静态的切入点Spring会对每一个目标类的方法调用一次MethodMatcher
的 matches
方法并将返回的返回值进行缓存。这样后面方法再次调用的时候便会取这个缓存。对于动态的切入点Spring会每一次调用matches
方法。我们可以看到matches
重载方法中matches(Method method, Class<?> targetClass, Object… args) 这个可以对方法的参数进行检查以确定目标方法是否适用于通知,例如可以用这个方法实现:当参数是一个String类型且以execute字符串开头的目标方法才适用于通知。
public interface MethodMatcher {
/**
* Perform static checking whether the given method matches.
* <p>If this returns {@code false} or if the {@link #isRuntime()}
* method returns {@code false}, no runtime check (i.e. no
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
* will be made.
* @param method the candidate method
* @param targetClass the target class
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class<?> targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
* <p>Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
* <p>This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
}
1,NameMatchMethodPointcut
在创建切入点的时候我们可以指定匹配方法名然后使通知在这些匹配的方法上执行,这个时候就可以使用NameMatchMethodPointcut
这个类下面是这个类的简单实现如下所示:
继续使用之前的SimpleBeforeAdvice
类作为通知其相关测试类方法代码如下:
getEmployeeName1并不匹配不会执行通知
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testNameMatchMethodPointcut() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
// 匹配getEmployeeName方法
pointcut.addMethodName("getEmployeeName1");
// 使用默认的advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
// 获取代理对象
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行结果
李四
2,JdkRegexpMethodPointcut
上面介绍了NameMatchMethodPointcut
类可以对指定的方法进行匹配,但是一个个添加也确实麻烦一些。如果使用正则匹配是不是更方便一些呢?例如想匹配以指定前缀开头的所有方法呢?我们还是使用之前的前置通知类测试代码如下所示:
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testJdkRegexPointCut() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
//设置匹配所以以get开头的方法
pointcut.setPattern(".*get.*");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
// 获取代理对象
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@5bc7e78e
李四
3,DyanmicMethodMatcherPointcut
上面介绍两种切点方式都是静态切入点的实现,下面将演示如何使用动态方法切入点,我们设置当调用EmployeeService的 getEmployeeName 方法 参数为2才执行相应的通知。
新增DynamicMethodMatcherPointcut类实现,需要注意的是需要重写matches 方法。
public class SimpleDynamicMethodPointcut extends DynamicMethodMatcherPointcut {
/**
* 此抽象方法必须被重写
*/
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
// 匹配getEmployeeName方法
return "getEmployeeName".equals(method.getName()) && (Integer) args[0] == 2;
}
}
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testDynamicMethod() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
SimpleDynamicMethodPointcut pointcut = new SimpleDynamicMethodPointcut();
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(2));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[2]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@1a7cb3a4
null
4,AspectJExpressionPointcut
Spring也内置了基于AspectJ切入点表达式支持的类,如果需要使用AspectJ切入点表达式需要在项目中添加如下依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
或者
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testAspectExpressionPointcut() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.demo.aop.EmployeeServiceImpl.getEmployeeName(..))");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@1e1e9ef3
李四
5,AnnotationMatchingPointcut
如果在某些类的某些方法添加了指定的注解,如果需要基于自己的注解来实现特定的通知需要使用到AnnotationMatchingPointcut 类的支持,接下来将演示这个类的使用:
首先定一个注解:
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AdviceRequired {
}
添加注解
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testAnnotationPointcut() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
AnnotationMatchingPointcut pointcut = AnnotationMatchingPointcut.forMethodAnnotation(AdviceRequired.class);
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(bean);
proxyFactory.addAdvisor(advisor);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@26f46fa6
李四
6,ControlFlowPointcut
该切点一般可以指定在固定的类中固定方法去执行其他类的方法才使通知生效的场景下使用。
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testControlFlowPointcut() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
//构建切点
ControlFlowPointcut pointcut = new ControlFlowPointcut(Demo1ApplicationTests.class, "execute");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println("第一次执行 getEmployeeName 开始------------------------");
proxy.getEmployeeName(1);
System.out.println("第一次执行 getEmployeeName 结束------------------------");
System.out.println("第二次执行 getEmployeeName 开始------------------------");
execute(proxy);
System.out.println("第二次执行 getEmployeeName 结束------------------------");
}
public void execute(EmployeeService employeeService) {
employeeService.getEmployeeName(2);
}
}
执行结果
第一次执行并没有进入切面
第一次执行 getEmployeeName 开始------------------------
第一次执行 getEmployeeName 结束------------------------
第二次执行 getEmployeeName 开始------------------------
执行的方法是:getEmployeeName
执行的参数是:[2]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@767a014e
第二次执行 getEmployeeName 结束------------------------
7,ComposablePointcut
之前使用的point接口的实现基本上都为一个advisor配置了一个切入点,在大多数情况下都够用,但是有时候会有将多个切入点组合在一起,以确定在目标类的目标方法执行相应的通知的需求。这个时候就可以使用 ComposablePointcut
这个类完成这个需求的实现。
ComposablePointcut 支持两种方法:union()与intersection() 两者方法都具有多个参数的重载类型以接收ClassFilter、MethodMatcher 参数,其中union接收这些对象是或的关系,而intersection是与的关系。
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testComposable() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
// 构建切点
ComposablePointcut pointcut = new ComposablePointcut((Pointcut) new StaticMethodMatcherPointcut() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
return method.getName().startsWith("get2");
}
});
pointcut.union(AnnotationMatchingPointcut.forMethodAnnotation(AdviceRequired.class));
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, simpleBeforeAdvice);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行结果
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@63c12e52
李四
五,切面
Spring的切面由实现了Advisor类的实例表示,这个接口由两个实现
Advisor
是Spring aop 的顶层抽象,用来管理Advice
和Pointcut
IntroductionAdvisor与PointcutAdvisor
最本质上的区别就是,IntroductionAdvisor只能应用于类级别的拦截,只能使用Introduction型的Advice。
而不能像PointcutAdvisor
那样,可以使用任何类型的Pointcut
,以及任何类型的Advice
。
从下面的实现关系能看出来。有一些默认组合了上面的说的切点的Advisor
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testAdvisor() {
//构建通知
SimpleBeforeAdvice simpleBeforeAdvice = new SimpleBeforeAdvice();
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(simpleBeforeAdvice);
advisor.addMethodName("getEmployeeName");
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
proxyFactory.setTarget(bean);
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
执行的方法是:getEmployeeName
执行的参数是:[1]
执行的对象是:com.example.demo.aop.EmployeeServiceImpl@46c269e0
李四
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
接口描述了目标类需要实现的新接口。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
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.IntroductionInterceptor;
/**
* 自己定义一个IntroductionInterceptor来实现IntroductionInterceptor接口
* 注意:此处也实现了接口IOtherInteface(这是类似于增强器部分)
* 相当于这个interptor目前就只处理 IOtherInterface
*/
public class SomeInteIntroductionInterceptor implements IntroductionInterceptor, IOtherInterface {
/**
* 判断调用的方法是否为指定类中的方法
* 如果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(IOtherInterface.class);
}
@Override
public void doOther() {
System.out.println("给人贴标签 doOther...");
}
}
/**
* 定义一个新的行为接口,这个行为准备作用在目标对象上
*/
public interface IOtherInterface {
void doOther();
}
@SpringBootTest
class Demo1ApplicationTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testIntroduction() {
//原对象
EmployeeService bean = applicationContext.getBean(EmployeeService.class);
ProxyFactory proxyFactory = new ProxyFactory(bean);
//此处采用IntroductionInterceptor 这个引介增强的拦截器
DynamicIntroductionAdvice advice = new SomeInteIntroductionInterceptor();
// 切点通知(注意:此处放的是复合切面)
DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice, IOtherInterface.class);
proxyFactory.addAdvisor(advisor);
IOtherInterface otherInterface = (IOtherInterface) proxyFactory.getProxy();
otherInterface.doOther();
System.out.println("===============================");
// EmployeeService本身自己的方法 也得到了保留
EmployeeService proxy = (EmployeeService) proxyFactory.getProxy();
System.out.println(proxy.getEmployeeName(1));
}
}
我是引介增强的方法体~~~invoke
给人贴标签 doOther...
===============================
李四
我们发现,我们没有更改过EmployeeService类的任何代码,它竟然就有了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");
}
}
test方法里修改如下:这样就ok了
Advice advice = new DelegatePerTargetObjectIntroductionInterceptor(OtherImpl.class, IOtherInte.class);
备注:若你需要复写invoke方法的逻辑,请扩展它即可(它的优点是:每次执行目标对象的时候,都可议缓存起来,提高一点效率吧)