Spring5框架五:AOP

AOP

一、概述
  1. 概念

    AOP:Aspect Oriented Programming,面向切面编程,是函数式编程的一种衍生范型(反射)。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

  2. 目的

    将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

  3. 通俗理解

    在不改变源代码的基础上,增加新的功能。

二、底层原理
  1. 底层使用动态代理
  2. 两种情况

    ① 有接口,使用JDK动态代理,创建当前接口实现类的代理对象

    ② 没有接口,使用CJLIB动态代理,创建当前类的子类的代理对象

三、JDK动态代理底层代码
  1. 有接口的示例
    interface UserDAO {
        public int add(int a, int b);
        public int update(int c);
        
    }
    public class UserDAOImpl implements UserDAO{
        @Overload
        public int add(int a, int b) {
            return a+b;
        }
        @Overload
        public int update(int c) {
            return c;
        }
    }
    
  2. 使用Proxy类,创建代理对象

    在这里插入图片描述

    newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
    // 第一个参数:被代理类的类加载器
    // 第二个参数:接口数组,指被增强方法所在的类,这个类实现的一个或多个接口
    // 第三个参数:实现InvocationHandler这个接口,传入被代理类的对象,写增强方法
    
    public class JdkProxy {
        public static void main(String[] args) {
            /*
                第一个参数:就是该类的类加载器
                第二个参数:一个Class数组,里面是实现类实现的各个接口的Class(需要创建)
                第三个参数:一个InvocationHandler接口的实现类(需要创建)
            */
            // 1.创建第二个参数:创建被代理实现类所实现的所有接口的Class数组
            Class[] interfaces = {UserDAO.class};
            // 3.创建被代理实现类的对象,用于传入第三个参数中
            UserDAOImpl userDAO = new UserDAOImpl();
            // 4.通过Proxy.newProxyInstance()方法创建指定实现类的代理对象
            UserDAO dao = (UserDAO)Proxy.newProxyInstance(                              													JdkProxy.class.getClassLoader(), 
               						   				    interfaces, 
              										    new UserDAOProxy(userDAO)
                                                         );
            // 5.通过生成的代理对象调用某个方法,该方法就会被增强
            int add = dao.add(1, 2);
            System.out.println("add = " + add);
        }
    }
    
    // 2.创建第三个参数,实现InvocationHandler接口
    class UserDAOProxy implements InvocationHandler {
        // 2.1 通过有参构造器将被代理类传进去,写成Object类型,是为了通用性,直接写成UserDAOImpl也可以。
        private Object obj;
        public UserDAOProxy(Object obj) {
            this.obj = obj;
        }
        // 2.2 在invoke方法内写增强的逻辑
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("执行方法之前");
            Object result = method.invoke(obj, args);
            System.out.println("执行方法之后");
            return result;
        }
    }
    
  3. 更加深入的分析及动态代理:详见博客 Java七十: 反射应用 - - 动态代理
四、AOP术语
  1. 连接点

    类里面哪些方法可以被增强,这些方法就称为连接点

  2. 切入点

    实际被真正增强的方法,就称为切入点

  3. 通知(增强)

    实际增强的逻辑部分称为通知,分为5类

    前置通知:在切入点(被增强方法)之前执行

    后置通知:在切入点之后执行,当切入点有异常,后置通知不执行

    环绕通知:在切入点前后都可执行,当切入点有异常,环绕后通知不执行,环绕前通知仍然执行

    异常通知:当切入点有异常时执行

    最终通知:无论什么情况,都会被最终执行

  4. 切面

    把通知应用到切入点的过程

五、AOP的操作的准备工作
  1. Spring框架中基于AspectJ 实现AOP操作

    AapectJ 不是Spring组成部分,是一个独立的AOP框架,一般把AspectJ框架和Spring框架一起使用,进行AOP操作

  2. 引入依赖

    在这里插入图片描述

  3. 切入点表达式

    作用:明确对哪个类中的哪个方法进行增强

    结构:

    execution([权限修饰符][返回类型][类全路径][方法名][参数列表])1,对com.atguigu.dao.BookDao类中的add方法进行增强
    execution(* com.atguigu.dao.BookDao.add(..))2,对com.atguigu.dao.BookDao类中的所有方法进行增强
    execution(* com.atguigu.dao.BookDao.*(..))3,对com.atguigu.dao包中所有类类中的所有方法进行增强
    execution(* com.atguigu.dao.*.*(..))
    
