Spring AOP详解

这篇博客详细介绍了Spring AOP的概念、实现原理及手动和自动方式的使用,包括JDK动态代理和CGLIB字节码增强。此外,还深入探讨了AspectJ的切入点表达式和通知类型,以及在Spring中如何配置和使用。最后,简要提及了JdbcTemplate在数据操作中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、AOP

1.1、AOP介绍

1.1.1、什么是AOP?
  • 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  • AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码。如下图所示:
  • 经典应用:事务管理、性能监视、安全检查、缓存 、日志等。
  • Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。
  • AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入对Aspect的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。
1.1.2、AOP实现原理
  • aop底层将采用代理机制进行实现。
  • 接口 + 实现类时 :spring采用 jdk 的 动态代理 Proxy。
  • 只有实现类时:spring 采用 cglib 字节码增强
1.1.3、AOP术语【掌握】
  1. Target :目标类,需要被代理的类。本例中如:UserService
  2. Joinpoint(连接点) :所谓连接点是指那些可能被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。本例中如:UserService的所有的方法
  3. PointCut 切入点 :所谓切入点是指我们要对哪些Joinpoint进行拦截,即已经被增强的连接点。例如:addUser()
  4. Advice :通知/增强,增强的代码。例如:after()、before()
    所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(即切面要完成的功能)。
  5. Weaving(织入) :是指把通知/增强advice应用到目标对象target来创建新的代理对象proxy的过程。
    spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。
  6. Proxy :代理类,一个类被AOP织入增强后,就产生一个结果代理类。
  7. Aspect(切面) : 是切入点Pointcut和通知Advice(引介)的结合。
  8. Introduction(引介) :引介是一种特殊的通知,在不修改类代码的前提下,Introduction 可以在运行期为类动态地添加一些方法或Field。

  • 小结:
    一个线是一个特殊的面。
    一个切入点和一个通知,组成成一个特殊的面。
    详解如图01:

    详解如图02:

1.2、手动方式

1.2.1、JDK动态代理
  • JDK动态代理:是对“装饰者”设计模式的简化。JDK动态代理使用前提:必须有接口。
    1. 目标类:接口 + 实现类
    2. 切面类:用于存放通知,名称叫:MyAspect.java
    3. 工厂类:编写工厂生成代理
    4. 测试类

1.2.1.1、目标类
UserService.java

// 目标接口
public interface UserService {
   
    public void addUser();
    public void updateUser();
    public void deleteUser();
}

UserServiceImpl.java

// 目标实现类,有接口
public class UserServiceImpl implements UserService {
   
    @Override
    public void addUser() {
   
        System.out.println("a_proxy.a_jdk addUser");
    }
    @Override
    public void updateUser() {
   
        System.out.println("a_proxy.a_jdk updateUser");
    }
    @Override
    public void deleteUser() {
   
        System.out.println("a_proxy.a_jdk deleteUser");
    }
}

1.2.1.2、切面类
MyAspect.java

// 切面类
public class MyAspect {
   
    public void before() {
   
        System.out.println("前方法");
    }
    public void after() {
   
        System.out.println("后方法");
    }
}

1.2.1.3、工厂类(自定义的)
MyBeanFactory.java

// 工厂类
public class MyBeanFactory {
   
