Spring AOP核心

什么是AOP

AOP用于实现应用中的横切关注点(crosscutting concerns) 。
横切关注点是指与正常业务逻辑紧耦合的其它逻辑,例如日志、权限等等。
通过AOP,可以将不同的关注点模块化,然后应用到不同的业务逻辑中,达到复用和解耦的目的。
AOP是OOP的补充,并不能取代OOP。

AOP术语

连接点(Joinpoint):程序运行时的某个点,如调用某个方法。连接点定义了可以插入AOP逻辑的程序单元。Spring AOP中,只有方法可以定义为连接点。
建议(Advice):AOP要在连接点插入的额外逻辑。例如建议方法(连接点)执行之前先执行什么(MethodBeforeAdvice)。Spring AOP中,用Advice接口表示建议。
切点(Pointcut):连接点的集合,方便统一建议多个连接点。例如,建议某个包下的所有方法都打印访问日志。Spring AOP中用Pointcut接口表示切点。
切面(Aspect):建议+切点形成一个切面。完整地实现了“在什么地方(切点)加入什么逻辑(建议)”的AOP目标。Spring AOP中,用Advisor接口表示切面。
织入(Weaving):将切面加入到应用程序的过程。有多种织入时机,编译时、运行时、加载时(AspectJ)
目标(Target):织入AOP的目标对象,也叫被建议的对象。
引入(Introduction):给一个对象引入额外的方法或字段从而改变该对象的结构。引入可以使某个对象实现特定接口的方法,即使该对象对应的类定义时并没有implements该接口。

静态AOP和动态AOP

静态AOP,在应用构建时织入,通过修改字节码实现。静态AOP,性能更好,但每次修改都要重新编译。AspectJ的编译时织入就是一种静态AOP。

Spring AOP是动态AOP,在运行时织入,通过动态代理实现。性能比静态AOP差点,但不需要重新编译。

Spring AOP核心使用方便,又提供了诸如事务管理等基于AOP的服务,所以Spring AOP是首选。AspectJ性能更好,AOP特性支持更全面,真有这方面需求,可用AspectJ。

Spring AOP基本架构

Spring AOP的核心是代理,通过ProxyFactory控制织入和代理创建的过程。

在运行时,Spring分析IOC容器中所有bean的横切关注点(连接点),然后动态创建代理bean。调用方依赖注入时,注入的是代理bean,而不是目标的bean。然后代理bean会根据运行条件织入相应的建议。

Spring支持两种代理实现:JDK动态代理和CGLIB代理。对于实现特定接口的目标对象,使用JDK动态代理,否则使用CGLIB代理。

Spirng AOP中,Advisor接口表示切面(切点+建议),Advisor又有两个子接口: PointcutAdvisor和IntroductionAdvisor,分别用于实现基于切点和引入的AOP逻辑。

示例:通过AOP,改变特工(Agent)的说话方式(speak方法)

package demo.aop;

public class Agent {
    //连接点
    public void speak() {
        System.out.print("Bond");
    }
}
//MethodInterceptor是一个典型的建议
public class AgentDecorator implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.print("James ");//方法执行前干嘛
        Object object=invocation.proceed();
        System.out.println("!");//方法执行后干嘛
        return object;
    }
}
package demo.aop;

import org.springframework.aop.framework.ProxyFactory;

public class AgentAOPDemo {
    public static void main(String[] args) {
        Agent target=new Agent();//目标对象
        ProxyFactory proxyFactory=new ProxyFactory();//代理工厂,Spring AOP核心类
        proxyFactory.addAdvice(new AgentDecorator());//添加建议
        proxyFactory.setTarget(target);//添加目标
        Agent proxy= (Agent) proxyFactory.getProxy();//创建代理
        target.speak();
        System.out.println();
        proxy.speak();//调用代理的方法,目标对象的方法逻辑被改变
    }
}

ProxyFactory 用于编程方式的AOP织入和代理创建。

