aop是什么

aop是面向切面编程(aspect oriented programing)的简称。aop的出现并不是要完全替代oop,仅是作为oop的有益补充。
aop的应用场合是有限的,一般只适合于那些具有横切逻辑的应用场合。

  • 性能监测

  • 访问控制

  • 事务管理

  • 日志记录
    ...

aop中的概念

连接点(joinpoint)

一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就称为连接点。比如,

  • 类开始初始化前,后

  • 类中某个方法调用前,后

  • 方法抛出异常后
    ...

连接点由两个信息确定:

  1. 用方法表示的程序执行点

  2. 用相对点表示的方位

如在Test.foo()方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置。
spring使用切点对执行点定位,而方位则在增强类型中定义.

切点(pointcut)

每个程序类都可能有多个连接点,aop通过切点定位特定点。类比于数据库查询:连接点相当于数据库中的记录,切点相当于查询条件。
切点和连接点不是一对一关系,一个切点可以匹配多个连接点。
切点只定位到某个方法上,如果希望定位到具体的连接点上,还需要提供方位信息。

增强(advice)

增强是织入到目标类连接点上的一段代码.它除用于描述一段代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,就可以找到特定的连接点了。
spring提供的增强接口都是带方位名的:BeforeAdvice,AfterReturningAdvice,ThrowsAdvice等。

目标对象(target)

增强逻辑的织入目标类。

引介(introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原来没有实现某个接口,通过引介,也可以动态的为业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

织入(weaving)

织入是将增强添加对目标类具体连接点的过程。aop有三种织入方式:

  1. 编译期织入,这要求使用特殊的java编译器

  2. 类装载期织入,这要求使用特殊的类装载器

  3. 动态代理织入,在运行期为目标类添加增强,生成子类

spring使用第3种方式织入,aspectj使用第1,2种方式。

代理(proxy)

一个类被aop织入增强后,就产生一个结果类,它融合了原来类和增强逻辑的代理类。我们可以采用调用原来类相同的方式调用代理类。

切面(aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义。spring aop负责实施切面,它将切面所定义的横切逻辑织入到切面所指定的连接点钟。

创建增强类

前置增强

场景:高级餐厅的服务员在回答顾客之前都会说'你好!...'.

public class Waiter {
    public void check(String name){
        System.out.println("结账?"+name);
    }
    public void serve(String name){
        System.out.println("要点什么?"+name);
    }
}

前置增强

import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;

public class GreetAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] args, Object obj)throws Throwable {
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
    }
}

测试

import org.springframework.aop.BeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;

public class TestBeforeAdvice {
    public static void main(String[] args){
        Waiter target=new Waiter();
        BeforeAdvice advice=new GreetAdvice();
        ProxyFactory pf=new ProxyFactory();//spring提供的代理工厂
        pf.setTarget(target);//设置代理目标
        pf.addAdvice(advice);//添加增强
        Waiter proxy=(Waiter)pf.getProxy();//代理实例
        proxy.serve("TheViper");
        proxy.check("TheViper");
    }
}

结果

你好!TheViper
来点什么?TheViper
你好!TheViper
结账?TheViper
  • ProxyFactory内部使用JDK代理或CGLib代理,将增强应用到目标类。

  • 还可以将接口设置为代理目标。

...
ProxyFactory pf=new ProxyFactory();
pf.setInterfaces(target,getClass().getInterfaces);
pf.setTarget(target);
...

在spring中配置

application-context.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" 
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd ">
    <bean id='greetAdvice' class='com.GreetAdvice'></bean>
    <bean id='target' class='com.Waiter'></bean>
    <bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
    p:target-ref='target' p:interceptorNames='greetAdvice'/>
</beans>  

测试

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestBeforeAdvice {
    public static void main(String[] args){        
        ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
        Waiter waiter=(Waiter)ctx.getBean("waiter");
        waiter.serve("TheViper");
        waiter.check("TheViper");
    }
}