    public static UserService createService() {
   
        // 1、先有目标类对象
        final UserService userService = new UserServiceImpl();
        // 2、再有切面类
        final MyAspect myAspect = new MyAspect();
        /* 3、最后有代理类,将目标类(切入点)和切面类(通知)进行结合  =》  切面
         *  Proxy.newProxyInstance
         *      参数1:ClassLoader loader 类加载器,我们知道,动态代理类在运行时创建的,任何类都需要类加载器将其加载到内存。
         *          类加载器该如何写呢?
         *          答:一般情况下:当前类.class.getClassLoader()
         *              或者 目标类的实例.getClass().getClassLoader()
         * 
         *      参数2:Class[] interfaces 代理类需要实现的所有接口
         *          方式1:目标类的实例.getClass().getInterfaces()  注意:该方式只能获得自己接口,不能获得父元素接口
         *          方式2:new Class[]{UserService.class}   
         *              例如:jdbc 驱动   => DriverManager => 获得接口 Connection
         * 
         *      参数3:InvocationHandler h 处理类,是一个接口,必须进行实现类,一般情况下采用:匿名内部类
         *          该接口提供了一个 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke 方法
         *              参数31:Object proxy       代理对象
         *              参数32:Method method      代理对象当前执行的方法的描述对象(反射)
         *                  执行的方法名:method.getName()
         *                  执行的方法:method.invoke(对象, 实际参数)
         *              参数33:Object[] args      方法的实际参数
         */
        UserService proxyService = (UserService) Proxy.newProxyInstance(
            MyAspect.class.getClassLoader(), 
            userService.getClass().getInterfaces(), 
            new InvocationHandler() {
   
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
                    // 前执行
                    myAspect.before();
                    // 执行目标类的方法
                    Object obj = method.invoke(userService, args);
                    // 后执行
                    myAspect.after();
                    return obj;
                }
            });
        return proxyService;
    }
}

1.2.1.4、测试类
TestJDK.java

// 测试类
public class TestJDK {
   
    @Test
    public void demo01() {
   
        UserService userService = MyBeanFactory.createService();
        userService.addUser();
        userService.updateUser();
        userService.deleteUser();
    }
}

程度运行结果为:

前方法
a_proxy.a_jdk addUser
后方法
前方法
a_proxy.a_jdk updateUser
后方法
前方法
a_proxy.a_jdk deleteUser
后方法
  • debug调试的结果:
  • JDK动态代理返回的是:$Proxy (id=34)
1.2.2、CGLIB字节码增强
  • 没有接口,只有实现类。
  • 采用字节码增强框架 cglib,运行原理:在运行时,创建目标类的子类,从而对目标类进行增强。
  • 导入jar包:
    自己导jar包(了解):
    • 核心包:hibernate-distribution-3.6.10.Final\lib\bytecode\cglib\cglib-2.2.jar
    • 依赖包:struts-2.3.15.3\apps\struts2-blank\WEB-INF\lib\asm-3.3.jar
  • spring-core-3.2.0.RELEASE.jar 已经整合以上两个内容,所以我们只需要导入这个包就可以了,如下图所示:

    项目中的位置:

1.2.2.1、目标类
UserServiceImpl.java

// 目标实现类,没接口
public class UserServiceImpl {
   
    public void addUser() {
   
        System.out.println("a_proxy.b_cglib addUser");
    }
    public void updateUser() {
   
        System.out.println("a_proxy.b_cglib updateUser");
    }
    public void deleteUser() {
   
        System.out.println("a_proxy.b_cglib deleteUser");
    }
}

1.2.2.2、切面类
MyAspect.java的代码同上 1.2.1.2、切面类 代码,这里不再赘述!

1.2.2.3、工厂类(自定义的)
MyBeanFactory.java

// 工厂类
public class MyBeanFactory {
   
    public static UserServiceImpl createService() {
   
        // 1、先有目标类对象
        final UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2、再有切面类对象
        final MyAspect myAspect = new MyAspect();
        // 3、最后有代理类,采用cglib,底层创建目标类的子类
        // 3.1、核心类
        Enhancer enhancer = new Enhancer();
        // 3.2 、先确定父类
        enhancer.setSuperclass(userServiceImpl.getClass());
        /* 3.3、 设置回调函数 ,MethodInterceptor接口  等效  jdk中的 InvocationHandler接口 
         *      intercept() 等效 jdk中的  invoke()
         *          参数1、参数2、参数3:和以invoke()方法的参数一样
         *          参数4:methodProxy 方法的代理
         */
        enhancer.setCallback(new MethodInterceptor() {
   
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
   
                // 前执行
                myAspect.before();
                // 执行目标类的方法
                Object obj = method.invoke(userServiceImpl, args);
                // 执行代理类的父类,就是执行目标类(目标类和代理类是斧子关系),相当于inwoke调用了2次
                methodProxy.invokeSuper(proxy, args);
                // 后执行
                myAspect.after();
                return obj;
            }});
        // 4、创建代理
        UserServiceImpl proxyService = (UserServiceImpl) enhancer.create();
        return proxyService;
    }
}

