AOP使用以及异常

一、AOP是什么

AOP,即面向切面编程。AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制、异常处理等,封装起来,便于减少系统重复的代码,降低模块之间的耦合度。AOP注重的是许多解决问题的方法中的共同点。

img

二、AOP的实现

AOP的实现可以分为两种:1.静态织入 2.动态代理

(1)静态AOP

静态织入就是在编译期,切面直接以字节码的形式编译到目标字节码文件中。这种方式对系统的性能没有影响,但是灵活性不够。

(2)动态AOP

spring动态AOP有两种方式:

  • JDK动态代理:通过反射和动态编译实现
  • cglib动态代理:通过修改字节码来实现代理

这里我们主要讨论JDK动态代理的方式。
JDK动态代理的两个核心就是InvokationHandler和Proxy,下面我用一个简单的例子来说明JDK动态代理的实现。
由于JDK动态代理只能创建指定接口的动态代理,所以下面先提供一个Dog接口:

interface Dog {  
    // info()方法声明  
    public void info();  
  
    // run()方法声明  
    public void run();  
}  

然后为接口Dog创建一个实现方法:

class GunDog implements Dog {  
    // info方法实现,仅仅打印一个字符串  
    @Override  
    public void info() {  
        System.out.println("我是一只猎狗");  
    }  
  
    // run方法实现,仅仅打印一个字符串  
    @Override  
    public void run() {  
        System.out.println("我奔跑迅速");  
    }  
}  

在DogUtil中创建两个通用的方法(公共代码):

class DogUtil {  
    // 第一个拦截器方法  
    public void method1() {  
        System.out.println("-----------模拟第一个通用方法-----------");  
    }  
  
    // 第二个拦截器方法  
    public void method2() {  
        System.out.println("-----------模拟第二个通用方法-----------");  
    }  
} 

假设info()和run()代表两个要调用公共代码的方法,但是不能以硬编码的方式来进行调用,应该怎么办呢?可以借助Proxy和InvocationHandler来实现:当程序调用info()和run()方法时,系统将会“自动”将method1()和method2()两个通用方法插入到info()和run()方法。

class MyInvokationHandlerPro implements InvocationHandler {  
    // 需要被代理的对象  
    private Object target;  
  
    public void setTarget(Object target) {  
        this.target = target;  
    }  
  
    // 执行动态代理对象的所有方法时,都会被替换成执行如下的invoke方法  
    public Object invoke(Object proxy, Method method, Object[] args)  
            throws Exception {  
        DogUtil du = new DogUtil();  
        // 执行DogUtil对象中method1方法  
        du.method1();  
        // 以target作为主调来执行method方法  
        Object result = method.invoke(target, args);  
        // 执行DogUtil对象中method2方法  
        du.method2();  
        return result;  
    }  
}  

上面程序中的类是InvokationHandler的一个实现类,该实现类的invoke方法将会作为代理的方法实现,其中invoke方法包含了一行关键的代码,这行代码以target作为主调通过反射来执行method方法,这就实现了target对象的原有方法。

有了代理处理类以及需要被代理的对象target,我们还需要一段代码为指定的target生成动态代理:

class MyProxyFactory {  
    // 为指定target生成动态代理对象  
    public static Object getProxy(Object target) throws Exception {  
        // 创建一个MyInvokationHandler对象  
        MyInvokationHandlerPro handler = new MyInvokationHandlerPro();  
        // 为MyInvokationHandler设置target对象  
        handler.setTarget(target);  
        // 创建,并返回一个动态代理  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), handler);  
    }  
}  

上面的类是一个动态代理工厂类,它提供了一个getProxy方法,为target对象生成了一个动态代理对象。这个动态代理实现了与target相同的接口,所以动态代理对象可以当成target对象来使用。通俗一点说,就像为明星忙碌的经纪人,他们就是活生生的代理,帮助明星打理事务。
当程序调用动态代理对象的指定方法的时候,实际上就是执行MyInvokationHandlerPro 的invoke方法。
下面提供主程序来测试这种动态代理的效果:

