美文网首页
Spring AOP篇

Spring AOP篇

作者: Goet | 来源:发表于2020-07-27 17:29 被阅读0次

一、什么是AOP?

AOP(Aspect Oriented Programming)面向切面编程。

AOP和OOP的区别与联系
OOP(面向对象编程)针对问题领域中以及业务处理过程中存在的实体及其属性和操作进行抽象和封装,面向对象的核心概念是纵向结构的,其目的是获得更加清晰高效的逻辑单元划分。

而 AOP则是针对业务处理过程中的切面进行提取,用面向对象的思路,将业务操作对象的核心功能和对它的其他服务性功能代码分离,即某一个操作在各个模块中都有涉及,这个操作就可以看成“横切”存在于系统当中。 AOP则将这些操作与业务逻辑分离,使程序员在编写程序时可以专注于业务逻辑的处理,而利用 AOP将贯穿于各个模块间的横切关注点自动耦合进来。

我简单的理解为,OOP是纵向,主要负责业务处理;AOP是横向,负责对每个纵向业务不同阶段执行相同的操作。

AOP是OOP的延续和补充,二者 并不是相互竞争的两种技术,

为什么需要AOP?AOP能干啥?
开发中经常会面临的种种非功能性需求(操作日志、权限控制、性能监测等等),在很多方法中需要做这类重复的工作,如果直接写在方法中,会有很多重复代码,而且难以维护。这个时候,AOP就是一个非常好的选择。

Spring的一个关键组件是AOP框架,Spring AOP 模块提供拦截器来拦截一个应用程序,例如,当执行一个方法时,你可以在方法执行之前或之后添加额外的功能。

二、Spring AOP原理

Spring AOP是通过动态代理来实现的,要弄清楚Spring AOP的原理,首先要明白什么是动态代理。
首先回顾一下设计模式里的代理模式。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个人或一个机构代表另一个人或者一个机构采取行动。某些情况下,客户不想或者不能够直接引用一个对象,代理对象可以在客户和目标对象直接起到中介的作用。客户端分辨不出代理主题对象与真实主题对象。代理模式可以并不知道真正的被代理对象,而仅仅持有一个被代理对象的接口,这时候代理对象不能够创建被代理对象,被代理对象必须有系统的其他角色代为创建并传入。
更详细内容可以看这一篇:设计模式(一)

然后再来了解一下静态代理。

1.静态代理

直接上代码举例。
首先我们有一个Service接口

public interface Service{
    void doSomething();
}

有Service接口的实现类ServiceImpl

public class ServiceImpl implements Service {
    @Override
    public void doSomething() {
        System.out.println("do something...");
    }
}

然后我们想在do something前后做点别的事情(实际开发过程中可能是参数校验、统计方法执行时间、检查对象状态、记录日志等等),也就是有一些前置或者后续动作,但是又不想改变现有的实现类,该怎么办?这个时候就可以使用代理模式来做方法增强。

创建一个代理类

public class ServiceStaticProxy implements Service {
    private Service service;

    public ServiceStaticProxy(Service service) {
        this.service = service;
    }

    @Override
    public void doSomething() {
        System.out.println("pre actions");
        service.doSomething();
        System.out.prinln("post actions");
    }
}

测试类App.java

public class App {

    public static void main(String[] args) {
        ServiceImpl myService = new ServiceImpl(); // 真实对象
        ServiceStaticProxy proxy = new ServiceStaticProxy(myService); // 与代理对象建立联系
        proxy.doSomething(); // 使用代理
    }
}

测试结果:


测试结果

这样就可以在不修改目标对象功能的前提下,对目标功能进行扩展。静态代理方式在编译时就确定了代理类与目标类之间的关系。

考虑一下,如果ServiceImpl类中还有别的方法,也需要加上前置或者后续动作,就不得不将代码写n遍。进一步如果还有别的Service接口实现类里面的所有方法也需要加上这些动作,那要重复的地方就更多了。这个时候,动态代理闪亮登场。

2.动态代理

动态代理有两种实现方式,JDK反射机制,和cglib。

2.1 JDK动态代理

JDK提供了InvocationHandler接口和Proxy类,借助这两个工具可以实现动态代理。
还是拿上一小节静态代理中使用的Service接口和ServiceImpl类举例,搞一个JDK动态代理工厂类:

public class JDKProxyFactory {
    private Object target;

    public JDKProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        Object proxy = Proxy.newProxyInstance(
            target.getClass().getClassLoader(), 
            target.getClass().getInterfaces(),
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("pre actions");
                    Object result = method.invoke(target, args);
                    System.out.println("post actions");
                    return result;
                }
            });
        return proxy;
    }
}

