Spring5学习笔记(三、AOP开发)

文章目录

  • 静态代理设计模式
    • 为什么需要代理设计模式?
      • 问题
    • 代理设计模式
      • 概念
      • 名词解释
      • 代理开发的核心要素
      • 编码
      • 静态代理存在的问题
  • Sring的动态代理开发
    • Spring动态代理的概念
    • 搭建开发环境
    • Spring动态代理的开发步骤
    • 动态代理细节分析
  • Spring动态代理详解
    • 额外功能的详解
      • MethodBeforeAdvice分析
      • MethodInterceptor(方法拦截器)
    • 切入点详解
      • 切入点表达式
        • 1. 方法切入点表达式
        • 2. 类切入点表达式
        • 3. 包切入点表达式
      • 切入点函数
        • 切入点函数的逻辑运算
  • AOP开发
    • AOP的概念
    • AOP的开发步骤
    • 切面的名词解释
    • AOP的底层实现原理
      • 核心问题
      • 动态代理类的创建
        • 1. JDK的动态代理
        • 2. CGlib的动态代理
        • 总结
      • Spring工厂如何加工原始对象
  • 基于注解的AOP开发
    • 基于注解的AOP开发步骤
    • 细节
      • 1. 切入点复用
      • 2. 动态代理的创建方式
  • AOP开发中的一个坑
  • AOP阶段知识总结

静态代理设计模式

为什么需要代理设计模式?

问题

  • 在JavaEE分层开发中,哪个层次对于我们来讲最重要?
    DAO --> Service --> Controller
    JavaEE分层开发中,最为重要的是Service层。

  • Service层中包含了哪些代码?
    Service层 = 核心功能(几十上百行代码) + 额外功能(附加功能)
    核心功能:业务运算、DAO调用
    额外功能:不属于业务、可有可无、代码量很小(如事务、日志、性能、…)
    Spring5学习笔记(三、AOP开发)_第1张图片

  • 额外功能书写在Service层中好不好?
    Service层的调用者的角度(Controller):需要在Service层书写额外功能(事务)。
    软件设计者的角度:Service层不需要额外功能,因为不好维护。

  • 现实中的问题和解决办法:
    租房场景
    Spring5学习笔记(三、AOP开发)_第2张图片
    租房场景改造
    Spring5学习笔记(三、AOP开发)_第3张图片

代理设计模式

概念

通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。

名词解释

  1. 目标类(原始类):指的是业务类(核心功能 --> 业务运算、DAO调用)
  2. 目标方法(原始方法):目标类(原始类)中的方法就是目标方法(原始方法)。
  3. 额外功能(附加功能):日志、事务、性能、…

代理开发的核心要素

代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口(为了与目标类(原始类)的方法保持一致)

编码

Spring5学习笔记(三、AOP开发)_第4张图片
静态代理:为每一个原始类,手动创建一个代理类(.java .class)

//代理类实现与原始类相同的接口,保证方法一致
public class UserServiceProxy implements UserService {
    //原始类
    private UserServiceImpl userService = new UserServiceImpl();

    @Override
    public void register(User user) {
        //额外功能
        System.out.println("-----log-----");
        //调用原始类中的方法
        userService.register(user);
    }

    @Override
    public boolean login(String name, String password) {
        //额外功能
        System.out.println("-----log-----");
        //调用原始类中的方法
        return userService.login(name, password);
    }
}

对于每个Service原始类都需要一个对应的代理类,代码冗余。

静态代理存在的问题

  1. 代理类文件数量过多,不利于项目的管理。
    UserServiceImpl --> UserServiceProxy
    OrderServiceImpl --> OrderServiceProxy
  2. 额外功能维护性差
    代理类中,额外功能修改复杂(麻烦)。一百个就需要修改一百次。

Sring的动态代理开发

Spring动态代理的概念

概念:通过代理类,为原始类(目标)增加额外的功能。
好处:利于原始类(目标)的维护。

搭建开发环境

引入jar包

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-aopartifactId>
    <version>5.1.14.RELEASEversion>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjrtartifactId>
    <version>1.8.9version>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.8.9version>
    <scope>runtimescope>
dependency>