ProxyFactory的addAdvice方法添加建议,实际上是添加切面。通过判断参数类型,创建相应的Advisor实现。这里实际上是addAdvisor(pos, new DefaultPointcutAdvisor(advice))。
DefaultPointcutAdvisor属于PointcutAdvisor一种实现,适用于任意切点和建议,所以会对目标对象的所有连接点(方法)执行建议。

ProxyFactory的setTarget方法添加目标对象,实际是在父类AdvisedSupport中实现的。通过 new SingletonTargetSource(target),这是一个静态的TargetSource,所以每次都是直接返回target。

ProxyFactory的getProxy()方法创建代理。实际上是通过父类ProxyCreatorSupport创建默认的AOP代理工厂DefaultAopProxyFactory。

DefaultAopProxyFactory是AopProxyFactory接口的默认实现类,用于创建JDK动态代理或CGCLIB代理。当isOptimize、isProxyTargetClass、 hasNoUserSuppliedProxyInterfaces条件之一满足时,使用CGLIB代理,否则使用JDK动态代理。

所以,这里使用的是CGLIB代理。return new ObjenesisCglibAopProxy(config); 它调用父类CglibAopProxy构造函数,通过AdvisedDispatcher将配置分发到目标对象,Dispatcher for any methods declared on the Advised class.

Spring AOP中的建议

Spring AOP中用Advice接口表示建议。它是一个标记接口,没有任何方法。这意味着建议可用不同的方式实现,例如拦截器(Interceptors)。

Interceptor接口也没有任何方法,要实现某种建议,就要使用其对应的子接口,如MethodInterceptor。

建议接口功能
BeforeMethodBeforeAdvice方法执行前的建议,如认证和权限管理
After-ReturningAfterReturningAdvice方法正常返回之后的建议
After(finally)AfterAdvice方法返回之后的建议,包括异常之后
AroundMethodInterceptor适用于各种情况
ThrowsThrowsAdvice方法抛出异常之后的建议,例如错误日志集中处理
IntroductionIntroductionInterceptor引入额外的方法

Spring AOP中的切点

Spring AOP中用Pointcut接口表示切点。

public interface Pointcut {
	ClassFilter getClassFilter ();
	MethodMatcher getMethodMatcher();
}

Spring分析一个Pointcut是否应用于某个方法时,先用该类过滤器ClassFilter来匹配目标方法所在类。

public interface ClassFilter {
	boolean matches(Class<?> clazz);
}

类匹配成功后,再匹配方法:

public interface MethodMatcher {
	boolean matches(Method m, Class<?> targetClass);
	boolean isRuntime();
	boolean matches(Method m, Class<?> targetClass, Object[] args);
}

isRuntime()表示静态匹配还是动态匹配。
如果是静态匹配,第一次检查时调用matches(Method m, Class<?> targetClass)方法,缓存结果,之后再检查时直接返回结果。
如果是动态匹配,matches(Method m, Class<?> targetClass)方法返回true后,再进一步调用matches(Method m, Class<?> targetClass, Object[] args)方法,该方法可以对调用时的参数进行匹配。
所以静态匹配性能好,动态匹配精度高。

Spring 提供的切点实现

Spring已经提供了很多切点实现类:

类名功能
DynamicMethodMatcherPointcut便于实现动态匹配的基类
StaticMethodMatcherPointcut便于实现静态匹配的基类
AnnotationMatchingPointcut基于Java注解匹配类或方法
AspectJExpressionPointcut基于AspectJ表达式匹配
JdkRegexpMethodPointcut基于正则表达式匹配
NameMatchMethodPointcut基于方法名匹配
ComposablePointcut用于组合多个切点,union,intersection
ControlFlowPointcut特殊的切点,用于匹配定义在某个方法内的所有方法

示例:使用AspectJ表达式定义切点

首先需要添加AspecJ相关依赖:

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
package demo.aop;

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;

