Spring-AOP

Spring-AOP

一、静态代理

  • 静态代理,代理类拥有真实角色的引用,同时增强真实角色的方法

  • 以结婚为例,模拟静态代理过程:

    • 模拟思路:

      1. 编写一个抽象接口(Marry),统一对外暴露结婚接口(toMarry)
      2. 编写真实角色类(You)实现Marry的toMarry方法
      3. 编写代理类(MarryCompany)实现Marry接口,拥有真实的引用,同时在构造方法中实例化真实角色对象,再重写toMarry()方法时,设置增强该toMarry()的方法
    • 代码实现:

      /**
       * 方便对外统一接口,让代理角色和真实角色都实现接口方法
       */
      public interface Marry {
      
          /**
           * 结婚接口
           */
          void toMarry();
      }
      
      /**
       * 真实角色
       */
      public class You implements Marry {
      
          @Override
          public void toMarry() {
              System.out.println("等了这么久,终于等到你。。");
          }
      }
      
      /**
       * 代理角色,代理的是You
       */
      public class MarryCompany implements Marry {
      
          // 引用真实角色-又叫目标对象
          private You target;
      
          MarryCompany() {}
      
          // 采用构造器方式赋值target对象,当然也可以采用setter方法
          MarryCompany(You target) {
              this.target = target;
          }
      
          @Override
          public void toMarry() {
              // 编写增强方法
              before();
              // 调用真实角色的结婚方法
              target.toMarry();
              // 结婚之后的方法
              after();
      
          }
      
          private void before() {
              System.out.println("结婚现场紧张布置中。。。。");
          }
      
          private void after() {
              System.out.println("恭喜你进入人生第二阶段。。。。");
          }
      }
      
      public class MarryTest {
      
          @Test
          public void testMarry() {
              // 创建代理对象
              You real = new You(); // 创建真实角色
              MarryCompany marryCompany = new MarryCompany(real); // 创建代理对象
              marryCompany.toMarry();
          }
      }
      

二、动态代理

1. 概念

​ 在程序运行期,动态创建某个代理对象。

2. JDK反射机制生成

  1. 编写一个代理生成处理类,实现 InvocationHandler 接口

    public class JdkHandler implements InvocationHandler {
      
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {     
            return null;
        }
    }
    
  2. 编写一个目标对象(真实角色)的引用,是一个Object类型

    public class JdkHandler implements InvocationHandler {
        
        // 真实角色的引用
        private Object target;
       
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {     
            return null;
        }
    }
    
  3. 编写生成代理对象的方法
    public class JdkHandler implements InvocationHandler {
    
        // 真实角色的引用
        private Object target;
    
        /**
         * 获取代理对象 -- 重点,通过反射机制获取代理对象
         * @param target 告诉代理生成器要生成什么样的代理对象
         * @return
         */
        public Object getProxy(Object target) {
            // 传入类加载器、当前对象的所有接口方法、InvocationHandler对象
            this.target = target;
            return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                   return null;
        }
    }
    
  4. 重写invoke方法,设置增强,同时执行代理方法
    /**
     * 基于JDK的代理
     */
    public class JdkHandler implements InvocationHandler {
    
        // 真实角色的引用
        private Object target;
    
        /**
         * 获取代理对象 -- 重点,通过反射机制获取代理对象
         * @param target 告诉代理生成器要生成什么样的代理对象
         * @return
         */
        public Object getProxy(Object target) {
            // 传入类加载器、当前对象的所有接口方法、InvocationHandler对象
            this.target = target;
            return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           // 增强方法
            before();
            // 执行真实角色的方法
            Object result = method.invoke(target, args);
            after();
            return result;
        }
    
        private void before() {
            System.out.println("结婚现场紧张布置中。。。");
        }
    
        private void after() {
            System.out.println("恭喜你进入人生第二阶段。。。");
        }
    }
    
  5. 测试

    public class JdkDynamicProxyTest {
    
        @Test
        public void testJdkProxy() {
            // 真实角色
            Marry marry = new You();
    
            JdkHandler jdkHandler = new JdkHandler();
            // 生成一个代理对象
            Marry marryCompany = (Marry)jdkHandler.getProxy(marry);
            // 执行接口方法, 会去执行Handler的invoke()方法
            marryCompany.toMarry();
        }
    
        @Test
        public void testHouseRentProxy() {
            // 真实角色
            RentHouse rentHouse = new Customer();
    
            JdkHandler jdkHandler = new JdkHandler();
            // 生成一个代理对象, 会去执行Handler的invoke()方法
            RentHouse lianjia = (RentHouse)jdkHandler.getProxy(rentHouse);
            // 执行接口方法
            lianjia.rent();
        }
    }
    