ProxyFactoryBean常用配置:

  • target:代理的目标对象

  • proxyInterfaces:代理所要实现的接口,可以是多个接口。该属性还有一个别名属性interfaces

  • interceptorNames:需要植入目标对象的Bean列表。这些Bean必须是实现了org.aopalliance.intercept.MethodInterceptororg.springframework.aop.Advisor的Bean,配置中的顺序对应调用的顺序。

  • singleton:返回的代理是否为单例,默认为单例

  • optimize:设置为true时,强制使用CGLib代理。对于singleton代理,推荐使用CGLib,对于其他作用域类型的代理,最好使用JDK代理。因为CGLib创建代理速度慢,而创建出的代理对象运行效率较高。JDK代理的表现与之相反

  • proxyTargetClass:是否对类进行代理(不是对接口进行代理),设置为true时,使用CGLib

从上面spring配置可以看到,ProxyFactoryBean用的是JDK代理,如果将proxyTargetClass设置为true后,无需再设置proxyInterfaces属性,即使设置了也会被忽略。

后置增强

场景:服务员和顾客交流后,礼貌的说'please enjoy yourself'.
后置增强

import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;

public class GreetAfterAdvice implements AfterReturningAdvice{
    
    @Override
    public void afterReturning(Object returnObj,Method method,Object[] args,Object obj)throws Throwable {
        //returnObj:目标实例方法返回的结果 method:目标类的方法 args:目标实例的方法参数 obj:目标类实例
        System.out.println("please enjoy yourself!");
    }
}

spring配置

...
<bean id='greetBeforeAdvice' class='com.GreetAdvice'></bean>
<bean id='greetAfterAdvice' class='com.GreetAfterAdvice'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:target-ref='target' p:interceptorNames='greetBeforeAdvice'/>
...

结果

你好!TheViper
要点什么?TheViper
please enjoy yourself!
你好!TheViper
结账?TheViper
please enjoy yourself!

环绕增强

环绕增强允许在目标类方法调用前后织入横切逻辑,它综合实现了前置,后置增强两种的功能。

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class GreetingInterceptor implements MethodInterceptor{

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object[] args=invocation.getArguments();
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
        Object obj=invocation.proceed();//通过反射调用目标方法
        System.out.println("please enjoy yourself!");
        return obj;
    }
}
...
<bean id='greetingInterceptor' class='com.GreetingInterceptor'></bean>
<bean id='target' class='com.Waiter'></bean>
<bean id='waiter' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:target-ref='target' p:interceptorNames='greetingInterceptor,greetAfterAdvice'/>
...

异常抛出增强

异常抛出增强最适合的场景是事务管理。

import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;

public class TransactionManager implements ThrowsAdvice{
    public void afterThrowing(Method method,Object[] args,Object target,Exception ex)throws Throwable{
        System.out.println("method:"+method.getName());
        System.out.println("抛出异常"+ex.getMessage());
        System.out.println("回滚");
    }
}

引介增强

引介增强不是在目标方法周围织入增强,而是为目标类创建新的方法和属性。所以引介增强的连接点是类级别的,非方法级别.

创建切面

前面织入增强时,都是织入到目标类的所有方法中。这节将会介绍如何让增强提供连接点方位的信息,如织入到方法前面,后面等,而切点进一步描述具体织入哪些类的哪些方法上。
spring通过org.springframework.aop.Pointcut接口描述切点,PointcutClassFilterMethodMatcher构成。
通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上。
此外,spring还提供注解切点和表达式切点,两者都使用AspectJ的切点表达式语言。

切点类型

  • 静态方法切点

  • 动态方法切点

  • 注解切点

  • 表达式切点

  • 流程切点

  • 复合切点

静态普通方法名匹配切面

StaticMethodMatcherPointcutAdvisor代表一个静态方法匹配切面,它通过StaticMethodMatcherPointcut定义切点,通过类过滤和方法名匹配定义切点。
例子,Seller类也有serve方法

public class Seller {
    public void serve(String name){
        System.out.println("seller说:要点什么?"+name);
    }
}

前置增强(advice)

public class GreetingBeforeAdvice implements MethodBeforeAdvice{
    @Override
    public void before(Method method, Object[] args, Object obj)throws Throwable {
        String clientName=args[0].toString();
        System.out.println("你好!"+clientName);
    }
}

切面(advisor)

import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

public class GreetAdvisor extends StaticMethodMatcherPointcutAdvisor{
    