public class AgentAOPDemo {
    public static void main(String[] args) {
        //使用AspectJ表达式定义的切点
        AspectJExpressionPointcut pc = new AspectJExpressionPointcut();
        pc.setExpression("execution(* speak(..))");//所有speak方法
        //使用DefaultPointcutAdvisor
        Advisor advisor = new DefaultPointcutAdvisor(pc, new AgentDecorator());

        ProxyFactory proxyFactory=new ProxyFactory();//代理工厂,Spring AOP核心类
        proxyFactory.addAdvisor(advisor);
        proxyFactory.setTarget(new Agent());//添加目标
        Agent proxy= (Agent) proxyFactory.getProxy();//创建代理
        proxy.speak();//调用代理的方法,目标对象的方法逻辑被改变
    }
}

Spring AOP中的Introduction

关于Introduction怎么翻译,我的理解是“引入”,即为目标对象引入新的功能。
使用Spring Introduction可以为目标对象动态引入任意接口的方法。
Spring把Introduction看作一种特殊的around建议(继承了MethodInterceptor),由IntroductionInterceptor接口表示。
在这里插入图片描述
Spring提供了DelegatingIntroductionInterceptor类,方便我们实现IntroductionInterceptor接口。所以,要构建一个Introduction,只要继承DelegatingIntroductionInterceptor类,同时实现我们要引入的方法所在的接口。

与PointcutAdvisor相对应,基于引入的AOP切面用IntroductionAdvisor接口表示,Spring也提供了默认实现类DefaultIntroductionAdvisor

Introduction和Advice的区别

首先Introduction也是一种建议,它本质上是一种around advice。
使用基于Advice的建议时,一般可以把同一个建议实例应用到多个对象,这种方式叫:per-class life cycle
而使用基于Introduction的建议时,建议成了目标对象的一部分,所以必须为每个目标对象使用唯一的建议实例。这种方式叫:per-instance life cycle

示例:检查对象是否被修改

检查对象是否被修改,可以定义IsModified接口,然后构建一个实现该接口的Introduction建议,当把改Introduction建议应用到目标对象时,就具备检查对象是否被修改过的能力了。

IsModified接口:判断对象是否被修改

package demo.aop;

public interface IsModified {
    boolean isModified();
}

IsModifiedMixin类:定义一个引入了IsModified接口的Introduction建议。

package demo.aop;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class IsModifiedMixin extends DelegatingIntroductionInterceptor implements IsModified {
    private boolean isModified = false;//对象被修改标志
    //缓存setter-getter方法对,避免每次都通过反射获取getter方法
    private Map<Method, Method> methodCache = new HashMap<>();

    @Override
    public boolean isModified() {
        return isModified;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (!isModified) {
            //setter方法被调用时,对象可能被修改
            if ((invocation.getMethod().getName().startsWith("set"))
                    && (invocation.getArguments().length == 1)) {
                Method getter = getGetter(invocation.getMethod());
                if (getter != null) {
                    Object newVal = invocation.getArguments()[0];
                    Object oldVal = getter.invoke(invocation.getThis(), null);
                    if ((newVal == null) && (oldVal == null)) {
                        isModified = false;
                    } else if ((newVal == null) && (oldVal != null)) {
                        isModified = true;
                    } else if ((newVal != null) && (oldVal == null)) {
                        isModified = true;
                    } else {
                        isModified = !newVal.equals(oldVal);
                    }
                }
            }
        }
        //一定要调用父类的invoke方法,因为是由DelegatingIntroductionInterceptor负责分发该方法调用的
        return super.invoke(invocation);
    }

    /**
     * 通过setter方法获取getter方法,使用反射
     * @param setter setter方法
     * @return 对应的getter方法
     */
    private Method getGetter(Method setter) {
        Method getter = methodCache.get(setter);
        if (getter != null) {
            return getter;
        }
        String getterName = setter.getName().replaceFirst("set", "get");
        try {
            getter = setter.getDeclaringClass().getMethod(getterName, null);
            synchronized (methodCache) {
                methodCache.put(setter, getter);
            }
            return getter;
        } catch (NoSuchMethodException ex) {
            return null;
        }
    }
}

