当前位置:   article > 正文

Spring AOP编程_2. 为在线问答项 的 service 层增加 aop (1)编写 个logadvice类,对ser

2. 为在线问答项 的 service 层增加 aop (1)编写 个logadvice类,对service

Spring—AOP编程

笔记整理自 b站 孙哥说Spring5

image-20220417193011687

1. 静态代理设计模式

Ⅰ. 为什么需要代理设计模式

问题

  • 在JavaEE分层开发开发中,哪个层次对于我们来讲最重要

    DAO ---> Service --> Controller 
    
    • 1

    JavaEE分层开发中,最为重要的是Service层

  • Service层中包含了哪些代码?

    Service层中 = 核心功能(几十行 上百代码) + 额外功能(附加功能)
    1. 核心功能
       业务运算
       DAO调用
    2. 额外功能 
       1. 不属于业务
       2. 可有可无
       3. 代码量很小 
       
       事务、日志、性能...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    image-20220622231832743

    image-20220622232844763

    image-20220622233505803

  • 额外功能书写在Service层中好不好?

    Service层的调用者的角度(Controller):需要在Service层书写额外功能。
                            软件设计者:Service层不需要额外功能。
    
    • 1
    • 2
  • 现实生活中的解决方式

    租房场景

    image-20220623103950653

    租房场景改造

    既然房东不想做额外功能,但房客还需要这些功能,所以此时需要有一个第三方:中介 负责这些功能,在程序中也就是我们的代理类Proxy

    image-20220623111617477

Ⅱ. 代理设计模式

1️⃣ 概念
  • 通过代理类,为原始类(目标)增加额外的功能。
  • 好处:利于原始类(目标)的维护。
2️⃣ 名词解释
  • 原始(目标)类

    ➢ 指的是 业务类(核心功能 => 业务运算 DAO调用)

  • 原始(目标)方法

    ➢ 原始(目标)类中的方法 就是原始(目标)方法

  • 额外功能(附加功能)

    ➢ 日志、事务、性能

3️⃣ 代理开发的核心要素

代理类 = 目标类(原始类) + 额外功能 + 原始类(目标类)实现相同的接口

