spring中的AOP

程序中的问题

在实际开发中我们需要添加一些非业务的代码,例如提交事务,记录日志等等,此时你可能会这些编写程序:

定义一个记录日志的工具类:

public class MyLog {

    /**
     * 记录日志
     * @param clazz
     */
    public static void doLog(Class<?> clazz){
        System.out.println("记录日志:" + clazz.getName());
    }
}

定义一个提交事务的工具类:

public class MyTransaction {

    public static void doTransaction(Class<?> clazz){
        System.out.println("提交事务:" + clazz.getName());
    }
}

在dao中使用这两个工具类:

@Repository("userDao")
public class UserDaoImpl implements UserDao {

    @Override
    public void addUser() {
        //记录日志
        MyLog.doLog(this.getClass());

        //业务逻辑
        System.out.println("添加用户数据");

        //提交事务
        MyTransaction.doTransaction(this.getClass());
    }

    public void doOther(){
        System.out.println("做一些其他的事情");
    }
}

上面这段程序还是有些问题的,在增删改查的每个方法中都需要使用上面两个工具类,这样会导致这两个工具类侵入到了业务逻辑的代码中,影响了业务逻辑代码的可读性,降低了代码的可维护性,同时也增加了开发难度。

使用动态代理

动态代理可以在不修改主业务逻辑的前提下,扩展和增强其功能,这里我们使用jdk自带的动态代理来解决上面问题。

创建动态代理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //记录日志
        MyLog.doLog(target.getClass());

        //业务逻辑
        Object invoke = method.invoke(target, args);

        //提交事务
        MyTransaction.doTransaction(target.getClass());

        return invoke;
    }
}

修改之前的UserDaoImpl类:

@Override
public void addUser() {
    System.out.println("添加用户数据");
}

创建测试方法:

import com.monkey1024.dao.UserDao;
import com.monkey1024.dao.impl.UserDaoImpl;
import com.monkey1024.util.MyInvocationHandler;

import java.lang.reflect.Proxy;

public class Test02 {

    public static void main(String[] args) {

        //创建目标类对象
        UserDao userDao = new UserDaoImpl();

        //创建代理
        UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new MyInvocationHandler(userDao));

        userDaoProxy.addUser();
    }
}

通过动态代理的方式,可以看到在UserDaoImpl类中只有业务逻辑代码,非常整洁。

AOP简介

AOP(Aspect Orient Programming),面向切面编程,是面向对象编程 OOP 的一种补充。在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。例如转账功能,在转账代码的前后需要一些非业务方面的处理,权限控制,记录日志,事务的开启与结束,这些代码就可以使用AOP将其切入到转账代码的前后,这样就可以很好地分离业务代码和非业务代码。
AOP的优点就是降低代码之间的耦合,提高代码的复用性。

spring底层就是采用动态代理模式实现AOP的。采用了两种代理:

  • JDK 的动态代理,如果被代理了实现了接口,会默认使用jdk的动态代理。底层通过反射方式创建代理类的对象
  • CGLIB的动态代理,如果类没有实现接口,会使用CGLIB动态代理。底层是对代理类生成的class文件加载进来,通过修改其字节码生成子类来创建代理对象

spring之所以会引入这两种方式是因为其各有优缺点,
从性能上讲,使用字节码处理的CGLIB要比使用反射的JDK动态代理好。
从耦合度上讲,jdk要好于额外需要依赖字节码处理框架ASM的CGLIB。

面向切面

AOP的术语

(1)目标对象(Target)
目标对象指 将要被增强的对象。即包含主业务逻辑的类的对象。上例中的UserDaoImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

(2)切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知,实际就是对业务逻辑的一种增强。

(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。

(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,则 addUser()为切入点,而 doOther()仅为连接点。 被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。

(5)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例中的MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。Advice有下面几种,这里使用常用的AspectJ方式:

  • 前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
  • 后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
  • 异常通知(After throwing advice):在连接点抛出异常后执行
  • 最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
  • 环绕通知(Around advice):在连接点之前和之后均执行。

(6)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。

(7)aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。

aop