1.2.2.4、测试类
TestJDK.java的代码同上 1.2.1.4、切面类 代码,这里不再赘述!
程度运行结果为:

前方法
a_proxy.b_cglib addUser
a_proxy.b_cglib addUser
后方法
前方法
a_proxy.b_cglib updateUser
a_proxy.b_cglib updateUser
后方法
前方法
a_proxy.b_cglib deleteUser
a_proxy.b_cglib deleteUser
后方法
  • debug调试的结果:
  • CGLIB字节码增强返回的是:UserServiceImpl$$EnhancerByCGLIB$$157a2b67 (id=34)
1.2.3、代理知识总结
  • Spring在运行期,生成动态代理对象,不需要特殊的编译器。
  • Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入的。
    1. 若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    2. 若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
  • 程序中应优先对接口创建代理,便于程序解耦维护。
  • 标记为final的方法,不能被代理,因为无法进行覆盖。
    1. JDK动态代理,是针对接口生成子类,接口中的方法不能使用final修饰。
    2. CGLib动态代理,是针对目标类生产子类,因此目标类和目标类的方法是不能使用final修饰。
  • Spring只支持方法连接点,不提供属性连接。

1.3、AOP联盟增强(通知)类型

  • AOP联盟为通知Advice定义了org.aopalliance.aop.Advice
  • Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
    • 1、前置通知:org.springframework.aop.MethodBeforeAdvice
      • 在目标方法执行前实施增强
    • 2、后置通知:org.springframework.aop.AfterReturningAdvice
      • 在目标方法执行后实施增强
    • 3、环绕通知:org.aopalliance.intercept.MethodInterceptor
      • 在目标方法执行前后实施增强
    • 4、异常抛出通知:org.springframework.aop.ThrowsAdvice
      • 在方法抛出异常后实施增强
    • 5、引介通知:org.springframework.aop.IntroductionInterceptor
      • 在目标类中添加一些新的方法和属性

模拟环绕通知:

环绕通知:`必须手动执行目标方法`
    try {
   
       // 前置通知
       // 执行目标方法
       // 后置通知
    } catch() {
   
       // 异常抛出通知
    }

1.4、spring 编写代理:半自动

  • 让spring 给我们创建代理对象,我们从spring容器中手动的获取代理对象。
  • 导入jar包:
    • 核心jar包:4 + 1
    • AOP的jar包:AOP联盟(规范/接口)、spring-aop(实现)
1.4.1、目标类

UserService.java

// 目标接口
public interface UserService {
   
    public void addUser();
    public void updateUser();
    public void deleteUser();
}

UserServiceImpl.java

// 目标实现类,有接口
public class UserServiceImpl implements UserService {
   
    @Override
    public void addUser() {
   
        System.out.println("b_factory_bean addUser");
    }
    @Override
    public void updateUser() {
   
        System.out.println("b_factory_bean updateUser");
    }
    @Override
    public void deleteUser() {
   
        System.out.println("b_factory_bean deleteUser");
    }
}
1.4.2、切面类
// 切面类
/**
 * 切面类中需要删除之前自己写的通知,添加上AOP联盟提供的通知(即要增强的东西),需要实现不同接口,接口就是规范,从而就确定方法名称。
 *      采用“环绕通知” MethodInterceptor
 *
 */
public class MyAspect implements MethodInterceptor {
   
    @Override
    public Object invoke(MethodInvocation mi) throws Throwable {
   
        System.out.println("我们的前代码");
        // 使用AOP联盟的环绕通知:必须手动执行目标方法
        Object obj = mi.proceed();
        System.out.println("我们的后代码");
        return obj;
    }
}
1.4.3、spring配置

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值