    @Override
    public boolean matches(Method method, Class<?> cls) {//切点方法匹配
        return "serve".equals(method.getName());
    }
    //切点类匹配规则:Waiter的类或子类
    public ClassFilter getClassFilter(){
        return new ClassFilter(){
            public boolean matches(Class cls){
                return Waiter.class.isAssignableFrom(cls);
            }
        };
    }
}

spring配置

...
<bean id='greetBeforeAdvice' class='com.GreetingBeforeAdvice'></bean>
<bean id='waiterTarget' class='com.Waiter'></bean>
<bean id='sellerTarget' class='com.Seller'></bean>
<!-- 向切面注入前置增强 -->
<bean id='greetingAdvisor' class='com.GreetAdvisor' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='greetingAdvisor'/>
<bean id='waiter' parent='parent' p:target-ref='waiterTarget'></bean><!-- waiter代理 -->
<bean id='seller' parent='parent' p:target-ref='sellerTarget'></bean><!-- seller代理 -->
...

<bean id='parent' abstract='true' ...>表示通过一个父<bean>定义公共的配置信息。
测试

ApplicationContext ctx=new ClassPathXmlApplicationContext("application-context.xml");
Waiter waiter=(Waiter)ctx.getBean("waiter");
Seller seller=(Seller)ctx.getBean("seller");
waiter.serve("TheViper");
waiter.check("TheViper");
seller.serve("TheViper");

结果

你好!TheViper
waiter说:要点什么?TheViper
waiter说:结账?TheViper
seller说:要点什么?TheViper

可以看到切面只是织入到Waiter.serve()方法调用前的连接点上,而Waiter.check()和Seller.serve()都没有织入切面。

静态正则表达式匹配切面

RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类。该类已经是功能齐备的的实现类了,一般情况下,无需扩展该类。

...
<bean id='regexpAdvisor' class='org.springframework.aop.support.RegexpMethodPointcutAdvisor' 
p:advice-ref='greetBeforeAdvice'>
    <property name="patterns">
        <list>
            <value>.*che.*</value>
        </list>
    </property>
</bean>
<bean id='parent' abstract='true' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='regexpAdvisor'/>
...

这里定义了一个匹配模式.*che.*,它会匹配check()方法。

...
waiter.serve("TheViper");
waiter.check("TheViper");
...
waiter说:要点什么?TheViper
你好!TheViper
waiter说:结账?TheViper

动态切面

import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.DynamicMethodMatcherPointcut;

public class GreetingDynamicPointcut extends DynamicMethodMatcherPointcut{
    private static List<String> specialCients=new ArrayList<String>();
    static{
        specialCients.add("Tom");//添加白名单
        specialCients.add("TheViper");
    }
    //切点类匹配规则:Waiter的类或子类
    public ClassFilter getClassFilter(){//静态匹配
        return new ClassFilter(){
            public boolean matches(Class cls){
                System.out.println("对"+cls.getName()+"类做静态检查");
                return Waiter.class.isAssignableFrom(cls);
            }
        };
    }
    public boolean matches(Method method, Class<?> cls) {//切点方法静态匹配
        System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法做静态检查");
        return "serve".equals(method.getName());
    }
    @Override
    public boolean matches(Method method, Class<?> cls, Object[] args) {//动态匹配
        System.out.println("对"+cls.getName()+"类的"+method.getName()+"方法做动态检查");
        String clientName=args[0].toString();
        return specialCients.contains(clientName);
    }
}

匹配规则:目标类为Waiter或其子类,方法名为serve,动态传入的参数name必须在白名单中存在。

    <bean id='dynamicAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor'>
        <property name="pointcut">
            <bean class='com.GreetingDynamicPointcut'/>
        </property>
        <property name="advice">
            <bean class='com.GreetingBeforeAdvice'/>
        </property>
    </bean>
    <bean id='waiter1' class='org.springframework.aop.framework.ProxyFactoryBean' 
    p:interceptorNames='dynamicAdvisor' p:target-ref='waiterTarget'/>
        Waiter waiter=(Waiter)ctx.getBean("waiter1");
        waiter.serve("Peter");
        waiter.check("Peter");
        waiter.serve("TheViper");
        waiter.check("TheViper");