六、基于AspectJ注解的AOP操作
  1. 在Spring配置文件中,开启注解扫描
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.atguigu.spring5.aopannotation"></context:component-scan>
    
  2. 使用注解创建User和UserProxy对象
    // 被代理类
    @Repository
    public class User {
        public void add() {
            System.out.println("add方法");
        }
    }
    
    // 代理类(增强类)
    @Component
    // 表示可以生成代理对象
    @Aspect
    public class UserProxy {
        
    }
    
  3. 在Spring配置文件中开启生成代理对象
    <!-- 开启Aspect生成代理对象 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  4. 配置不同类型的通知
    // 表示可以被扫描到
    @Component
    // 表示可以生成代理对象
    @Aspect
    public class UserProxy {
        // 前置通知
        @Before(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void before() {
            System.out.println("before...前置通知");
        }
    	
        // 后置通知
        @AfterReturning(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void afterReturning() {
            System.out.println("afterReturning...后置通知");
        }
    	
        // 最终通知
        @After(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void after() {
            System.out.println("after...最终通知");
        }
    	
        // 异常通知
        @AfterThrowing(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void afterThrowing() {
            System.out.println("afterThrowing...异常通知");
        }
    	
        // 环绕通知
        @Around(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("around...环绕之前");
            proceedingJoinPoint.proceed();
            System.out.println("around...环绕之后");
        }
    }
    

    注:@Aspect配合通知注解内的切点execution表达式,就完成了Proxy.newProxyInstance()及InvocationHandler的功能

  5. 测试类
    public class TestAOP {
        @Test
        public void testAOP() {
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
            User user = context.getBean("user",User.class);
            user.add();
        }
    }
    
    执行结果

    在这里插入图片描述

  6. 抽取相同切入点表达式

    注意,通知内的value值中的方法一定要带括号

    @Component
    @Aspect
    public class UserProxy {
    
        // 抽取相同切入点表达式
        @Pointcut(value = "execution(* com.atguigu.spring5.aopannotation.User.add(..))")
        public void point() {
    
        }
        // 前置通知
        @Before(value = "point()")
        public void before() {
            System.out.println("before...前置通知");
        }
    	
        @AfterReturning(value = "point()")
        public void afterReturning() {
            System.out.println("afterReturning...后置通知");
        }
    }
    
  7. 优先级@Order,有多个代理类时,可在这些类上使用@Order来确定执行顺序
    // @Order从0开始的整数,数字越小,优先级越高
    @Component
    @Aspect
    @Order(2)
    public class UserProxy {}
    
七、基于AspectJ的xml配置文件的AOP操作
  1. 新建两个类
    // 被增强类
    public class Book {
        public void buy() {
            System.out.println("buy...");
        }
    }
    
    // 代理类
    public class BookProxy {
        public void add() {
            System.out.println("增强...");
        }
    }
    
  2. 对两个类进行AOP配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 									http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/aop 									http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置类 -->
        <bean id="book" class="com.atguigu.spring5.annoxml.Book"></bean>
        <bean id="bookProxy" class="com.atguigu.spring5.annoxml.BookProxy"></bean>
    
        <!-- 配置aop增强 -->
        <aop:config>
            <!-- 配置切入点 -->
            <aop:pointcut id="p" expression="execution(* com.atguigu.spring5.annoxml.Book.buy(..))"/>
            <!-- 配置切面,即哪个增强方法,其类型和切入点 -->
            <aop:aspect ref="bookProxy">
                <aop:before method="add" pointcut-ref="p"/>
            </aop:aspect>
        </aop:config>
    </beans>
    
八、完全注解进行AOP操作
  1. 增加一个配置类
    @Configuraton
    // 开启注解扫描
    @ComponentScan(basePackages="com.atguigu.spring5.aopannotation")
    // 开启Aspect生成代理对象
    @EnableAspectAutoProxy(proxyTargeClass=true)
    public class ConfigAOP {
        
    }
    
  2. 后续操作和第六部分完全一样
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

e_nanxu

感恩每一份鼓励-相逢何必曾相识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值