【spring】---面向切面编程AOP

一、AOP介绍

AOP(Aspect Oriented Programming)是基于切面编程的,可无侵入的在原本功能的切面层添加自定义代码,一般用于日志收集、权限认证等场景。
AOP原理:在目标对象(target object)的某些方法(jointpoint)添加不同种类的操作(通知、增强操处理),最后通过某些方法(weaving、织入操作)实现一个新的代理目标对象。
在这里插入图片描述
切面:存放公共功能,主要包括通知/增强、切点。
通知/增强:是具体实现方法。
在这里插入图片描述

切点:定义通知应该切入到哪些连接点上。
连接点:在目标对象的字段和方法上。
织入:把切面应用到目标对象并创建新的代理对象的过程。
在这里插入图片描述

二、Spring AOP

在这里插入图片描述

1.spring AOP—基于代理的spring AOP

----接口

public interface Sleepable {
    public void sleep();
}

----接口实现类

public class SleepableImpl implements Sleepable{
    @Override
    public void sleep() {
        System.out.println(" sleep !!!");
    }
}

----切面类(通知)

public class SleepHelper implements MethodBeforeAdvice,AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("sleep after end!");
    }
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
       System.out.println("sleep before start!");
    }
}

----applicationContenxt.xml

<!--基于代理的经典AOP-->
   <!-- 切面类(通知) -->
<bean id ="sleepHelper" class="com.wwy.Aop.AchieveAOP.BaseAgentAOP.SleepHelper"></bean>

<bean id ="sleepableImpl" class="com.wwy.Aop.AchieveAOP.BaseAgentAOP.SleepableImpl"></bean>
   <!-- 定义切点  [pattern属性指定了正则表达式,它匹配所有的sleep方法] -->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
    <property name="pattern" value=".*sleep"></property>
</bean>
   <!-- 定义通知者 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
    <property name="advice" ref="sleepHelper"></property>
    <property name="pointcut" ref="sleepPointcut"></property>
</bean>
   <!-- 产生代理对象 -->
<bean id="sleepableImplProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="sleepableImpl"></property>
    <property name="interceptorNames" value="sleepHelperAdvisor"></property>
</bean>

----测试

@Service
public class AOPTestService {
    @Autowired
    SleepableImpl sleepable;
    @PostConstruct
    public void setterServiceWay1(){
      sleepable.sleep();
    }
}

—按照上面的配置,输出
sleep before start!
sleep !!!
sleep after end!
类之间的依赖关系如下:
在这里插入图片描述

2.spring AOP—纯POJO切面

—类

public class SleepPojoAspect {
    public void before(){
       System.out.println("sleep before pojo!");
    }
    public void afterReturning(){
        System.out.println("sleep after pojo!");
    }
    public void afterThrowing(Exception ex) throws Exception {
        System.out.println("sleep afterThrowing pojo!");
    }
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object proceed =null;
        if (!"".equals("admin")){
            System.out.println("核心方法被执行");
            proceed = pjp.proceed(pjp.getArgs());
            System.out.println("核心方法执行完");
        }
        return proceed;
    }
}

----applicationContenxt.xml

<!--POJO切面实现AOP-->
<bean id ="sleepPojoAspect" class="com.wwy.Aop.AchieveAOP.POJOAOP.SleepPojoAspect"></bean>
    <!--proxy-target-class="true" 表示使用cglib代理-->
    <!--配置AOP-->
<aop:config>
    <!--切面-->
    <aop:aspect id="aspect" ref="sleepPojoAspect">
        <!--切入点的配置-->
        <aop:pointcut id="pointcut" expression="execution(* com.wwy.Aop.AchieveAOP.*.*(..))"/>
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after-throwing method="afterThrowing" throwing="ex" pointcut-ref="pointcut"/>
        <aop:around method="around" pointcut-ref="pointcut"/>
        <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

----测试

@Service
public class AOPTestService {
    @Autowired
    SleepableImpl sleepable;
    @PostConstruct
    public void setterServiceWay1(){
      sleepable.sleep();
    }
}

【实际xml中同样配置了基于代理的AOP】输出结果
sleep before start!
sleep before pojo!
核心方法被执行
sleep !!!
sleep after pojo!
核心方法执行完
sleep after end!

说明,对同一个SleepableImpl类sleep()方法,两个AOP都进行了增强通知。

3.spring AOP—AspectJ注解驱动的切面

–目标对象类

@Component
public class SleepAspectDrive {
    public int add(){
        System.out.println("add way!");
        return 1;
    }
    public int modify(){
        System.out.println("modify way!");
        return 1;
    }
}

–切面类

