Spring AOP 是通过动态代理的方式来实现,主要是通过Pointcut、Advice、Advisor及ProxyFactoryBean 等接口来创建代理对象。
在IoC容器中,Advice 是一个bean(这样可以在通知中使用其他的bean),而Pointcut虽然不是一个Bean,但是它所在的Aspect(切面)是一个bean。
推荐Spring AOP在IoC容器中通过@AspectJ注解的形式来使用。
1 Pointcut
Pointcut 接口定义了一个或一组连接点的集合,用于匹配符合的方法。可以被复用,通常与通知(Advice)一起使用。
该接口通常不是由开发者直接实现,而是通过Spring AOP提供的切点表达式来定义(例如,@Pointcut)。
图 Pointcut 接口 UML
1.1 MethodMatcher 与 ClassFilter
切点由这两部分组成。
ClassFilter: 限制切入点或引入到给定目标类集的筛选器。
MethodMatcher: 检查目标方法是否符合Advice的条件。
图 ClassFilter 接口 UML
图 MethodMatcher 接口 UML
MethodMatcher 可以用来匹配静态及动态(匹配特定调用参数)的方法。
如果isRuntime() 方法返回false,表示只匹配静态方法。那么将调用matches带两个参数的方法。
如果 isRuntime() 返回true 并且matches带两个参数的方法返回true,那么matches带三个参数的方法将会被调用。
1.2 静态与动态Pointcut
静态切点:用于匹配方法及目标类,不能匹配特定的参数。实际开发中,大都是静态切点。Spring只需对静态切点求值一次。
动态切点:匹配方法的静态信息及参数值信息。性能耗费比静态切点大得多。Spring需要每次对它进行求值,且不能缓存结果。代表类是ControlFlowPointcut。
1.2.1 AspectJExpressionPointcut
是用于表示基于AspectJ表达式的切点的类型。是Spring AOP对AspectJ切点表达式支持的一部分。允许你使用AspectJ风格的切点来定义切点。
图 AspectJExpressionPointcut UML
图 AspectJExpressionPointcut 实现Pointcut接口的方法
AspectJExpressionPointcut 通过表达式来生成对应的ClassFilter与MethodMatcher。
2 Advice
每一个Advice 在IoC容器中都是一个bean,一个Advice实例可以被所有的目标对象访问,也可以让不同的目标对象访问该Advice的不同实例。这种模式成为Per-class 及 Per-instance 模式。
Per-class: 是最常用的类型,只对方法及参数器作用。适合那些不需要依赖目标类状态或不需要引入新的状态的情况,即不是引入。
Per-instance: 适合引入、支持混合。该类通知可以为目标类添加新的状态。
2.1 Advice 的类型
Advice的类型有Before、After(After returning及After throwing)及Around。“引入”也是一个特殊的Advice。Spring 为这些类型提供了不同的接口。
2.1.1 Before Advice
图 MethodBeforeAdvice UML
AspectJMethodBeforeAdvice 是封装了AspectJ 的Before Advice的一个通知。
图 AspectJMethodBeforeAdvice UML
2.1.2 Around Advice
org.aopalliance.intercept.MethodInterceptor 接口常用来实现Around 通知。允许你在运行时拦截并修改方法调用。
图 MethodInterceptor UML
其实现类需实现invoke方法,并调用MethodInvocation的proceed方法来执行需要增强的方法。
class TracingInterceptor implements MethodInterceptor {
Object invoke(MethodInvocation i) throws Throwable {
System.out.println("method "+i.getMethod()+" is called on "+
i.getThis()+" with args "+i.getArguments());
Object ret=i.proceed();
System.out.println("method "+i.getMethod()+" returns "+ret);
return ret;
}
}
2.1.3 Introduction Advice
“Introduction” 引入允许向目标对象动态地添加新的接口和方法,从而改变其类型。在创建代理对象时,代理对象会实现新的接口。
DeclareParentsAdvisor 用于实现@DeclareParents 注解的引入操作。
图 DeclareParentsAdvisor UML
typePatternClassFilter 用来过滤哪些目标类需要被引入。而advice则是用来实现引入的接口。getInterfaces则是返回需要被引入的接口。在其一个构造器中,传入给advice字段的类型IntroductionInterceptor。
图 IntroductionInterceptor UML
implementsInterface方法用于判断当前Advice是否实现了给定的接口。
3 ProxyFactoryBean
是Spring AOP中用于创建代理对象的一个工厂类。允许用户通过配置的方式来创建AOP代理。
@Component
public class CustomBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("------------------自定义before通知------------------");
System.out.println("方法名:" + method.getName());
System.out.println("参数:" + Arrays.asList(args));
System.out.println("目标对象:" + target);
}
}
@Service
public class UserService {
public void showInfo() {
System.out.println("userService");
}
public void showName(String name) {
System.out.println("name:" + name);
}
}
@Configuration
@ComponentScan(basePackages = {"aop2.factory"})
public class FactoryConfiguration {
@Bean
public ProxyFactoryBean userServiceProxy(UserService userService) {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(userService);
proxyFactoryBean.setInterceptorNames("customBeforeAdvice");
return proxyFactoryBean;
}
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(FactoryConfiguration.class);
UserService proxyObj = (UserService) context.getBean("userServiceProxy");
proxyObj.showInfo();
proxyObj.showName("黄先生");
}
}
可以设置ProxyFactoryBean的属性来控制生产代理对象的机制。
proxyTargetClass | 如果为true,则强制使用CGLIB代理。 |
optimize | 控制是否将主动优化应用通过CGLIB创建的代理。 |
forzen | 一旦对象被初始化,其配置就不能被修改。 |
exposeProxy | 为true时,原始代理对象会被注入到目标对象中。这样目标对象就可以通过某种机制访问到代理对象自身。 |
proxyInterfaces | 指定代理对象应该实现的接口。 |
interceptorNames | 指定应该应用到代理对象上的通知名称。 |
sigleon | 用于指定代理是否时单例。 |
表 ProxyFactoryBean 的属性
3.1 ProxyFactory
如果不想在IoC容器中使用AOP,则可以使用ProxyFactory 来创建代理。
ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
factory.addAdvice(myMethodInterceptor);
factory.addAdvisor(myAdvisor);
MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();