Spring动态代理的开发步骤

  1. 创建原始对象(目标对象)

    public class UserServiceImpl implements UserService {
        @Override
        public void register(User user) {
            //核心功能
            System.out.println("UserServiceImpl.register (业务运算+DAO调用)");
        }
    
        @Override
        public boolean login(String name, String password) {
            //核心功能
            System.out.println("UserServiceImpl.login");
            return true;
        }
    }
    
    <bean id="userService" class="com.angenin.proxy.UserServiceImpl"/>
    
  2. 额外功能
    Spring提供了MethodBeforeAdvice接口,我们需要把额外功能书写在此接口的实现类的before方法中,这个实现类的before方法会在原始方法执行前先执行。

    public class Before implements MethodBeforeAdvice {
        //书写运行在原始方法之前的额外功能
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            //额外功能
            System.out.println("Before...before...");
        }
    }
    
    <bean id="before" class="com.angenin.dynamic.Before"/>
    
  3. 定义切入点
    切入点:额外功能加入的位置。
    目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法。
    简单的测试:所有方法都作为切入点,都加入额外功能。

    
    <aop:config>
        
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
    aop:config>
    
  4. 组装(2和3的整合)

    
    <aop:config>
        
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
        
        
        <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    aop:config>
    
  5. 调用
    获得Spring工厂创建的动态代理对象,并进行调用。此时,通过原始对象的id值获得的就是代理对象,生成的对象用原始类的接口类型进行接收。
    Spring5学习笔记(三、AOP开发)_第5张图片
    Spring5学习笔记(三、AOP开发)_第6张图片
    打断点debug后可以看到获得的userService对象为代理对象。

动态代理细节分析

  1. Spring创建的动态代理类在哪里?
    Spring框架在运行时,通过动态字节码技术,在JVM创建的,运行在JVM内部,等程序结束后,会和JVM一起消失。

    什么叫动态字节码技术?
    通过第三方动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虛拟机结束,动态字节码跟着消失。
    Spring5学习笔记(三、AOP开发)_第7张图片
    结论:动态代理不需要定义类文件,都是JⅥM运行过程中动态创建的,所以不会造成静态代理,类文件数量过多,影晌项目管理的问题。

  2. 动态代理编程简化代理的开发。
    在额外功能不改变的前提下,创建其他目标类(原始类)的代理对象时,只需要指定原始(目标)对象即可。

  3. 动态代理额外功能的维护性大大增强。

Spring动态代理详解

额外功能的详解

MethodBeforeAdvice分析

  1. MethodBeforeAdvice接口的作用:额外功能运行在原始方法执行之前,进行额外功能操作。
    public class Before implements MethodBeforeAdvice {
        /*
            作用:书写运行在原始方法之前的额外功能
    
            参数:
                Method:额外功能所增加给的那个原始方法(如:login方法、register方法)
                Object[]:额外功能所增加给的那个原始方法的参数(如login方法的String name, String password)
                Object:额外功能所增加给的那个原始对象(如UserServiceImpl)
         */
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            //额外功能
            System.out.println("Before...before...");
        }
    }
    
  2. before方法的3个参数在实战中,该如何使用?
    before方法的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用,实战开发中before的三个参数用得比较少。

MethodInterceptor(方法拦截器)

MethodBeforeAdvice --> 运行在原始方法之前
MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行前、后、前后以及原始方法执行出现异常时执行,MethodInterceptor接口功能比MethodBeforeAdvice接口更加强大,所以更推荐使用MethodInterceptor接口

