Spring | AOP
by Botao Xiao
AOP术语
1.通知Advice(方法级别的增强):
- 想要添加的功能。对已有的方法想要添加的功能。
- 说明了干什么什么时候干。
- 例如在事件之前添加记录日志的代码就是一个Advice。
2.连接点JoinPoint:
- 允许添加通知的位置,几乎所有的方法都可以添加通知。
- 要对什么方法进行增强?所有的连接点都可以被增强,所以几乎所有的方法都是连接点。
3.切入点Pointcut:
- 具体对那些方法进行增强,那些需要被增强的Joinpoint被称为切点。
- 一个类有100个Jointpoint,而我们只想要对一个方法进行增强,就需要把那个方法定义为切点。
4.切面Aspect:
- Advice和Pointcut的结合。个人认为这是一个抽象概念。
- 通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
5.引入Introduction(类级别的增强):
- 类似于Advice,但是是针对类级别的增强,给某个类添加方法或属性。
- 例如某个类没有实现某个接口,我们可以通过Introduce添加接口实现的逻辑。
6.目标Target:
- 引入中所提到的目标类。
7.织入Weaving:
- 把切面应用到目标对象来创建新的代理对象的过程。
- 织入的过程被分为三种:
- 编译期的织入, 要求使用特殊的Java编译器。
- 类装载期的织入,要求使用特殊的ApplicationClassLoader。
- 动态代理的织入,在运行期使用动态代理对方法进行增强。
- Spring采用动态代理织入,而AspectJ通过编译期织入和类装载期织入。
代理
- JDK的动态代理
- 要生成代理的对象必须要实现某个接口。
public static void main(String[] args) throws Exception {
final ProxyService service = new ProxyServiceImpl(); //实现的就是当前实例的代理。
ProxyService proxy = (ProxyService) Proxy.newProxyInstance(service.getClass().getClassLoader(),
new Class[]{ProxyService.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Monitor.begin(); //在通过反射调用方法之前实行织入。
Object ret = method.invoke(service, null);
Monitor.end();
return ret;
}
});
proxy.doOperation();
System.out.println(proxy);
}
- CgLib的动态代理
- 为类创建一个子类,并在子类方法中拦截父类父类方法的调用并织入。
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer(); //这是CgLib的增强器对象。
private Object getProxy(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
// 拦截方法,在代理调用父类的方法之前和之后进行织入。
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
Monitor.begin();
Object ret = proxy.invokeSuper(obj, args);
Monitor.end();
return ret;
}
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
Car car = (Car) cglibProxy.getProxy(Car.class);
car.run();
}
}
增强类
- 增强的xml配置 ```xml
#### 前置增强
1. 通过继承MethodBeforeAdvice实现before方法,并在before中实现要织入的前置增强。
```Java
// 定义前置增强的方法
public void before(Method arg0, Object[] args, Object arg2) throws Throwable {
String name = (String) args[0];
System.out.println("Hello Mr/Ms. " + name);
}
@Test
public void test() {
Waiter waiter = new NaiveWaiter();
BeforeAdvice greetBeforeAdvice = new GreetBeforeAdvice();
ProxyFactory pf = new ProxyFactory(waiter); //通过Spring代理工厂的方法生成代理对象。
pf.addAdvice(greetBeforeAdvice); //织入前置增强。
Waiter proxy = (Waiter) pf.getProxy();
proxy.greetTo("Sean");
}
通过xml配置生成代理,织入前置增强
<!-- 通过ProxyFactoryBean配置代理 -->
<!-- 定义了前置增强的方法 -->
<bean id="greetingAdvice" class="ca.mcmaster.spring.aop.GreetBeforeAdvice"/>
<!-- 要生成代理的对象 -->
<bean id="target" class="ca.mcmaster.spring.aop.NaiveWaiter"/>
<!-- 生成的代理对象 -->
<bean id="waiter" class="org.springframework.aop.framework.ProxyFactoryBean" scope="singleton">
<property name="proxyInterfaces" value="ca.mcmaster.spring.aop.Waiter"/>
<!-- 要织入的增强 -->
<property name="interceptorNames" value="greetingAdvice"/>
<property name="target" ref="target"/>
<!-- 如果设置为true,则强制使用CgLib, 单例适合CgLin,JDK适合其他-->
<!-- Cglib创建慢,效率高, JDK Proxy创建快,效率低-->
<property name="optimize" value="true"/>
</bean>
后置增强
- 通过实现AfterReturningAdvice接口实现后置增强。xml配置和前置增强类似。
public class GreetingAfterAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { String name = (String) args[0]; System.out.println("Goodbye " + name); } }
环绕增强
- 通过实现MethodInterceptor接口并实现invoke方法。
public class GreetingInterceptor implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { Object[] arguments = invocation.getArguments(); String name = (String) arguments[0]; System.out.println("Hi! " + name); //环绕前增强 invocation.proceed(); //对实际的方法调用。 System.out.println("Goodbye! " + name); //环绕后增强 return null; } }
异常抛出增强
- 用于增强抛出异常后的情况,可以用于事务的回滚。
- 需要实现ThrowsAdvice。
public class GreetAfterException implements ThrowsAdvice { public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable{ System.out.println("Get exception: " + ex.getMessage()); System.out.println("Method: " + method.getName()); } }
创建切面
-
切面是Advise和PointCut的集合,在定义了切面后就知道增强是什么,要在哪个方法的那个位置进行增强。
-
Spring的切点类
public interface Pointcut { ClassFilter getClassFilter(); //确定当前的类是否匹配过滤条件。 MethodMatcher getMethodMatcher(); //当前的方法是否匹配过滤条件。 Pointcut TRUE = TruePointcut.INSTANCE; }
切点的类型
- 静态方法切点:org.springframework.aop.support.StaticMethodMatcherPointcut
- 动态方法切点:org.springframework.aop.support.DynamicMethodMatcherPointcut
- 注解切点:org.springframework.aop.support.annotation.AnnotationMatchingPointcut
- 表达式切点:org.springframework.aop.support.ExpressionPointcut
- 流程切点:org.springframework.aop.support.ControlFlowPointcut
- 复合切点:org.springframework.aop.support.ComposablePointcut
切面类型
- Advisor: 一般的切面,太过宽泛,不会直接使用。
- PointcutAdvisor: 具有切点的切面,包含Advise和Pointcut两个类,经常使用。
- IntroductionAdvisor:引介切面。
切面的xml配置,我个人觉得很繁琐,但是研究有助于对aop的理解
<!-- 配置切面 -->
<!-- seller和waiter的实例对象,将会为这两个对象生成代理 -->
<bean id="waiterTarget" class="ca.mcmaster.spring.aop.wiring.Waiter" scope="singleton"/>
<bean id="sellerTarget" class="ca.mcmaster.spring.aop.wiring.Seller" scope="singleton"/>
<!-- 配置通知(增强的内容) -->
<bean id="greetingBeforeAdvice" class="ca.mcmaster.spring.aop.wiring.GreetingBeforeAdvice" scope="singleton"/>
<!-- 配置切面(内部定义增强的内容和切点) -->
<!-- 其中对方法和类进行了匹配 -->
<bean id="greetingAdvisor" class="ca.mcmaster.spring.aop.wiring.GreetingAdvisor" scope="singleton">
<!-- 将通知织入切面 -->
<property name="advice" ref="greetingBeforeAdvice"/>
</bean>
<!-- 将切面织入代理对象 -->
<!-- 定义了要生成的多个Bean对象代理的父类 -->
<bean id="parent" class="org.springframework.aop.framework.ProxyFactoryBean" abstract="true">
<property name="interceptorNames" value="greetingAdvisor"/>
<property name="proxyTargetClass" value="true"/>
</bean>
<bean id="waiterProxy" parent="parent">
<property name="target" ref="waiterTarget"/>
</bean>
<bean id="sellerProxy" parent="parent">
<property name="target" ref="sellerTarget"/>
</bean>
切面的增强
- 前置增强
public class GreetingBeforeAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("Wow! Hello " + args[0] + "!"); } }
- 定义切面
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor { @Override public boolean matches(Method method, Class<?> targetClass) { return "greetTo".equals(method.getName()); //方法匹配 } @Override public ClassFilter getClassFilter() { // return super.getClassFilter(); return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { // 要匹配的类是不是clazz的子类。 return Waiter.class.isAssignableFrom(clazz); //类匹配 } }; } }
- 实现注入,进行测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:beans.xml"}) public class GreetingAdvisorTest { @Autowired @Qualifier("waiterProxy") private Waiter waiterProxy; @Autowired @Qualifier("sellerProxy") private Seller sellerProxy; @Test public void test() { System.out.println(waiterProxy); waiterProxy.greetTo("Sean"); sellerProxy.greetTo("Seanforfun"); } }
通过正则表达式匹配切面
<!-- 通过正则表达式对方法和类进行匹配 -->
<bean id="regexAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="greetingBeforeAdvice"/>
<property name="patterns">
<list>
<!-- 通过正则匹配所有包含greet的方法 -->
<value>.*greet.*</value>
</list>
</property>
</bean>
动态切面
- 动态切面的动态体现在切点上,可以针对参数对方法进行增强。
public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut { private static final List<String> names; static{ names = new ArrayList<>(); names.add("Sean"); names.add("Irene"); } @Override public boolean matches(Method method, Class<?> targetClass, Object[] args) { System.out.println("对" + method.getName() + "进行动态检查"); return names.contains((String)args[0]); //检查参数是否符合要求 } @Override public ClassFilter getClassFilter() { return new ClassFilter() { @Override public boolean matches(Class<?> clazz) { return Waiter.class.isAssignableFrom(clazz); } }; } @Override public boolean matches(Method method, Class<?> targetClass) { System.out.println("对" + method.getName() + "进行静态检查"); return "greetTo".equals(method.getName()); } }
- 在xml中进行配置,实际上切面由pointcut和增强方法构成,所以我们要通过定义DefaultBeanFactoryPointcutAdvisor中的这两个选项来定义切面。 ```xml
3. 测试
```Java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:beans.xml"})
public class GreetingDynamicPointcutTest {
@Autowired
@Qualifier("waiterProxy")
private Waiter waiter;
@Test
public void test() {
waiter.greetTo("Irene"); //结果只会对注册了的名字进行增强。
waiter.greetTo("X.B");
}
}
流程切面(慢!)
流程切面多用于策略模式和门面模式的结合
public class WaiterDelegant {
private Waiter waiter;
public void setWaiter(Waiter waiter) {
this.waiter = waiter;
}
public void service(String clientName){
waiter.greetTo(clientName); //我们只想对其中的某一个方法进行增强
waiter.serveTo(clientName);
}
}
- 通过xml进行流程切面 ```xml
### 复合切面
> 复合切面用于合并多种切点
* 此段代码并没有完全成功,只是实现了流程控制并没有实现name match。
```Java
public class GreetingComposablePointcut {
public Pointcut getIntersectionPointcut(){
ComposablePointcut cp = new ComposablePointcut();
Pointcut pt1 = new ControlFlowPointcut(WaiterDelegant.class, "service");
NameMatchMethodPointcut pt2 = new NameMatchMethodPointcut();
pt2.addMethodName("greetTo");
cp.intersection((Pointcut)pt2).intersection(pt1);
return cp;
}
}
<!-- 设置复合切面 -->
<bean id="composablePointcut" class="ca.mcmaster.spring.aop.wiring.GreetingComposablePointcut"/>
<bean id="composableAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" value="#{composablePointcut.intersectionPointcut}"/>
<property name="advice" ref="greetingBeforeAdvice"/>
</bean>
自动创建代理
当我们要进行织入的时候,我们需要使用代理。上面的代码中,所生成的代理对象都是通过ProxyFactoryBean进行生成的,较为麻烦。我们可以通过使用自动代理的机制,让容器为我们自动生成代理对象。
BeanPostProcessor
BeanNameAutoProxyCreator
基于Bean配置名的自动代理创建器。(命名规则)
<!-- 使用bean名称进行自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator" >
<property name="beanNames">
<list>
<!-- 通过正则表达式进行bean对象的匹配 -->
<value>*Target</value>
</list>
</property>
<property name="interceptorNames" value="greetingBeforeAdvice"/>
<property name="optimize" value="true"/>
</bean>
- 测试
public class AutomaticBeanFactoryTest { @Autowired @Qualifier("waiterTarget") private Waiter waiter; @Autowired @Qualifier("sellerTarget") private Seller seller; @Test public void test(){ waiter.greetTo("Seanforfun"); seller.greetTo("Sean"); } } Wow! Hello Seanforfun! Waiter Greet to Seanforfun Wow! Hello Sean! Seller greet to Sean
DefaultAdvisorAutoProxyCreator
切面(advisor)的信息中包含了要增强的对象(pointcut)和增强的方法(advice),其中提供了足够的信息用于生成代理对象。所以当我们定义了DefaultAdvisorAutoProxyCreator对象后,可以为要增强的bean对象自动生成代理。
<!-- 通过切面生成代理 -->
<!-- 切面本身包括了增强方法和要增强的对象,这两个对象已经包括了足够的信息,
当匹配到了要被增强的对象时,会自动生成代理。 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
- 所有作为bean对象生成的切面均被注册
对greetTo进行静态检查 对greetTo进行静态检查 对serveTo进行静态检查 对toString进行静态检查 对clone进行静态检查 对greetTo进行静态检查 Wow! Hello Seanforfun! Wow! Hello Seanforfun! 对greetTo进行动态检查 Waiter Greet to Seanforfun Wow! Hello Sean! Seller greet to Sean
Subscribe via RSS