封装成一个基于Introduction的切面:

package demo.aop;

import org.springframework.aop.support.DefaultIntroductionAdvisor;

public class IsModifiedAdvisor extends DefaultIntroductionAdvisor {
    public IsModifiedAdvisor() {
        super(new IsModifiedMixin());
    }
}

目标对象所在类:

package demo.aop;

public class Contact {
    private String name;
    private String phoneNumber;
    private String email;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

演示类:

package demo.aop;

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

public class IntroductionDemo {
    public static void main(String[] args) {
        Contact target = new Contact();
        target.setName("John Mayer");
        IntroductionAdvisor advisor = new IsModifiedAdvisor();
        ProxyFactory pf = new ProxyFactory();
        pf.setTarget(target);
        pf.addAdvisor(advisor);
        pf.setOptimize(true);//强制使用CGLIB代理
        Contact proxy = (Contact) pf.getProxy();//如果是JDK代理,这里会报错,因为proxy并没有实现Contact接口
        IsModified proxyInterface = (IsModified)proxy;
        System.out.println("Is Contact?: " + (proxy instanceof Contact));
        System.out.println("Is IsModified?: " + (proxy instanceof IsModified));
        System.out.println("Has been modified?: " + proxyInterface.isModified());
        proxy.setName("John Mayer");
        System.out.println("Has been modified?: " + proxyInterface.isModified());
        proxy.setName("Eric Clapton");
        System.out.println("Has been modified?: " + proxyInterface.isModified());
    }
}

声明式AOP配置

ProxyFactory用于编程式AOP实现,实际开发中一般不直接使用,而是使用声明式AOP配置。

Spring支持三种声明式AOP配置:

  • ProxyFactoryBean:实现了FactoryBean,告诉SpringApplicationContext通过一个bean去创建代理,一般不使用。
  • Spring aop namespace:在基于xml风格的SpringApplicationContext配置中使用
  • @AspectJ-style annotations:在基于Java注解风格的SpringApplicationContext配置中使用。

示例:使用AspectJ风格注解配置AOP

package demo.aop;

import org.springframework.stereotype.Component;

@Component
public class Agent {
    //连接点
    public void speak() {
        System.out.print("Bond");
    }
}
package demo.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class AgentDecorator {

    @Pointcut("execution(* speak(..))")
    public void speakPointCut() {
    }

    @Around("speakPointCut()")
    public Object speakAroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.print("James ");
        Object val= proceedingJoinPoint.proceed();
        System.out.println("!");
        return val;
    }
}

package demo.aop;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.GenericApplicationContext;

@Configuration
@ComponentScan("demo.aop")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AgentAOPDemo {
    
    public static void main(String[] args) {
        GenericApplicationContext context=new AnnotationConfigApplicationContext(AgentAOPDemo.class);
        Agent agent= (Agent) context.getBean("agent");//spring 会获取代理,而不是原来的bean
        agent.speak();
    }
}

总结

  1. AOP作为OOP的补充,用于解决应用中的横切问题。横切问题就是遍布在各个对象正常业务逻辑中的系统关注点,例如日志记录、权限控制等等。
  2. AOP两个核心概念:建议和切点。建议是横切问题的抽象,即我们要AOP额外干什么。切点即切入点,也就是把建议用到什么地方。两者结合就是“在什么地方再干点什么”。
  3. Spring 提供了两类建议:Advice和Introduction。Introduction是一种特殊的建议,可以为目标对象引入任意接口的方法。
  4. Spring AOP的核心是代理,通过创建代理对象,将实际方法调用转发到代理对象,从而实现AOP织入。Spring提供两种代理对象:JDK动态代理和CGLIB代理。
  5. ProxyFactory用于编程式AOP实现,实际开发中一般不直接使用,而是用基于AOP的服务,如声明式事务管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值