3. CGLib实现

  • 使用Spring的CGLib模块完成动态代理

  • 实现步骤:

    1. 实现 MethodInterceptor 接口

    2. 编写一个真实角色的引用

    3. 编写构建代理对象的方法:通过 Enhancer类 进行创建,不需要相关的接口

    4. 重写拦截请求代理的方法,设置增强

      import org.springframework.cglib.proxy.Enhancer;
      import org.springframework.cglib.proxy.MethodInterceptor;
      import org.springframework.cglib.proxy.MethodProxy;
      
      import java.lang.reflect.Method;
      
      public class DynamicCglibProxyHandler implements MethodInterceptor {
          // 真是角色的引用
          private Object target;
      
          /**
           * 创建代理对象
           * @param target 真实角色对象
           * @return
           */
          public Object buildProxy(Object target) {
              this.target = target;
              // 创建代理对象
              Enhancer enhancer = new Enhancer();
              // 通过enhancer对象创建代理对象
              enhancer.setSuperclass(target.getClass());
              enhancer.setCallback(this);
              Object proxy = enhancer.create();
              return proxy;
          }
      
          /**
           * 拦截执行代理的方法
           * @param o
           * @param method
           * @param objects 执行代理方法的参数
           * @param methodProxy 执行代理的方法
           * @return
           * @throws Throwable
           */
          @Override
          public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
                  throws Throwable {
              before();
              Object result = methodProxy.invoke(target, objects);// 其中执行的是真实角色的方法索引应该传入target对象
              after();
              return result;
          }
      
          private void before() {
              System.out.println("方法执行前。。。。");
          }
      
          private void after() {
              System.out.println("方法执行后。。。。");
          }
      }
      
      • 编写一个StudyJava的类
      public class StudyJava {
      
          public String study(String name) {
              System.out.println("好喜欢学JAVA...");
              return name + ":好喜欢学JAVA";
          }
      }
      
      • 编写一个测试类
      import com.xyz.marry.Marry;
      import com.xyz.marry.You;
      import com.xyz.rent.Customer;
      import com.xyz.rent.RentHouse;
      import com.xyz.study.StudyJava;
      import org.junit.Test;
      
      public class TestDynamicCGlibProxy {
      
          @Test
          public void testDynamicProxy() {
              // 创建一个生成器
              DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
              // 创建一个真实角色
              Marry marry = new You();
              // 创建代理对象
              Marry proxy = (Marry) handler.buildProxy(marry);
              // 执行Marry的toMarry方法
              proxy.toMarry();
          }
      
          @Test
          public void testRentHouse() {
              // 创建生成器
              DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
              // 创建租房真实角色
              RentHouse rentHouse = new Customer();
              // 生成一个代理对象
              RentHouse lianjia = (RentHouse)handler.buildProxy(rentHouse);
              // 执行代理租房方法
              String result = lianjia.rent("Nobody");
              System.out.println("最终结果:" + result);
          }
      
          @Test
          public void testStudyJava() {
              // 创建生成器
              DynamicCglibProxyHandler handler = new DynamicCglibProxyHandler();
              // 创建真实角色
              StudyJava studyJava = new StudyJava();
              // 生成一个代理对象
              StudyJava banzhang = (StudyJava)handler.buildProxy(studyJava);
              // 执行代理方法
              String result = banzhang.study("Nobody");
              System.out.println("最终结果:" + result);
          }
      }
      