//这里的MethodInterceptor是aop包下的那个
public class Around implements MethodInterceptor {
    /*
        invoke方法的作用:额外功能书写在invoke
              额外功能执行的时机   1. 原始方法之前
                                2. 原始方法之后
                                3. 原始方法之前 和 之后
                                4. 原始方法执行异常时
       调用参数methodInvocation的proceed()方法,可以让原始方法执行,所以我们可以在执行前写额外功能,也可以在之后写,或者都写
        参数:
            methodInvocation:额外功能所增加给的那个原始方法
        返回值:
            Object:原始方法执行后的返回值
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //额外功能1
        System.out.println("额外功能在原始方法执行前执行-------");
        //原始方法
        Object ret = methodInvocation.proceed();
        //额外功能2
        //System.out.println("额外功能在原始方法执行后执行-------");
		//返回的是原始方法执行后的返回值
        return ret;
    }
}
   <bean id="around" class="com.angenin.dynamic.Around"/>

   
   <aop:config>
       
       <aop:pointcut id="pc" expression="execution(* *(..))"/>
       
       <aop:advisor advice-ref="around" pointcut-ref="pc"/>
   aop:config>

什么的额外功能需要在原始方法运行的前后都运行呢?事务等。

  • 额外功能运行在原始方法抛出异常时

    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            //额外功能1
            //System.out.println("额外功能在原始方法执行前执行-------");
            Object ret = null;
            try {
                //原始方法
                ret = methodInvocation.proceed();
            } catch (Throwable throwable) {
                System.out.println("额外功能在原始方法抛出异常时执行-------");
                throwable.printStackTrace();
            }
            //额外功能2
            //System.out.println("额外功能在原始方法执行后执行-------");
            return ret;
        }
    

    具体额外功能执行的时机,需要按照需求进行合理的安排。

  • MethodInterceptor影响原始方法的返回值

    原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值。
    不返回原始方法的返回值,就会影响到结果。

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = methodInvocation.proceed();
    	//return 1;
        return false;
    }
    

切入点详解

切入点决定额外功能加入的位置(这里的位置指的是方法,切入哪些方法)。


execution(* *(..)):匹配了所有方法
execution():切入点函数
* *(..)

切入点表达式

1. 方法切入点表达式

Spring5学习笔记(三、AOP开发)_第8张图片

* *(..) --> 所有方法

* --> 修饰符 返回值
* --> 方法名
() --> 参数表
.. --> 对参数没有要求(参数有没有、有几个、什么类型都行)
  • 定义名为login的方法作为切入点:
    * login(..)
    
  • 定义名为login并且有两个字符串类型参数的方法作为切入点:
    * login(String,String)
    注意:
    	非java.lang包中的类型,必须写全限定类名
    	如:* register(com.angenin.proxy.User)
    	..可以和具体的参数类型连用
    	如:* login(String,..)
    	第一个参数为String类型的login方法
    
  • 精准方法切入点限定
    上面所讲解的方法切入点表达式不精准。
    如下图,不能精准到a包下的UserServiceImpl的第一个login方法。
    Spring5学习笔记(三、AOP开发)_第9张图片
    精准切入
    Spring5学习笔记(三、AOP开发)_第10张图片
    修饰符/返回值		包.类.方法(参数)
    	*			com.baizhiedu.a.UserServiceImpl.login(String,String)
    精准到a包下的UserServiceImpl的第一个login方法
    
2. 类切入点表达式

指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。
Spring5学习笔记(三、AOP开发)_第11张图片

  • 语法一
    a包下UserServiceImpl类中的所有方法都加入额外功能
    * com.baizhiedu.a.UserServiceImpl.*(..)
    
  • 语法二
    忽略包
    1. 类只存在一级包 com.UserServiceImpl
    * *.UserServiceImpl.*(..)
    
    2. 类存在多级包 com.baizhiedu.a.UserServiceImpl
    * *..UserServiceImpl.*(..)
    
3. 包切入点表达式

指定包作为额外功能加入的位置,自然包中的所有类及其方法都会加入额外的功能。

  • 语法一
    切入点包中的所有类,下面这条表达式包括的类必须在proxy包中,即使在proxy的子包中也无法生效。
    * com.baizhiedu.proxy.*.*(..)
    
  • 语法二
    切入点当前包及其子包都生效(多加一个点)
    * com.baizhiedu.proxy..*.*(..)
    
    包切入点在实战中使用得更多,把相加额外功能的类放到同个包下即可。

切入点函数

切入点函数:用于执行切入点表达式。

  1. execution
    最为重要的切入点函数,功能最全。
    可以执行方法切入点表达式、类切入点表达式、包切入点表达式。

    弊端:execution执行切入点表达式,书写麻烦。
    注意:其他的切入点函数只是简化了execution书写的复杂度,在功能上完全一致。

  2. args
    作用:主要用于函数(方法)参数的匹配,关注点在于参数,忽略包、类和方法名。

    如:切入点为方法参数必须得是2个字符串类型的参数。
    
    execution(* *(String,String))
    替换后
    args(String,String)
    
  3. within
    作用:主要用于进行类、包切入点表达式的匹配,关注点在于包名、类名,忽略方法名和参数。

    如:切入点为UserServiceImpl类。
    
    execution(* *..UserServiceImpl.*(..))
    替换后
    within(*..UserServiceImpl)
    
    如:切入点为proxy包下及其子包的所有类
    
    execution(* com.baizhiedu.proxy..*.*(..))
    替换后
    within(com.baizhiedu.proxy..*)
    
  4. @annotation
    作用:为具有特殊注解的方法加入额外功能。

    自定义一个Log注解。

    @Target(ElementType.METHOD) //决定在什么位置起作用(这里是在方法上起作用)
    @Retention(RetentionPolicy.RUNTIME) //决定在什么时候起作用(这里在运行时起作用)
    public @interface Log {}
    

    为UserServiceImpl类的register方法加上@Log注解。

    <aop:config>
    	
        <aop:pointcut id="pc" expression="@annotation(com.angenin.proxy.Log)"/>
        <aop:advisor advice-ref="around" pointcut-ref="pc"/>
    aop:config>
    
切入点函数的逻辑运算

指的是 整合多个切入点函数一起配合工作,进而完成更为复杂的需求。

  • and 与操作

    案例:方法名为login,并且参数为2个字符串。
    
    execution(* login(String,String))
    替换后
    execution(* login(..)) and args(String,String)
    
    注意:与操作不能用于同种类型的切入点函数,因为and需要同时满足。
    如:login方法和register方法都作为切入点(这样写是错误的,可以换成or)
    execution(* login(..)) and execution(* register(..))
    
  • or 或操作

    案例:login方法和register方法都作为切入点
    
    execution(* login(..)) or execution(* register(..))
    

AOP开发

AOP的概念

  • AOP(Aspect Oriented Programing)面向切面编程 = Spring动态代理开发
    以切面为基本单位的程序开发,通过切面间的彼此协同,相互调用,完成程序的构建。
    切面 = 切入点 + 额外功能

  • OOP(Object Oritened Programing)面向对象编程 Java
    以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用,完成程序的构建。

  • POP(Procedure Oriented Programing)面向过程(方法、函数)编程 C语言
    以过程为基本单位的程序开发,通过过程间的彼此协同,相互调用,完成程序的构建。

AOP的概念:

  • 本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。
  • 好处:利于原始类的维护。
  • 注意:AOP是基于OOP的,所以AOP不可能取代OOP,AOP是OOP为了解耦而做的补充。

AOP的开发步骤

  1. 原始对象
  2. 额外功能(MethodInterceptor)
  3. 切入点
  4. 组装切面(额外功能 + 切入点)

切面的名词解释

切面 = 切入点 + 额外功能

几何学
	面 = 点 + 形同的性质

Spring5学习笔记(三、AOP开发)_第12张图片

AOP的底层实现原理

核心问题

  1. AOP如何创建动态代理类?(动态字节码技术)
  2. Spring工厂如何加工创建代理对象?为什么通过原始对象的id值,获得的是代理对象?

动态代理类的创建

1. JDK的动态代理

Proxy.newProxyInstance方法参数详解

关于类加载器:
首先得了解类加载器的作用,及其对象生成的过程。
Spring5学习笔记(三、AOP开发)_第13张图片
根据老师讲的内容画的图,如果画得不对不好,麻烦各位请指出来。
Spring5学习笔记(三、AOP开发)_第14张图片>Spring5学习笔记(三、AOP开发)_第15张图片
因为代理类没有字节码文件,所以JVM没有给它生成它的类加载器,需要借用其他类的类加载器,所以Proxy.newProxyInstance的第一个参数为其他类的类加载器(原始类或者其他类都可以)。

Spring5学习笔记(三、AOP开发)_第16张图片

  1. 原始对象直接创建。
  2. 额外功能由InvocationHandler接口的invoke方法解决。
  3. 相同接口由原始对象获取其Class,再获取其实现的接口。

InvocationHandler的invoke与MethodInterceptor的invoke对比:
Spring5学习笔记(三、AOP开发)_第17张图片
代码实现

public class TestJdkProxy {
    public static void main(String[] args) {
        //代理创建3要素:1.原始对象;2.额外功能;3.代理对象和原始对象实现相同的接口。

        // 1.创建原始对象
        UserServiceImpl userService = new UserServiceImpl();

        // 2.JDK创建动态代理

        //这里为了下面来起来整洁一点,单独拿开,平时用匿名内部类即可
        InvocationHandler handler = new InvocationHandler() {
            /*
            InvocationHandler的invoke方法:
                作用:用于书写额外功能,额外功能运行在原始方法执行前、后、前后、抛出异常
                参数:
                    proxy:忽略即可,代表的是代理对象
                    method:原始方法(增加额外功能的那个原始方法)
                    args:原始方法的参数

                method.invoke方法:执行原始方法,返回原始方法执行后的结果,
                    第一个参数为原始对象
                    第二个参数为原始方法的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("----proxy  log-----");
                //传入原始对象和原始方法的参数
                //这里需要注意,内部类使用外部类的变量时,外部类的变量需要用final来修饰
                Object ret = method.invoke(userService, args);
                //返回原始方法执行后的结果
                return ret;
            }
        };
        /*
            第一个参数为借用的类加载器(任意类都可以)
            第二个参数为原始类实现的接口,通过获取原始对象的class再获取接口(userService.getClass().getInterfaces())
            第三个参数为InvocationHandler接口,在此接口的invoke方法中书写额外功能
         */
        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
                                userService.getClass().getInterfaces(), handler);

        //因为InvocationHandler接口是一个函数式接口,所以可以使用Lambda表达式,代码变得更加简洁(因为main方法的参数同样为args,所以需要改一下)