要求: 代理类的方法名与目标类的方法名要一一对应(即代理类要和目标类实现同一个接口,这也就是为什么我们的service层一定要写一个接口然后写实现类的原因!!!

房东 => public interface UserService{
           m1
           m2
       }
       UserServiceImpl implements UserService {
           m1 => 业务运算 DAO调用
           m2 
       }
       UserServiceProxy implements UserService {
           m1
           m2
       } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
4️⃣ 编码
  • 静态代理:为每一个原始类,手工编写一个代理类 (.java .class)

image-20220623112920727

5️⃣ 静态代理存在的问题
  • 静态类文件数量过多,不利于项目管理

    UserServiceImpl => UserServiceProxy

    OrderServiceImpl => OrderServiceProxy

  • 额外功能维护性差

    ➢ 代理类中 额外功能修改复杂(麻烦)需要修改所有的代理类

基于上面的两个致命缺点,我们可以使用动态代理,解决了上面的两个缺点。

  • 动态代理没有代理类,使用字节码技术生成代理类
  • 额外功能单独抽取,所有需要此额外功能的目标类都可以使用,所以修改额外功能非常方便

2. Spring的动态代理开发

image-20220623133502867

Ⅰ. Spring动态代理的概念

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

Ⅱ. 搭建开发环境

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.1.14.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.3</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Ⅲ. 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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

配置文件applicationContext.xml

<bean id="userService" class="com.lyc.proxy.UserServiceImpl"/>
  • 1
2️⃣ 额外功能

MethodBeforeAdvice接口

额外的功能书写在接口的实现中,运行在原始方法执行之前运行额外功能。

public class Before implements MethodBeforeAdvice {
    /*
      作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("-----method before advice log------");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

配置文件applicationContext.xml

<bean id="before" class="com.lyc.dynamic.Before"/>
  • 1
3️⃣ 定义切入点
  • 切入点:额外功能加入的位置。

  • 目的:由程序员根据自己的需要,决定额外功能加入给哪个原始方法。

  • 简单的测试:所有方法都做为切入点,都加入额外的功能。

    <aop:config>
        <!--
            aop:pointcut :切入点
            expression :切入点表达式
            execution(* *(..)) :代表所有方法都作为切入点加入额外功能 login、register
        -->
        <aop:pointcut id="pc" expression="execution(* *(..))"/>
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
4️⃣ 组装
  • 2 2 2 3 3 3 步整合,嵌套在<aop:config>标签中,组装成切面

    <!--
        组装:目的把切入点 与 额外功能 进行整合
        表达的含义:所有的方法 都加入 before的额外功能
    -->
    <aop:advisor advice-ref="before" pointcut-ref="pc"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
5️⃣ 调用
  • 目的:获得Spring工厂创建的动态代理对象,并进行调用。

    ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
    /*
    	注意:
       	1. Spring的工厂通过原始对象的id值获得的是代理对象
       	2. 获得代理对象后,可以通过声明接口类型,进行对象的存储
    */
    UserService userService = (UserService) ctx.getBean("userService");
    
    userService.login();
    userService.register();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    达到效果

    image-20220623124506445

    Debug调试一下,通过原始对象id获取的对象的确是代理对象Proxy

    image-20220623124646778

Ⅳ. 动态代理细节分析

  • Spring创建的动态代理类在哪里?

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

    ➢ 什么叫动态字节码技术:通过第三个动态字节码框架,在JVM中创建对应类的字节码,进而创建对象,当虚拟机结束,动态字节码跟着消失。

    ➢ 结论:动态代理不需要定义类文件,都是JVM运行过程中动态创建的,所以不会造成静态代理 类文件数量过多,影响项目管理的问题。

    image-20220623130933082

  • 动态代理编程简化代理的开发

    ➢ 在额外功能不改变的前提下,创建其他原始类(目标类)的代理对象时,只需指定原始(目标)对象即可。

  • 动态代理额外功能的维护性大大增强

3. Spring动态代理详解

Ⅰ. 额外功能的详解

  • MethodBeforeAdvice分析(使用较少)

    方法中的所有参数都是基于原始类动态变化

    // 1.MethodBeforeAdvice接口作用:额外功能运行在原始方法执行之前,进行额外功能操作。
    public class Before implements MethodBeforeAdvice {
        
        /**
         * 作用:需要把运行在原始方法执行之前运行的额外功能,书写在before方法中
         * param1 Method method :额外功能所增加给的原始方法(login、register)
         * param2 Object[] objects :额外功能所增加给的那个原始方法的参数((String name, String password)、(User user))
         * param3 Object o :额外功能所加给的原始对象(UserServiceImpl、OrderServiceImpl)
         * 所有参数都是基于原始类动态变化的
         */
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println("-----method before advice log------");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    // 2.before方法的3个参数在实战中,该如何使用。
    //   before方法的参数,在实战中,会根据需要进行使用,不一定都会用到,也有可能都不用。
    Servlet{
        service(HttpRequest request, HttpResponse response) {
            request.getParameter("name") -->       
            response.getWriter() ---> 
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • MethodInterceptor(方法拦截器)(使用较多,因为功能强大)

    // MethodInterceptor接口:额外功能可以根据需要运行在原始方法执行:前、后、前后。
    public class Arround implements MethodInterceptor {
        /**
         * invoke()方法作用:额外功能书写在invoke方法中
         * 确定:原始方法怎么运行
         * @param methodInvocation :额外功能所增加给的那个原始方法(login、register)
         * methodInvocation.proceed() => (login、register)原始方法运行
         * @return Object ret:原始方法的返回值
         */
        @Override
        public Object invoke(MethodInvocation methodInvocation) throws Throwable {
            // TODO 原始方法执行之前书写额外功能
            Object ret = methodInvocation.proceed();
            // TODO 原始方法执行之后书写额外功能
            return ret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ➢ 额外功能运行在原始方法执行之前

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("=======原始方法执行之前的额外功能=======");
        Object ret = methodInvocation.proceed();
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ➢ 额外功能运行在原始方法执行之前

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = methodInvocation.proceed();
        System.out.println("=======原始方法执行之后的额外功能=======");
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ➢ 额外功能运行在原始方法执行之前、之后

    // 什么样的额外功能 运行在原始方法执行之前,之后都要添加?
    // 事务
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("=======原始方法执行之前的额外功能=======");
        Object ret = methodInvocation.proceed();
        System.out.println("=======原始方法执行之后的额外功能=======");
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    image-20220623152359321

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

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        Object ret = null;
        try {
            ret = methodInvocation.proceed();
        } catch (Throwable throwable) {
            System.out.println("=======原始方法抛出异常 执行的额外功能=======");
            throwable.printStackTrace();
        }
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    // 原始方法的返回值,直接作为invoke方法的返回值返回,MethodInterceptor不会影响原始方法的返回值
    // MethodInterceptor影响原始方法的返回值:Invoke方法的返回值,不要直接返回原始方法的运行结果即可。
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
       System.out.println("-----log-----");
       Object ret = invocation.proceed();
       return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

Ⅱ. 切入点详解

  • 切入点决定额外功能加入位置(方法)

    <aop:pointcut id="pc" expression="execution(* *(..))"/>
    exection(* *(..)) => 匹配了所有方法    a×  b√  c× ?
    
    1. execution()  切入点函数
    2. * *(..)      切入点表达式 
    
    • 1
    • 2
    • 3
    • 4
    • 5
1️⃣ 切入点表达式
  • 方法切入点表达式

    image-20220623154013747

    *  *(..) => 所有方法
    
    *  => 修饰符 返回值
    *  => 方法名
    () => 参数表
    .. => 对于参数没有要求(参数有没有都行,参数有几个都行,参数是什么类型的都行)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ➢ 定义login方法作为切入点

    * login(..)
    
    # 定义register作为切入点
    * register(..)
    
    • 1
    • 2
    • 3
    • 4

    ➢ 定义login方法且login方法有两个字符串类型的参数 作为切入点

    * login(String,String)
    
    # 注意:非java.lang包中的类型,必须要写全限定名
    * register(com.lyc.proxy.User)
    
    # ..可以和具体的参数类型连用
    * login(String,..)  => login(String),login(String,String),login(String,com.lyc.proxy.User)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ➢ 精准方法切入点限定

    image-20220623155831936

    image-20220623161457490

    修饰符 返回值   包.类.方法(参数)
    
        *         com.lyc.proxy.UserServiceImpl.login(..)
        *         com.lyc.proxy.UserServiceImpl.login(String,String)
    
    • 1
    • 2
    • 3
    • 4
  • 切入点表达式

    指定特定类作为切入点(额外功能加入的位置),自然这个类中的所有方法,都会加上对应的额外功能。

    image-20220623162225416

    ➢ 语法1

    # 类中的所有方法加入了额外功能 
    * com.lyc.proxy.UserServiceImpl.*(..)  
    
    • 1
    • 2

    ➢ 语法2

    # 忽略包
    1. 类只存在一级包  com.UserServiceImpl
    * *.UserServiceImpl.*(..)
    
    2. 类存在多级包    com.lyc.proxy.UserServiceImpl
    * *..UserServiceImpl.*(..)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
  • 切入点表达式(实战中用的较多)

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

    image-20220623162958752

    ➢ 语法1

    # 切入点包中的所有类,必须在proxy中,不能在proxy包的子包中
    * com.lyc.proxy.*.*(..)
    
    • 1
    • 2

    ➢ 语法2

    # 切入点当前包及其子包都生效 
    * com.lyc.proxy..*.*(..) 
    
    • 1
    • 2
2️⃣ 切入点函数

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

  • execution

    最为重要的切入点函数,功能最全

    ➢ 执行:方法切入点表达式、类切入点表达式、包切入点表达式。

    ➢ 弊端:execution执行切入点表达式 ,书写麻烦:execution(* com.lyc.proxy..*.*(..))

    ➢ 注意:其他的切入点函数 简化是execution书写复杂度,功能上完全一致。

  • args

    ➢ 作用:主要用于函数(方法)参数的匹配

    ➢ 切入点:方法参数必须得是2个字符串类型的参数

    execution(* *(String,String))

    args(String,String)

  • within

    ➢ 作用:主要用于进行类、包切入点表达式的匹配

    ➢ 切入点:UserServiceImpl这个类、com.lyc.proxy这个包

    execution(* *..UserServiceImpl.*(..))

    within(*..UserServiceImpl)

    execution(* com.lyc.proxy..*.*(..))

    within(com.lyc.proxy..*)

  • @annotation

    ➢ 作用:为具有特殊注解的方法加入额外功能

    <aop:pointcut id="" expression="@annotation(com.lyc.Log)"/>
    
    • 1
  • 切入点函数的逻辑运算

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

    ➢ and与操作

    案例:
    方法名:login
    参数:  2个字符串 
    
    1. execution(* login(String,String))
    
    2. execution(* login(..)) and args(String,String)
    
    注意:与操作不能用于同种类型的切入点函数 
    
    案例:register方法 和 login方法作为切入点 
    
    execution(* login(..)) or execution(* register(..))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    ➢ or或操作

    案例:register方法 和 login方法作为切入点
    
    execution(* login(..)) or  execution(* register(..))
    
    • 1
    • 2
    • 3

4. AOP编程

Ⅰ. AOP概念

AOP(Aspect Oriented Programing) 面向切面编程

切面 = 切入点 + 额外功能

面向切面编程 = Spring动态代理开发

  • AOP的概念

    本质就是Spring的动态代理开发,通过代理类为原始类增加额外功能。

    ➢ 好处:利于原始类的维护

  • 注意:AOP编程不可能取代OOP(面向对象编程),是OOP编程的有意补充。

Ⅱ. AOP编程的开发步骤

本质就是Spring的动态代理开发。

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

Ⅲ. 切面的名词解释

  • 切面 = 切入点 + 额外功能
  • 几何学:面 = 点 + 相同的性质

image-20220623174644836

5. AOP的底层实现原理

Ⅰ. 核心问题

  • AOP如何创建动态代理类

    ➢ 动态字节码技术

  • Spring工厂如何加工创建代理对象

    ➢ 通过原始对象的id值,获得的是代理对象

Ⅱ. 动态代理类的创建

1️⃣ JDK的动态代理
  • Proxy.newProxyInstance()方法参数详解

image-20220623202131388

image-20220623202101570

  • 编码

    public class TestJDKProxy {
        /*
            1. 借用类加载器  TestJDKProxy
               借哪个都可以  UserServiceImpl
            2. JDK8.x前 内部类访问外部类局部变量 要将局部变量声明为final
               final UserService userService = new UserServiceImpl();
         */
        public static void main(String[] args) {
            // 1.创建原始对象
            UserService userService = new UserServiceImpl();
            // 2.JDK创建动态代理
            // 2.1内部类实现InvocationHandler接口 调用invoke方法
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("=====proxy log=====");
                    // 原始方法运行
                    Object ret = method.invoke(userService, args);
                    return ret;
                }
            };
            // 2.2调用Proxy.newProxyInstance()返回代理对象
            UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
                TestJDKProxy.class.getClassLoader(), userService.getClass().getInterfaces(), handler);
            // 3.执行原始方法
            userServiceProxy.login("lyc", "123");
            userServiceProxy.register(new User());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    image-20220623213453249

2️⃣ Cglib的动态代理

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

image-20220623205536601

  • Cglib编码

    package com.lyc.cjlib;
    
    import com.lyc.proxy.User;
    import org.springframework.cglib.proxy.Enhancer;
    // 此时用到的MethodInterceptor是cglib包下的 之前用的在aop包下
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class TestCglib {
        public static void main(String[] args) {
            // 1.创建原始对象
            UserService userService = new UserService();
            /*
               2.通过Cglib方式创建动态代理对象
               Proxy.newProxyInstance(classloader,interface,invocationhandler)
               Enhancer.setClassLoader()  →↑
               Enhancer.setSuperClass()              →↑
               Enhancer.setCallback() => MethodInterceptor(cglib) →↑
               Enhancer.create() => 代理
             */
            Enhancer enhancer = new Enhancer();
            // 2.1借用类加载器
            enhancer.setClassLoader(TestCglib.class.getClassLoader());
            // 2.2提供原始对象(父类)的class对象
            enhancer.setSuperclass(userService.getClass());
            // 2.3提供额外功能
            MethodInterceptor interceptor = new MethodInterceptor() {
                // 等同于 InvocationHandler => invoke()
                @Override
                public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    System.out.println("----cglib log----");
                    // 执行原始方法
                    Object ret = method.invoke(userService, args);
    
                    return ret;
                }
            };
            enhancer.setCallback(interceptor);
            // 3.创建代理
            UserService userServiceProxy = (UserService) enhancer.create();
            // 4.调用方法
            userServiceProxy.login("lyc", "123");
            userServiceProxy.register(new User());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
  • 总结

    JDK动态代理: Proxy类 Proxy.newProxyInstance() 通过接口创建代理的实现类

    Cglib动态代理: Enhancer类 Enhancer.setXx() 通过继承父类创建的代理类

Ⅲ. Spring工厂如何加工原始对象

  • 再谈BeanPostProcessor

    AOP的底层实现

image-20220627104344739

BeanPostProcessor就是对Spring工厂创建的对象进行加工,在前面开发Spring动态代理的过程中,获取到Spring的ApplicationContext工厂后,通过原始对象的id属性实际上获取的是代理对象,实际上Spring就是通过在BeanPostProcessor的After方法中对于传来的bean对象通过代理模式为其生成一个代理对象,最后返回的是这个代理对象,所以我们最后通过原始类的id属性获取的是代理对象。

  • 编码

    工厂代理类

    public class ProxyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            return bean;
        }
    
        /*
             Proxy.newProxyInstance();
         */
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            InvocationHandler handler = new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("=====BeanPostProcessor's new log=====");
                    Object ret = method.invoke(bean, args);
                    return ret;
                }
            };
            return Proxy.newProxyInstance(
                ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    配置文件applicationContext.xml

    <bean id="userService" class="com.lyc.factory.UserServiceImpl"></bean>
    
    <!--
        1. 实现BeanPostProcessor 进行加工
        2. 配置文件中对BeanPostProcessor进行配置
    -->
    <bean id="proxyBeanPostProcessor" class="com.lyc.factory.ProxyBeanPostProcessor"></bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试成功

    image-20220623232511334

6. 基于注解的AOP编程

Ⅰ. 基于注解的AOP编程的开发步骤

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

而AOP的注解编程,将后三步直接整合到了一起

只需要两步

  1. 原始对象
  2. 定义切面类
  • 编码

    切面类

    package com.lyc.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    /*
        1.额外功能
                 public class MyAround implements MethodInterceptor {
                     public Object invoke(MethodInvocation invocation) {
    
                         Object ret = invocation.proceed();
    
                         return ret;
                     }
                 }
        2.切入点
               <aop:config>
                   <aop:pointcut id="" expression="execution(* login(..))"/>
               </aop:config>
     */
    // 使用Aspect注解,表示这个类是一个切面类 在切面类里可以定义很多的切入点+额外功能
    @Aspect
    public class MyAspect {
    
        // 使用Around注解表示这是额外功能,类似于动态代理的invoke方法,返回值就是原始对象的返回值
        // 在注解里面直接定义切入点表达式
        // 注意方法的修饰符和返回值必须是 public + Object 方法名可以自定义
        // 参数 ProceedingJoinPoint 等同于原来的 MethodInvocation 此自定义方法等同于之前的invoke()方法
        @Around("execution(* login(..))")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("====aspect log====");
            Object ret = joinPoint.proceed();
    
            return ret;
        }
    }
    /*
    通过切面类 定义了 额外功能 @Around
             定义了  切入点  @Around("execution(* login(..))")
             @Aspect 切面类
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    配置文件applicationContext.xml

    <bean id="userService" class="com.lyc.aspect.UserServiceImpl"></bean>
    
    <!--
       切面
         1. 额外功能
         2. 切入点
         3. 组装切面
    -->
    <bean id="around" class="com.lyc.aspect.MyAspect"></bean>
    
    <!-- 告知Spring基于注解进行AOP编程 -->
    <aop:aspectj-autoproxy/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    测试

    public class TestAspectProxy {
        public static void main(String[] args) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
            UserService userService = (UserService) ctx.getBean("userService");
    
            userService.login("lyc", "csgo");
    
            userService.register(new User());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    只有login方法加入了额外功能

    image-20220624120957812

Ⅱ. 细节

  • 切入点复用

    @PointCut注解

    把切入点的配置 提取到一个独立的方法上面,打上@Pointcut注解;定义切入点表达式,后续更加有利于切入点复用。

    @Aspect
    public class MyAspect {
    
        // 提取切入点至@PointCut注解中 利于代码的后续维护
        // 权限修饰符和返回值必须是 public + void 方法名任意 方法体可以为空
        @Pointcut("execution(* login(..))")
        public void myPointCut() {}
        
        // 直接引用方法名
        @Around("myPointCut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("====aspect log====");
            Object ret = joinPoint.proceed();
    
            return ret;
        }
    
        @Around("myPointCut()")
        public Object around1(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("====aspect tx====");
            Object ret = joinPoint.proceed();
    
            return ret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
  • 动态代理的创建方式

    AOP底层实现  2种代理创建方式
      1. JDK   通过实现接口 做新的实现类方式 创建代理对象
      2. Cglib 通过继承父类 做新的子类      创建代理对象
      
    默认情况 AOP编程 底层应用JDK动态代理创建方式 
    如果切换Cglib
         1. 基于注解AOP开发
            <aop:aspectj-autoproxy proxy-target-class="true" />
         2. 传统的AOP开发
            <aop:config proxy-target-class="true">
            </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

7. AOP开发中的一个坑

Ⅰ. 问题描述

在同一个业务类中,业务方法间进行调用,如方法a调用了方法b,两个方法都增加了额外功能,但是最后在实际调用过程中只有a方法的额外功能实现了,b没有实现。

public void a() {
	b();
}

public void b() {
	...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

原因:在同一个业务类中,进行业务方法间的相互调用,只有最外层的方法,才加入了额外功能(内部的方法,通过普通的方式调用,都调用的是原始方法)

public class UserServiceImpl implements UserService {

    @Override
    public void register(User user) {
        System.out.println("UserServiceImpl.register 业务运算 + DAO ");
        // 内部调用login方法
        this.login("lyc", "csgo");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

myPointCut方法的注解需要改成@Pointcut("execution(* *..UserServiceImpl.*(..))"),UserServiceImpl类中所有的方法都会加上额外功能

测试

public class TestAspectProxy {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext2.xml");
        UserService userService = (UserService) ctx.getBean("userService");

        //userService.login("lyc", "csgo");
        
        userService.register(new User());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

执行结果,只有register方法加入了额外功能,login方法未加上额外功能:

image-20220624130037117

这是因为在Test中调用register方法的实际是Spring创建的代理对象Proxy,而在register方法中调用login方法的是this对象UserServiceImpl,这只是个原始类,只执行核心功能。

Ⅱ. 解决方案

实现ApplicationContextAware接口重写setApplicationContext()方法,将ApplicationContext作为成员变量在setApplicationContext()方法中进行赋值,在调用中使用ApplicationContext对象获取代理对象执行方法。

注意: 不要在方法中创建ApplicationContext对象,因为这个对象代表了Spring工厂,占用资源多,一个程序中应该只存在一个。

// 如果想让内层的方法也调用代理对象的方法,就要实现AppicationContextAware接口获得工厂,进而获得代理对象
public class UserServiceImpl implements UserService, ApplicationContextAware {

    private ApplicationContext ctx;

    // 获取工厂
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

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

        // 调用的是原始对象的login方法 => 核心功能
        /*
            设计目的:代理对象的login方法 =>  额外功能+核心功能
            ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext2.xml");
            UserService userService = (UserService) ctx.getBean("userService");
            userService.login();

            Spring工厂重量级资源 一个应用中 应该只创建一个工厂
         */
        UserService userService = (UserService) ctx.getBean("userService");
        userService.login("lyc", "csgo");
    }

    @Override
    public boolean login(String name, String password) {
        System.out.println("UserServiceImpl.login");
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

测试

两个方法都加上了额外功能

image-20220624132501606

8. AOP阶段知识总结

image-20220624132523232

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/86780
推荐阅读
相关标签
  

闽ICP备14008679号