@Component("sleepDriveAspectJ")
@Aspect
public class SleepDriveAspectJ {
    @Pointcut("execution(* com.wwy.Aop.AchieveAOP.AspectJDrive.SleepAspectDrive.*(..))")
    public void PointcutTion() {}
    @Before("PointcutTion()")
    public void BeforeMethod(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        System.out.println("BeforeMethod  The method   "+ methodName +"   parameter is  "+ Arrays.asList(args));
    }
    //后置通知,方法执行之后执行(不管是否发生异常)
    @After("PointcutTion()")
    public void AfterMethod(JoinPoint jp) {
        String methodName = jp.getSignature().getName();
        Object[] args = jp.getArgs();
        System.out.println("AfterMethod  The method    "+ methodName +"   parameter is  "+Arrays.asList(args));
    }
}

–applicationContext.xml配置

<context:component-scan base-package="com.wwy.*">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- AspectJ注解驱动的AOP -->
<aop:aspectj-autoproxy />
   <aop:config proxy-target-class="true"></aop:config>
<aop:aspectj-autoproxy/>

—测试

@Service
public class AOPTestService {
    @Autowired
    SleepAspectDrive sleepAspectDrive;
    @PostConstruct
    public void setterServiceWay1(){
        sleepAspectDrive.add();
        sleepAspectDrive.modify();
    }
}

3.spring AOP—注入式AspectJ切面

如果你的 AOP 需求超过了简单的方法调用(如构造器或属性拦截),注入式AspectJ切面能够帮助你将值注入到AspectJ驱动的切面中。 【不介绍了】

三、Spring AOP实现步骤

前面介绍了各种AOP方式代码实现。有这样疑问,利用Spring AOP实现了我们的功能,关键是哪几步?
A.得到代理对象;
B.利用递归责任链执行前后置通知及目标方法。

3.1.如何生成代理对象

首先,需要创建代理工厂【代理工厂需要信息:拦截器数组,目标对象接口数组,目标对象】;
创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器【用于最终的调用目标方法】;
调用getProxy方法时,根据条件返回代理对象【使用JDK或CgLib来生成代理对象,在创建代理对象时,会创建一个外层拦截器,该拦截器用于控制整个AOP的流程】。

3.1.1代理对象调用过程

当对代理对象进行调用,会触发外层拦截器;
外层拦截器根据代理配置信息,创建内层拦截器链【拦截器链设计模式就是职责链模式】。创建的过程中,会根据表达式判断当前拦截是否匹配这个拦截器;
当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法,最后返回。
在这里插入图片描述

3.1.2ProxyFactoryBean

在这里插入图片描述
配置ProxyFactoryBean主要分为以下几步:
A.定义使用的Advisor通知器,这个通知器应当作为一个Bean来定义。这个通知器的实现定义了需要对目标对象进行增强的切面行为,也就是Advice通知。
B.定义ProxyFactoryBean,把它作为一个Bean来定义,它是封装AOP功能的主要类。
C.定义target属性,作为target属性注入的Bean,是需要用AOP通知器中的切面应用来增强的对象,也就是前面所提到的base对象。

在这里插入图片描述

3.2.代理对象是如何执行某个方法【“递归责任链执行前后置通知及目标方法”】?

按照之前的分析,代理对象最终底层确定是选择JDK动态代理或CgLib动态代理来实现的。

return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));

我们确定了已经获取到某个代理对象,那么接下来是如何执行某个方法?
以CglibAopProxy代理对象为例,我们会触发CglibAopProxy.intercept()。在CglibAopProxy类中

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    1、首先将 通知/增强 包装成拦截器,全部放到拦截器链中
    2、如果没有拦截器链,说明没有切面增强的功能,直接执行目标方法;否则,传入拦截器链和目标对象,最终执行new CglibMethodInvocation(...).proceed()
}

再接着ReflectiveMethodInvocation类的proceed()方法

public Object proceed() throws Throwable {
1、首先判断拦截器链是否执行完毕,如果完成,那么调用目标方法,并返回
     2、否则采用递归方法先执行完所有的拦截器,最后在执行目标方法
  }

前置before拦截器MethodBeforeAdviceInterceptor

public Object invoke(MethodInvocation mi) throws Throwable {
    this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
    return mi.proceed();
}

先反射执行@Before方法,在调用mi.proceed()。这里mi就是CglibMethodInvocation对象,也就是递归责任链执行通知和方法。

后置After拦截器AspectJAfterAdvice

public Object invoke(MethodInvocation mi) throws Throwable {
    Object var2;
    try {
        var2 = mi.proceed();
    } finally {
        this.invokeAdviceMethod(this.getJoinPointMatch(), (Object)null, (Throwable)null);
    }
    return var2;
}

先执行mi.proceed(),再反射调用@After方法。

四、Spring AOP底层实现

在这里插入图片描述

按照前面代码示例分析,AOP生成的代理对象,最终底层是根据条件判断调用JDK动态代理还是CgLib动态代理。

4.1.动态代理