三、总结

在这里插入图片描述

1、静态代理和动态代理的区别
  1. 静态代理代理的是某一个类或接口;动态代理代理多种接口
  2. 静态代理的代理类由手动创建即程序运行前创建;动态代理自动创建,在程序运行期获取
2、基于JDK的动态代理和CGlib的区别
  • CGLib解决了JDK只能代理接口的局限性

四、AOP编程概念

​ AOP编程:就是面向切面编程,它是OOP的一种补充,原因是OOP关注的是某个类下的某个方法,而AOP关注的某些类下的某些方法。

五、AOP的应用场景

​ 一些公用的功能模块,比如:日志统计、性能分析、权限控制、事物管理等等。

六、AOP的优点

  • 降低模块与模块之间的耦合,提高模块的聚合 — 高内聚、低耦合
  • 提高代码的复用性
  • 提高了功能的扩展性

七、AOP的基本概念

  • 连接点(JoinPoint):被AOP拦截的方法
  • 切入点(PointCut):定义AOP拦截连接点的规则
  • 通知(Advice):也称作–增强,在连接点执行的基础上设置一些方法:
    • 前置通知(before advice):在方法前执行
    • 后置通知(after advice):方法执行后执行(不管是正常结束还是抛出异常都会执行)
    • 异常通知(exception advice):方法执行中抛出异常会调用
    • 返回通知(return advice):方法正常返回结果后调用
    • 环绕通知(around advice):拦截连接点,自定义连接点的执行流程
  • 切面(Aspect):切入点 + 通知组成
    • 决定了切面的定义,切入点定义了要拦截哪些类的哪些方法,通知则定义了拦截过方法后要做什么,切面则是横切关注点的抽象,与类相似,类是对物体特征的抽象,切面则是横切关注点抽象。
  • 目标对象(Target):就是被代理的真实对象
  • 织入(Weave):将切面应用到目标对象,同时创建代理对象的过程
  • 引入(Introduction):在不修改原有应用程序代码的情况下,在程序运行期动态给连接点添加方法或者属性的过程

八、面向切面编程

AOP 面向切面编程注解实现