//        UserService userServiceProxy = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(),
//                userService.getClass().getInterfaces(), (proxy, method, argss) -> {
//                    System.out.println("----log-----");
//                    return method.invoke(userService, argss);
//                });
        
        userServiceProxy.login("angenin", "123456");
        userServiceProxy.register(new User());
    }
}
2. CGlib的动态代理

CGlib创建动态代理的原理:父子继承创建代理对象,原始类作为父类,代理类作为子类,这样既可以保证二者方法一致,同时在代理类中提供新的实现(额外功能 + 原始方法)。

Spring5学习笔记(三、AOP开发)_第18张图片
Spring5学习笔记(三、AOP开发)_第19张图片
CGlib编码实现

public class TestCglib {
    public static void main(String[] args) {
        // 1.创建原始对象
        UserService userService = new UserService();

        /*
           2. 通过Cglib方式创建动态代理对象

            JDK动态代理:Proxy.newProxyInstance(classloader,interface,invocationhandler)
            Cglib动态代理:
                设置类加载器: Enhancer.setClassloader()   --> 同样,借用任意一个类的类加载器
                设置父类:    Enhancer.setSuperClass()
                设置额外功能: Enhancer.setCallback()  --> 实现接口MethodInterceptor
                Enhancer.create() ---> 创建代理
         */
        Enhancer enhancer = new Enhancer();
        //设置类加载器
        enhancer.setClassLoader(userService.getClass().getClassLoader());
        //设置父类
        enhancer.setSuperclass(userService.getClass());
        //这里的MethodInterceptor接口是spring的cglib包下的那个
        //可以用匿名内部类,这里只是为了看起来简洁
        MethodInterceptor interceptor = new MethodInterceptor() {
            //这里的intercept方法 等同于 InvocationHandler的invoke方法
            @Override
            public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("------cglib  log------");
                //这里调用method的invoke和前面的JDK动态代理一样
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        //设置额外功能
        enhancer.setCallback(interceptor);

        //创建代理
        UserService userServiceProxy  = (UserService) enhancer.create();
        userServiceProxy.login("angenin", "123456");
        userServiceProxy.register(new User());
    }
}
总结
1. JDK动态代理	Proxy.newProxyInstance()	通过接口创建代理的实现类
2. Cglib动态代理	Enhancer					通过继承父类创建的代理类

代理都是为了面向切面服务的。

Spring工厂如何加工原始对象

思路分析
Spring5学习笔记(三、AOP开发)_第20张图片
BeanPostProcessor在上一篇文章里讲到。

编码实现:

  1. 实现 BeanPostProcessor 进行加工
    public class ProxyBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            //返回生成的理对象
            return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), (proxy, method, args) -> {
                System.out.println("----new log-----");
                //返回原始类的执行结果
                return method.invoke(bean, args);
            });
        }
    }
    
  2. 配置文件中对 BeanPostProcessor 进行配置
    <bean id="userService" class="com.angenin.factory.UserServiceImpl"/>
    
    <bean id="proxyBeanPostProcessor" class="com.angenin.factory.ProxyBeanPostProcessor"/>
    
  3. 测试
    public class Test {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login("angenin", "123456");
            userService.register(new User());
        }
    }
    

基于注解的AOP开发

基于注解的AOP开发步骤

  1. 原始对象
    public class UserServiceImpl implements UserService {
    
        @Override
        public void register(User user) {
            System.out.println("UserServiceImpl.register (业务运算+DAO调用)");
        }
    
        @Override
        public boolean login(String name, String password) {
            System.out.println("UserServiceImpl.login");
            return true;
        }
    }
    
  2. 切面类(额外功能 + 切入点 + 组装)
    /*
        1. 原版的额外功能
            public class MyAround implements MethodInterceptor {
                public Object invoke(MethodInvocation invocation) {
                    return invocation.proceed();
                }
            }
    
        2. 切入点
            
     */
    
    @Aspect //加上@Aspect注解代表这个类是切面类
    public class MyAspect {
        //加上@Around注解后,此时的 around方法 相当于 MethodInterceptor接口的invoke方法
        //方法名和任意起,这里的参数 ProceedingJoinPoint 对应 原先的参数 MethodInvocation
        @Around("execution(* login(..))")   //对应aop:pointcut标签
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("----log-----");
            Object ret = joinPoint.proceed();
            return ret;
        }
    }
    
    <bean id="userService" class="com.angenin.aspect.UserServiceImpl"/>
    
    <bean id="around" class="com.angenin.aspect.MyAspect"/>
    
    <aop:aspectj-autoproxy/>
    
  3. 测试
    public class TestAspectProxy {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext4.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login("angenin", "123456");
            userService.register(new User());
        }
    }
    