public class TestProxy {  
    public static void main(String[] args) throws Exception {  
        // 创建一个原始的GunDog对象,作为target  
        Dog target = new GunDog();  
        // 以指定的target来创建动态代理  
        Dog dog = (Dog) MyProxyFactory.getProxy(target);  
        dog.info();  
        dog.run();  
    }  
}  

程序的执行结果为:

-----------模拟第一个通用方法-----------
我是一只猎狗
-----------模拟第二个通用方法-----------
-----------模拟第一个通用方法-----------
我奔跑迅速
-----------模拟第二个通用方法-----------

上面程序中的dog对象实际上就是动态代理的对象,只是该动态代理对象也实现Dog接口,所以也可以当成Dog对象使用。
这种动态代理在AOP被称为AOP代理。**AOP代理可以替代目标对象,AOP代理包含了目标对象的全部方法。**但是AOP代理中的方法与目标对象的方法存在差异:AOP代理里面的方法可以在执行目标方法之前,插入一些通用处理。

三、AOP中的“成员”介绍

连接点(JoinPoint)
在AOP表示为“在哪里做”,它用来定义在程序的什么地方能够通过AOP加入额外的逻辑。

切入点(Pointcut)
在AOP表示为“在哪里做的集合”。通过创建切入点,我们可以精确地控制程序中什么组件接到什么通知,而一个典型的切入点就是对某一个类的所有方法调用的集合。

通知、增加(Advice)
在AOP表示为“做什么”。增强即为在连接点上执行的行为,包括前置增强、后置增强、环绕增强。

方面/切面(Aspect)
在AOP表示为“在哪里做和做什么的集合”。切面是通知和切点的集合,通知和切点共同定义了切面的全部功能——它是什么,在何时何处完成其功能。

顾问(Advisor)
在Spring中,一个advisor就是一个aspect的完整的模块化标识。一般地,一个advisor包括通知和切入点,是通知和切入点的配置器。

目标对象(Target Object)
在AOP表示为“对谁做”。目标对象是需要被织入横切关注点的对象,也就是切入点选择的对象、需要被增强的对象。由于Spring AOP通过代理模式实现,从而这个对象永远是被代理对象。

AOP代理(AOP Proxy)
AOP框架使用代理模式创建的对象,从而实现在连接点处插入增加。Spring中,可以通过JDK动态代理或者CGLIB代理实现AOP代理,通过拦截器模型应用切面

织入(Weaving)
织入是一个过程,是将切面运用到目标对象从而创建出AOP代理对象的过程。织入是在编译时完成的,它通常是作为编译过程中的一个额外的步骤。

引入(introduction)
在AOP表示为“做什么(新增什么)”。引入也称为内部类型声明,为已有的类添加额外新的字段或方法。

四、AOP开发步骤

img

1. 功能横切:找出横切关注点
2.分离关注点:各自独立地实现这些横切关注点所需要完成的功能。
3.根据切入点匹配Target中的方法
4.根据AOP的配置寻找对应的Advice,然后进行重组/织入/结合。

五、问题

在学习Spring的AOP的注解和xml配置使用方式的时候,遇到异常:
在这里插入图片描述

六、配置的切面类

在这里插入图片描述

可以发现前置增强和后置增强都是没有参数的,代码编译没有问题;环绕增强必须要加一个参数(ProceedingJoinPoint),才可以执行目标方法,否侧只会执行本例中的“环绕前执行”和“环绕后执行”.(题外话)

造成本例中异常的原因在异常增强部分,异常增强的方法是带有参数的(Exception e),而在配置异常增强的时候,注解属性中没有定义这个参数e,Spring框架在加载本类的时候,当加载异常增强的注解代码时,会去找无参的方法afterThrowing() ,但是代码给出的却是有参的,Spring框架找不到自己想要的方法,所以就给报出了上述错误. 因为这个错误是在加载本类的时候报出的,所以尽管前置增强后置增强的代码没有问题,在执行的时候同样会报出同样的错.

七、解决办法

在这里插入图片描述

转载自miao2385951zoyoto