关于Spring Aop的深入理解

一、简介
1.AOP定义
AOP就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充,是Spring 一个重要的核心。
2.AOP作用
AOP主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

3.AOP应用场景
比较典型应用有:

  • 可以做接口日志记录
  • 可以做事务管理
  • 可以做接口的权限验证
  • 可以做性能监测

4.AOP 实现原理
AOP 保证开发者不修改源代码的前提下,为系统中的业务组件添加某种通用功能。AOP 的本质是由 AOP 框架修改业务组件的源代码,达到增强功能的目的。AOP可以将其分为两类:

  • 静态 AOP 实现:AOP 框架在编译阶段对程序源代码进行修改,生成了静态的 AOP 代理类(生成的 *.class 文件已经被改掉了,需要使用特定的编译器),比如 AspectJ。
  • 动态 AOP 实现:AOP 框架在运行阶段动态生成代理对象(在内存中以 JDK 动态代理,或 CGlib 动态地生成 AOP 代理类),如 SpringAOP。

4.AOP核心概念
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用术语如下:

  • Target(目标对象):代理的目标对象
  • Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
  • Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
  • Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
  • Aspect(切面):是切入点和通知(引介)的结合
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

Spring AOP 通知分类:

  • before(前置通知) :通知方法在目标方法调用之前执行。
  • after(后置通知):通知方法在目标方法返回或异常后调用。
  • after-returning(返回后通知):通知方法会在目标方法返回后调用。
  • after-throwing(抛出异常后通知):通知方法会在抛出异常后调用。
  • around(环绕通知):通知方法会将目标方法封装起来。

5.Spring AOP的实现原理
Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。

JDK动态代理
Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。
JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口,另一个是Proxy类。

我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。代码如下所示:

/**
 * 动态代理
 */
public class JdkProxyDemo implements InvocationHandler {
 
    private Log log;
 
    public JdkProxyDemo(Log log) {
        this.log= log;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before (前置通知)");
        Object result = null;
 
        try {
            result = method.invoke(log, args);
        }catch (Exception e) {
            System.out.println("e: " + e.getMessage());
            throw e;
        }finally {
            System.out.println("after (后置通知)");
        }
        return result;
    }
}

第二个Proxy类
我们可以通过这个类的newProxyInstance方法,返回一个代理对象。
生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。

public class DemoMain { public static void main(String[] args) { 
   //获取InvocationHandler对象 在构造方法中注入目标对象 
   InvocationHandler handler = new JdkProxyDemo(new Log()); 
   //获取代理类对象 
  Log proxyLog = (Log)Proxy.newProxyInstance(DemoMain.class.getClassLoader(), new Class[]{Log.class}, handler); 
 
   //调用目标方法
   proxyLog.request(); 
   proxyLog.response();
}

JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
但是如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLib 动态代理
Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截。

cglib 针对类进行代理,我们先创建一个类实现 MethodInterceptor接口

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LogMethodInterceptor implements MethodInterceptor {

     /**
                 *
                 * @param obj 表示增强的对象,即实现这个接口类的一个对象
                 * @param method 表示要被拦截的方法
                 * @param args 表示要被拦截方法的参数
                 * @param proxy 表示要触发父类的方法对象
                 * @return 
                 * @throws Throwable
                 */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        
           System.out.println("代理开始...");
           return methodProxy.invokeSuper(o, objects);
    }
}

测试代码:

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

import java.lang.reflect.Proxy;

public class TestApp {
    public static void main(String[] args) {
       Enhancer enhancer = new Enhancer(); // 通过CGLIB动态代理获取代理对象的过程
        enhancer.setSuperclass(Log.class); // 设置enhancer对象的父类
        enhancer.setCallback(new LogMethodInterceptor()); // 设置enhancer的回调对象
        Log proxy= (Log)enhancer.create(); // 创建代理对象
        // 通过代理对象调用目标方法
        proxy.logDeal();
    }
}

统一的日志切面代码如下:

import com.test.dao.DaoSupport;
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;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.awt.geom.Area;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * 日志切面实现
 */

@Component
@Aspect
public class LogService {

    @Resource(name = "daoSupport")
    private DaoSupport dao;

    public LogService() {
        System.out.println("Aop");
    }

    /**
     * 切点
     */
    @Pointcut("@annotation(com.test.service.logAop.MethodLog)")
    public void methodCachePointcut() { }


    /**
     * 切面
     *
     * @param point
     * @throws Throwable
     */
    @Around("methodCachePointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
        Calendar ca = Calendar.getInstance();
        String operDate = df.format(ca.getTime());
        String loginName;
        String name;
        String methodRemark = getMthodRemark(point);
        String methodName = point.getSignature().getName();
        String packages = point.getThis().getClass().getName();
        if (packages.indexOf("$$EnhancerByCGLIB$$") > -1) { // 如果是CGLIB动态生成的类
            try {
                packages = packages.substring(0, packages.indexOf("$$"));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        String operatingcontent = "";
        Object[] method_param = null;

        Object object;
        try {
            method_param = point.getArgs(); //获取方法参数
            object = point.proceed();
        } catch (Exception e) {
            throw e;
        }
        Area area = (Area) method_param[0];
        return object;
    }
    /**
     * 方法异常时调用
     * @param ex
     */
    public void afterThrowing(Exception ex) {
        System.out.println("afterThrowing");
        System.out.println(ex);
    }
    /**
     * 获取方法中的中文备注
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    public static String getMthodRemark(ProceedingJoinPoint joinPoint) throws Exception {

        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();

        Class targetClass = Class.forName(targetName);
        Method[] method = targetClass.getMethods();
        String methode = "";
        for (Method m : method) {
            if (m.getName().equals(methodName)) {
                Class[] tmpCs = m.getParameterTypes();
                if (tmpCs.length == arguments.length) {
                    MethodLog methodCache = m.getAnnotation(MethodLog.class);
                    if (methodCache != null) {
                        methode = methodCache.remark();
                    }
                    break;
                }
            }
        }
        return methode;
    }
}

了解完后,想必大家也会对Spring AOP有一个更深的理解。

你可能感兴趣的:(spring,java,jvm)