即使Service接口新增一个方法doSomethingElse也可以达到相同的效果,减少重复代码。

测试类App.java

public class App {

    public static void main(String[] args) throws Throwable {
        Service proxy = (Service) new JDKProxyFactory(new ServiceImpl()).getProxyInstance();
        proxy.doSomething();
        proxy.doSomethingElse();
    }
}

测试结果:


测试结果

可以发现,使用JDK动态代理的目标类必须实现某个接口。如果想要代理的目标类没有实现任何接口怎么办?还有另外一种动态代理的方式。

2.2 cglib动态代理

还是一样的Service接口和ServiceImpl实现类,搞一个cglib动态代理工厂类

public class CglibProxyFactory implements MethodInterceptor {
    private Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        // Enhancer工具类
        Enhancer enhancer = new Enhancer();
        // 设置父类
        enhancer.setSuperclass(target.getClass());
        // 设置回调函数
        enhancer.setCallback(this);
        // 创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
        throws Throwable {
        System.out.println("pre actions");
        Object result = method.invoke(target, objects);
        System.out.println("post actions");
        return result;
    }
}

这里的MethodInterceptor接口和Enhancer类是spring-core.jar包中的。

App.java测试类还是一样,不过把jdk动态代理工厂改成了cglib的:

public class App {

    public static void main(String[] args) throws Throwable {
        Service proxy = (Service) new CglibProxyFactory(new ServiceImpl()).getProxyInstance();
        proxy.doSomething();
        proxy.doSomethingElse();
    }
}

测试结果:


测试结果

cglib的实现方式是创建目标类的子类,所以有关键字final修饰的类是无法做动态代理的,因为其无法被继承。

2.3 spring AOP的动态代理

spring AOP的动态代理原理就是jdk方式+cglib方式。对需要做动态代理的类先判断其有没有实现接口,如果有,就用jdk的方式;没有的话,就是cglib方式。

三、AOP相关术语

Aspect(切面):Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。

Joinpoint(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。

Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。

Advice(通知):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。

Target(目标对象):织入 Advice 的目标对象。

Weaving(织入):将切面应用到目标对象并导致代理对象创建的过程。

其中,通知又分为以下几类:
前置通知(Before advice):在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)。

后置通知(After returning advice):在某连接点正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。

异常通知(After throwing advice):在方法抛出异常退出时执行的通知。

最终通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

环绕通知(Around Advice):包围一个连接点的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。

四、基于AOP的xml架构

1.导入 spring-aop j架构,如下:
beans.xml

还需要在应用程序的CLASSPATH中使用以下AspectJ 库文件:
aspectjrt.jar
aspectjweaver.jar
aspectj.jar
aopalliance.jar

2.声明aop
beans.xml

作为切面的Logging类:


Logging.java

作为切入点的Student类:


Student.java

MainApp.java内容:


MainApp.java

Beans.xml中bean的声明:


beans.xml

运行结果:


运行结果

五、基于AOP的@AspectJ注解

类比xml声明AOP
首先是Logging类的代码:

package com.tutorialspoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
@Aspect
public class Logging {
   /** Following is the definition for a pointcut to select
    *  all the methods available. So advice will be called
    *  for all the methods.
    */
   @Pointcut("execution(* com.tutorialspoint.*.*(..))")
   private void selectAll(){}
   /** 
    * This is the method which I would like to execute
    * before a selected method execution.
    */
   @Before("selectAll()")
   public void beforeAdvice(){
      System.out.println("Going to setup student profile.");
   }
   /** 
    * This is the method which I would like to execute
    * after a selected method execution.
    */
   @After("selectAll()")
   public void afterAdvice(){
      System.out.println("Student profile has been setup.");
   }
   /** 
    * This is the method which I would like to execute
    * when any method returns.
    */
   @AfterReturning(pointcut = "selectAll()", returning="retVal")
   public void afterReturningAdvice(Object retVal){
      System.out.println("Returning:" + retVal.toString() );
   }
   /**
    * This is the method which I would like to execute
    * if there is an exception raised by any method.
    */
   @AfterThrowing(pointcut = "selectAll()", throwing = "ex")
   public void AfterThrowingAdvice(IllegalArgumentException ex){
      System.out.println("There has been an exception: " + ex.toString());   
   }  
}

Student类的内容:


Student.java

MainApp.java内容:


MainApp.java

Beans.xml配置:


beans.xml

程序输出:


程序输出

相关文章

网友评论

      本文标题:Spring AOP篇

      本文链接:https://www.haomeiwen.com/subject/ixttrktx.html