对com.Waiter类做静态检查
对com.Waiter类的serve方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的check方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的clone方法做静态检查
对com.Waiter类做静态检查
对com.Waiter类的toString方法做静态检查
//上面是织入前spring对目标类中的所有方法进行的静态切点检查
对com.Waiter类做静态检查
对com.Waiter类的serve方法做静态检查
对com.Waiter类的serve方法做动态检查
waiter说:要点什么?Peter
对com.Waiter类做静态检查
对com.Waiter类的check方法做静态检查
//静态方法检查没通过,不用动态检查了
waiter说:结账?Peter
对com.Waiter类的serve方法做动态检查
//第二次调用不用执行静态检查
你好!TheViper
//动态检查,满足白名单,执行前置增强
waiter说:要点什么?TheViper
waiter说:结账?TheViper

定义动态切点时,切勿忘记同时覆盖getClassFilter()和matches(Method method,Class cls)方法,通过静态切点检查可以排除掉大部分不符合匹配规则的方法。

流程切面

spring的流程切面由DefaultPointcutAdvisorControlFlowPointcut实现。流程切点代表由某个方法直接或间接发起调用的其他方法。
定义Waiter的代理

public class WaiterDelegate {
    private Waiter waiter;
    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }
    public void service(String name){
        waiter.serve(name);
        waiter.check(name);
    }
}
<bean id='controlFlowPointcut' class='org.springframework.aop.support.ControlFlowPointcut'>
    <constructor-arg type='java.lang.Class' value='com.WaiterDelegate'/>
    <constructor-arg type='java.lang.String' value='service'/><!-- 指定流程切点的方法 -->
</bean>
<bean id='controlFlowAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' 
p:pointcut-ref='controlFlowPointcut' p:advice-ref='greetBeforeAdvice'/>
<bean id='waiter2' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='controlFlowAdvisor' p:target-ref='waiterTarget'/>
        Waiter waiter=(Waiter)ctx.getBean("waiter2");
        WaiterDelegate wd=new WaiterDelegate();
        wd.setWaiter(waiter);
        waiter.serve("TheViper");
        waiter.check("TheViper");
        wd.service("TheViper");
waiter说:要点什么?TheViper
waiter说:结账?TheViper
//直接调用,增强不起作用
你好!TheViper
waiter说:要点什么?TheViper
你好!TheViper
waiter说:结账?TheViper

复合切面

spring提供ComposablePointcut把两个切点组合起来,通过切点的复合运算表示。
ComposablePointcut本身也是一个切点,它实现了Pointcut接口。
图片描述
交集运算的方法
图片描述
并集运算
图片描述

import java.lang.reflect.Method;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.support.ComposablePointcut;
import org.springframework.aop.support.ControlFlowPointcut;
import org.springframework.aop.support.NameMatchMethodPointcut;

public class GreetingComposablePointcut {
    public Pointcut getIntersectionPointcut(){
        ComposablePointcut cp=new ComposablePointcut();
        Pointcut pt1=new ControlFlowPointcut(WaiterDelegate.class,"service");
        MethodMatcher pt2=new NameMatchMethodPointcut(){
            public boolean matches(Method method, Class<?> cls) {// 切点方法静态匹配
                return "check".equals(method.getName());
            }
        };
        return cp.intersection(pt1).intersection(pt2);
    }
}
<bean id='gcp' class='com.GreetingComposablePointcut'></bean>
<bean id='composableAdvisor' class='org.springframework.aop.support.DefaultPointcutAdvisor' 
p:pointcut='#{gcp.intersectionPointcut}' p:advice-ref='greetBeforeAdvice'></bean>
<bean id='waiter3' class='org.springframework.aop.framework.ProxyFactoryBean' 
p:interceptorNames='composableAdvisor' p:target-ref='waiterTarget'/>

\ #{gcp.intersectionPointcut}表示引用gcp.getIntersectionPointcut()方法返回的复合切点

        Waiter waiter=(Waiter)ctx.getBean("waiter3");
        WaiterDelegate wd=new WaiterDelegate();
        wd.setWaiter(waiter);
        waiter.serve("TheViper");
        waiter.check("TheViper");
        wd.service("TheViper");
waiter说:要点什么?TheViper
waiter说:结账?TheViper
//直接调用,增强不起作用
waiter说:要点什么?TheViper
你好!TheViper//匹配check方法
waiter说:结账?TheViper

引介切面


TheViper
465 声望16 粉丝