动态代理实际的操作对象,都是在目标类的基础上,生成一个具有代理目的,增强功能的新的代理proxy字节码类。都是在复合class字节码的规范下,对class字节文件内的内容进行操作,最后将生成的字节类对应的代理类,通过类加载器加载到JVM中,进行使用。

基于接口的代理----jdk自带的动态代理规则的实现方式;
基于类的代理----基于字节类增强的类代理,如cglib、javassist等。

4.2.JDK动态代理

4.2.1 JDK动态代理原理

通过反射类Proxy的newProxyInstance方法可以将一个目标对象转换为代理对象,另外继承InvocationHandler回调接口的子类,使用invoke方法对目标对象的方法进行了加强。
【**注意:**JDK动态代理,目标对象一定需要实现接口,代理对象不需要实现接口。】

Proxy代理类的执行过程

Proxy.newInstance() --> Constructor.copy() ----Constructor.newInstance() ----->
 $Proxy代理字节类生成 ---> Method.copy()-----> $Proxy.invoke()(目标接口方法调用) 
 ---> InvocationHandler.invoke() ---> Method.invoke()

生成代理类Proxy0 (指的就是cl),Proxy0有一个构造函数,构造函数的参数是 InvocationHandler 类型。
通过反射,生成类Proxy0的一个构造器对象。
再通过反射,生成类Proxy0的一个实例对象。

4.2.2 JDK动态代理–代码示例

public class DynamicProxyAgentJDK implements InvocationHandler{

    private Object target; //接收的目标对象
    /**  invoke()方法会自动对目标对象的各个方法进行加强
     * @param proxy    指代我们所代理的那个真实对象
     * @param method   指代的是我们所要调用真实对象的某个方法的Method对象
     * @param args     指代的是调用真实对象某个方法时接受的参数
     */
 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if(method.getName().equals("request")){
       //做前置加强
        return invokeAdvice(method,args,"before");
    }else if(method.getName().equals("response")){
        //做环绕加强
        return invokeAdvice(method,args,"around");
    }
    return null;
}
private Object invokeAdvice( Method method, Object[] args,String type){
    // BeforeAdvice
    Object retVal = null;
    try {
        // AroundAdvice
        retVal = method.invoke(target, args);
        // AroundAdvice
        // AfterReturningAdvice
    }catch (Throwable e) {
        // AfterThrowingAdvice
    }finally {
        // AfterAdvice
    }
    return retVal;
}
    /**
     * Proxy类----用来动态创建一个代理对象的类
     * newProxyInstance()方法-----得到一个动态的代理对象
     *   loader:      一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
     *   interfaces:  一个Interface对象的数组
     *   getClass():返回class类型的对象
     *   getClassLoader()是类加载器;getInterfaces()是获得这个对象所实现的接口。
     */
    public Object getJdkProxy(Object target){
        this.target = target;
        //获取JDK代理类对象
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
}

----测试

ProxySubject jdkProxySubject = (ProxySubject)new DynamicProxyAgentJDK().getJdkProxy(targetProxySubject);

4.3.CgLib动态代理

通过cglib来为那些没有接口的类创建代理对象。final方法、private方法不能代理。

4.3.1 CgLib动态代理–代码示例

public class DynamicProxyAgentCgLib implements MethodInterceptor{
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before 前置通知");
        Object result = null;
        try {
            result = methodProxy.invokeSuper(o, objects);
        }catch (Exception ex) {
            System.out.println("ex: " + ex.getMessage());
            throw ex;
        }finally {
            System.out.println("after 后置通知");
        }
        return result;
    }
    public Object getCgLibProxy(Class className){
        //获取Enhancer对象 创建增强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        //设置代理类的目标类 【如ProxySubjectTargetClass.class】
        enhancer.setSuperclass(className);
        //设置回调方法
        enhancer.setCallback(this);
        //获取代理对象
        return enhancer.create();
    }
}

—测试

/**
 * CgLib动态代理
 * 基于继承来实现代理,所以无法对final类、private方法和static方法实现代理
 */
 //获取代理对象
ProxySubject cglibProySubject = (ProxySubject) new DynamicProxyAgentCgLib().getCgLibProxy(ProxySubjectTargetClass.class);

—MethodInterceptor类有intercept()方法,这个方法有4个参数:
1)obj表示增强的对象,即实现这个接口类的一个对象;
2)method表示要被拦截的方法【目标对象的方法】;
3)args表示要被拦截方法的参数;
4)proxy表示要触发父类的方法对象;

getCgLibProxy()方法是传入父类字节码为入参,创建一个代理对象。然后intercept()方法拦截了所有目标对象的方法调用,methodProxy.invokeSuper(o, objects)通过代理类调用父类中的方法。obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    Object result = null;
    try {
        result = methodProxy.invokeSuper(o, objects);
    }catch (Exception ex) {
     }finally {
    }
    return result;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamBoy_W.W.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值