​ 使用 Aop 解决日志处理问题

  1. 引入aspectj语法依赖包

    <!--aspectj语法的jar包-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.9</version>
    </dependency>
    
  2. 开启aop注解驱动

    xmlns:aop="http://www.springframework.org/schema/aop"
    			http://www.springframework.org/schema/aop
    			http://www.springframework.org/schema/aop/spring-aop.xsd
    
    <!--开启AOP注解驱动-->
    <aop:aspectj-autoproxy />
    
  3. 编写一个LogAspect切面

    • 编写一个LogAspect类,添加@Component注解,标记此类交给Spring管
    • 添加@Aspect注解,标记此类是一个切面
    @Component // 代表此类交给Spring管理
    @Aspect // 标记此类是一个切面
    public class LogAspect {
        
    }
    
  4. 编写一个切入点

    • 采用aspectj语法进行定义(AOP 切入点表达式简介):
      • 执行任意公共方法:execution(public *(…))
      • 执行任意的set方法:execution(* set*(…))
      • 执行com.xyz.service包下任意类的任意方法:execution(* com.xyz.service.*.*(…))
      • 执行com.xyz.service 包 以及子包下任意类的任意方法:execution(* com.xyz.service…*.*(…))
    // 定义一个切入点:就是定义连接点(被AOP拦截的方法)的规则
    @Pointcut("execution(* com.xyz.service..*.*(..))")
    public void pointcut(){}
    
  5. 编写各种增强

    // 定义前置增强 -- 比较常见
    @Before("pointcut()")
    public void before(JoinPoint joinPoint) {
        // 获取目标对象
        Object target = joinPoint.getTarget();
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取执行方法
        Method method = signature.getMethod();
        System.out.println("方法名称:" + method.getName());
        // 获取参数
        Parameter[] parameters = method.getParameters();
        System.out.println("前置增强。。。。");
    }
    
    // 定义后置返回增强
    @AfterReturning("pointcut()")
    public void afterReturning() {
        System.out.println("后置返回增强。。。");
    }
    
    // 定义异常增强
    @AfterThrowing("pointcut()")
    public void afterThrowing() {
        System.out.println("异常增强。。。");
    }
    
    // 定义最终(后置)增强
    @After("pointcut()")
    public void after() {
        System.out.println("最终增强。。。");
    }
    
    // 环绕增强 --- 比较常见
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        // 获取目标对象
        Object target = joinPoint.getTarget();
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取执行方法
        Method method = signature.getMethod();
        System.out.println("方法名称:" + method.getName());
        // 获取参数
        Parameter[] parameters = method.getParameters();
    
        // 执行连接点方法
        try {
            Object result = joinPoint.proceed(); // 获取连接点返回值
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
    
  6. 测试

    public class LogAspectTest {
    
        @Test
        public void testBefore() {
            ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
            UserService userService = ac.getBean(UserService.class);
            User user = new User();
            user.setUserName("abc");
            user.setPassword("111");
            user.setId(1);
            userService.addUser(user);
        }
    
    
        /**
         * 测试返回增强
         */
        @Test
        public void testAfterReturning() {
            ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
            UserService userService = ac.getBean(UserService.class);
            userService.updateUser();
        }
    
        /**
         * 测试异常增强
         */
        @Test
        public void testThrowReturning() {
            ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
            UserService userService = ac.getBean(UserService.class);
            userService.deleteUser();
        }
    
        /**
         * 测试环绕增强
         */
        @Test
        public void testAround() {
            ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
            UserService userService = ac.getBean(UserService.class);
            String result = userService.updateUser();
            System.out.println(result);
        }
    }
    

AOP 面向切面编程 xml 配置

  1. 先编写一个切面类

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    public class PermissionAspect {
    
        // 定义前置增强 -- 比较常见
        public void before(JoinPoint joinPoint) {
            // 获取目标对象
            Object target = joinPoint.getTarget();
            // 获取方法签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取执行方法
            Method method = signature.getMethod();
            System.out.println("方法名称:" + method.getName());
            // 获取参数
            Parameter[] parameters = method.getParameters();
            System.out.println("前置增强。。。。");
        }
    
        // 定义后置返回增强
        public void afterReturning() {
            System.out.println("后置返回增强。。。");
        }
    
        // 定义异常增强
        public void afterThrowing() {
            System.out.println("异常增强。。。");
        }
    
        // 定义最终(后置)增强
        public void after() {
            System.out.println("最终增强。。。");
        }
    
        // 环绕增强 --- 比较常见
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            // 获取目标对象
            Object target = joinPoint.getTarget();
            // 获取方法签名
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取执行方法
            Method method = signature.getMethod();
            System.out.println("方法名称:" + method.getName());
            // 获取参数
            Parameter[] parameters = method.getParameters();
    
            // 执行连接点方法
            Object result = joinPoint.proceed(); // 获取连接点返回值
            return result;
    
        }
    }
    
  2. 在XML中进行切面、切点、增强的配置

    <!--配置一个Aspect Bean-->
    <bean class="com.xyz.configuration.PermissionAspect" id="permissionAspect"/>
    <!--配置切面-->
    <aop:config>
        <!--关联切面-->
        <aop:aspect ref="permissionAspect">
            <!--定义切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.xyz.controller..*.*(..))"/>
            <!--定义前置通知-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <!--定义返回通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
            <!--定义异常通知-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
            <!--定义最终通知-->
            <aop:after method="after" pointcut-ref="pointcut"/>
            <!--定义环绕通知-->
            <aop:around method="around" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
    

增强的执行顺序

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值