细节

1. 切入点复用

切入点复用:在切面类中定义一个空方法,在其上面加上@Pointcut注解,通过这种方式定义切入点表达式,后续更加有利于切入点的复用。

@Aspect
public class MyAspect {
    //创建空方法,提取出切入点表达式
    @Pointcut("execution(* login(..))")
    public void myPointcut(){}

    @Around(value = "myPointcut()")   //引用空方法的切入点函数
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----log-----");
        Object ret = joinPoint.proceed();
        return ret;
    }

    @Around("myPointcut()")   //引用空方法的切入点表达式
    public Object around2(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("----tx-----");
        Object ret = joinPoint.proceed();
        return ret;
    }
}

2. 动态代理的创建方式

AOP底层实现的2种代理创建方式:

  1. JDK:通过实现接口,做新的实现类方式,创建代理对象。
  2. Cglib:通过继承父类,做新的子类,创建代理对象。

Spring5学习笔记(三、AOP开发)_第21张图片
默认情况下,AOP底层应用JDK动态代理创建的方式。

切换为Cglib:

  1. 基于注解AOP开发
    只需要在aop:aspectj-autoproxy标签中加入proxy-target-class="true"即可。
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    
    Spring5学习笔记(三、AOP开发)_第22张图片
  2. 传统的AOP开发
    aop:config标签上加上proxy-target-class="true"即可,只是加的位置不同而已,属性都是同一个。
    <aop:config proxy-target-class="true">
    ...
    aop:config>
    

AOP开发中的一个坑

坑:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才是加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)。如果想让内层的方法也调用代理对象的方法,需要实现AppicationContextAware接口,通过其setApplicationContext方法获得工厂对象,进而获得代理对象,调用方法,加入额外功能。

原始类:

public class UserServiceImpl implements UserService, ApplicationContextAware {
    private ApplicationContext ctx;
    //通过实现 ApplicationContextAware 接口,通过 setApplicationContext方法 获取项目中的IOC工厂对象
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register (业务运算+DAO调用)");

        // 模拟同一个业务类不同的业务方法间相互调用的情况
        // 此时调用的是原始对象的login,不是代理对象,所以只有核心功能,没有额外功能
        // 但是我们在这里调用的目的是要有额外功能+核心功能的
        // 由于IOC工厂是重量级资源,一个应用最好只创建一个,所以不能在这里再创建一个工厂
        // 实现 ApplicationContextAware 接口,可以获取到项目中的IOC工厂对象
        //this.login("angenin", "11");
        
        //通过工厂对象创建代理对象,再调用login方法,实现额外功能+核心功能
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("angenin", "11");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}

AOP阶段知识总结

Spring5学习笔记(三、AOP开发)_第23张图片

下一篇:Spring5学习笔记(四、持久层整合与事务处理)

学习视频(p63-p107):https://www.bilibili.com/video/BV185411477k?p=63

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