Spring 的 AOP 功能

Spring AOP

文章目录

  • Spring AOP
    • 1 AOP 的概念
      • 1.1 基本描述
      • 1.2 AOP 的作用
      • 1.3 AOP 相关术语
    • 2 AOP 原理概述
      • 2.1 JDK 提供的 Proxy 类实现动态代理
      • 2.2 CGLIB 提供的 Enhancer 类实现动态代理
    • 3 AOP 操作
      • 3.1 切入点表达式
      • 3.2 基于注解的方式实现 AOP 操作
      • 3.3 基于 XML 配置文件的方式实现 AOP 操作(了解)
      • 3.4 完全注解开发

1 AOP 的概念

1.1 基本描述

Spring 官方解释:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop

  • 面向切面编程(Aspect-oriented Programming ,AOP)通过提供另一种思考 程序设计结构的方式来 补充面向对象程序设计(Object-oriented Programming,OOP)。
  • 面向对象编程中(OOP),模块化的关键单元是 ,而在面向切面编程中(AOP),模块化的单元是 切面
  • 切面 支持对关注点的模块化(比如事务管理)跨多个类型和对象。(这种关注在 AOP 文献中通常被称为 “跨领域”关注)
  • AOP 是 Spring 的一个关键组件,尽管 Spring IoC 容器不依赖于 AOP(意味着你不需要用AOP的话,可以不使用),但 AOP 是对 Spring IoC 的补充,提供了功能强大的中间件解决方案。

也可以去看看:百度百科


1.2 AOP 的作用

  可以对业务逻辑得到各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
  通俗描述就是,在不修改源代码的条件下,在主干功能中添加新功能,就是 AOP 的过程。


1.3 AOP 相关术语

  • 切面(Aspect): 用于跨多个类来实现切点模块化,在 Spring AOP 中,切面是通过使用常规类或者带有 @Aspect 注解的常规类来实现的。【把增强处理应用到切入点的过程】

  • 连接点(Join point): 程序执行过程中明确的点,如方法的调用,或者异常的抛出。在Spring AOP中,连接点总是指方法的调用。【类里面哪些方法可以被增强,这些方法称为连接点。】

  • 通知 / 增强(Advice): 切面在特定的连接点处采取的操作,增强/通知的类型有有 aroundbeforeafter 等类型。【实际增强的逻辑部分称为通知(增强处理),通知有多种类型:前置通知、后置通知、环绕通知、异常通知、最终通知】

  • 切入点(Pointcut): 切入点是与连接点匹配的描述。通知 (advice)切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配连接点的概念是 AOP 的核心,Spring 默认使用 AspectJ 切入点表达式。【切入点 就是可以插入增强处理的连接点。简而言之,当某个连接点被添加增强处理后就变成了切入点。】

  • 引入(Introduction): 引用允许你添加新方法或属性到现有的类中。

  • 目标对象(Target object):被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。

  • AOP 代理(AOP proxy) : 为了实现切面约定(通知方法的执行等),使用AOP框架创建对象。在Spring Framework中,AOP 使用 JDK 动态代理 或 CGLIB 代理。

  • 编织(Weaving):Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。

2 AOP 原理概述

AOP 底层使用到了动态代理模式:

  • 特点:字节码文件随用随创建,随用随加载。
  • 作用:不修改源码的基础上对方法增强。
  • 分类:基于接口的动态代理和基于子类的动态代理。
    • 第一种,基于接口的动态代理,使用 JDK 实现动态代理。
    • 第二种,基于子类的动态代理,使用第三方 CGLIB 库实现动态代理。

菜鸟教程-代理模式

简单理解代理模式:用户购买电脑不是到生产厂家处购买,而是到经销商处购买,经销商又从生产厂家取货。倘若用户电脑出问题了,找的是经销商的售后,经销商售后要么自己处理,要么返还给生产厂家处理。这个过程就叫做代理。

2.1 JDK 提供的 Proxy 类实现动态代理

  这是基于接口的动态代理方式,使用 proxy 类中的 static Object newProxyInstance​(ClassLoader loader, Class[] interfaces, InvocationHandler h) 方法创建代理对象。

可参考:Proxy API介绍

参数解读:

  • loader :类加载器,写的是被代理对象的类加载器,是固定写法
  • interfaces :用于让代理对象和被代理对象拥有相同的方法,是固定写法。
  • InvocationHandler: 用于提供增强的代码,让我们写如何代理,一般都是写一个该接口的实现类,通常情况下使用匿名内部类。

代码演示:

1 创建接口,定义相关方法

public interface UserDao {

    public int add(int a,int b);

    public String update(String name);

    public void run(String name);
}

2 创建接口实现类并实现方法

public class UserDaoImpl implements UserDao{
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了。。。");
        return a + b;
    }

    @Override
    public String update(String name) {
        System.out.println("update方法执行了。。。");
        return name;
    }

    @Override
    public void run(String name) {
        System.out.println("run 方法执行了。。。=>>" + name + "跑起来了。");
    }
}

3 使用 Proxy 类创建接口代理对象。

public static void main(String[] args) {
    // 创建被代理的对象
    final UserDaoImpl udi = new UserDaoImpl();

    //匿名内部类的方式,在内部实现 InvocationHandler 接口
    UserDao ud = (UserDao)Proxy.newProxyInstance(
            JDKProxy.class.getClassLoader(),
            UserDaoImpl.class.getInterfaces(),
            new InvocationHandler() {
                /**
                    * 作用:执行被代理对象的任何接口方法都会经过该方法,具有拦截过滤功能
                    * @param proxy 在其上调用方法的代理实例
                    * @param method 当前执行的 方法
                    * @param args  当前执行 方法 所需的参数,可以通过索引来操作
                    * @return       和被代理对象 方法 有相同的返回值
                    * @throws Throwable
                    */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //提供增强代码,拦截对应的方法作增强
                    if ("add".equals(method.getName())){
                        Object obj = (Integer)method.invoke(udi,args)*10;
                        return obj;
                    }
                    if ("update".equals(method.getName())){
                        Object obj = method.invoke(udi,"欢迎 " + args[0]);
                        return obj;
                    }
                    //不影响未增强的方法
                    return method.invoke(udi,args);
            });
    System.out.println(ud.add(1,2));    //增强了方法,将两数相加的结果乘以10倍
    System.out.println(ud.update("Roan"));  //在 name 前面加了欢迎。
    ud.run("Jacks");    //未增强的方法
}

2.2 CGLIB 提供的 Enhancer 类实现动态代理

  这是基于子类的动态代理方式,是由第三方 cglib 库提供,使用 Enhancer 类中的 public static Object create(Class type, Callback callback) 方法创建代理对象。

可参考以下博客:CGLIB原理及实现机制

参数解读:

  • type :用于指定被代理对象的字节码。
  • callback :用于提供增强的代码,与 JDK 动态代理中的 InvocationHandler 大同小异,这里实现的是 MethodInterceptor 接口。

代码演示:

1 创建类

public class Producer {
    public void saleProduct(float money) {
        System.out.println("销售产品,拿到钱~" + money);
    }

    public void afterService(float money) {
        System.out.println("提供售后服务,并拿到钱~" + money);
    }
}

2 创建代理对象

public class Client {
    public static void main(String[] args) {

        final Producer producer = new Producer();

        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param o     被代理实例
             * @param method    方法
             * @param objects   参数
             * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的。
             * @param methodProxy 当前执行方法的代理对象
             * @return  与被代理对象 方法 的返回值一致
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object rs = null;
                Float money = (Float) objects[0];
                if ("saleProduct".equals(method.getName())) {
                    rs = method.invoke(producer, money * 0.8f);
                }
                return rs;
            }
        });
        cglibProducer.saleProduct(1000);//使用代理对象调用方法

    }
}

3 AOP 操作

  • Spring 框架中一般基于 AspectJ 实现 AOP 操作。
  • AspectJ 不是 Spring 组成成分,独立 AOP 框架,一般把 AspectJ 和 Spring 框架一起使用,完成 AOP 操作。
  • 可以使用 AspectJ 分别基于 xml 配置文件和注解的方式实现 AOP 操作

3.1 切入点表达式

作用:知道对哪个类的哪个方法进行增强

语法结构:execution([权限修饰符][返回值类型][类的全限定类名][方法名称](参数列表))

实例:

execution(* com.jk.dao.UserDao.add(..));    //对 com.jk.dao.UserDao 中的 add 方法进行增强
execution(* com.jk.dao.UserDao.*(..));      //对 com.jk.dao.UserDao 中的所有方法进行增强
execution(* com.jk.dao.*UserDao*.*(..));    //对 com.jk.dao 中的所有类的所有方法进行增强
  • 可以用通配符* 代表任意。(任意权限修饰,任意方法,任意类,任意方法等)
  • .. 代表方法执行所需的参数。

3.2 基于注解的方式实现 AOP 操作

1 创建基本类,使用注解创建 User 和 UserProxy 对象。

@Component  //创建对象
public class User {
    public void add(){
        System.out.println("add 方法执行了");
    }
}

2 编写增强类,在增强类上面添加注解 @Aspect,配置不同类型的通知。

  • 在增强类的里面,在作为通知方法上添加通知类型注解,使用切入点表达式配置。
@Component
@Aspect     //生成代理对象
public class UserProxy {

    //相同切入点抽取
    @Pointcut(value = "execution(* com.jk.aop.annotation.User.add(..))")
    public void pointDemo(){}

    //前置通知
    // @Before(value = "execution(* com.jk.aop.annotation.User.add(..))")
    @Before("pointDemo()")      //抽取公共切入点
    public void before(){
        System.out.println("before...");
    }
    
    //最终通知:方法执行之后,无论有没有异常都会通知
    @After(value = "execution(* com.jk.aop.annotation.User.add(..))")
    public void after() {
        System.out.println("after...");
    }

    //后置通知(返回通知):方法返回值之后执行,有异常则不会通知
    @AfterReturning(value = "execution(* com.jk.aop.annotation.User.add(..))")
    public void afterReturning(){
        System.out.println("afterReturning...");
    }

    //异常通知:当方法有异常时执行
    @AfterThrowing(value = "execution(* com.jk.aop.annotation.User.add(..))")
    public void afterThrowing(){
        System.out.println("afterThrowing...");
    }

    //环绕通知:在方法执行前后都会通知,如果有异常,则环绕之后不会通知,环绕之前依旧通知。
    @Around(value = "execution(* com.jk.aop.annotation.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕之前。。。");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后...");
    }
}

3 进行通知/增强的配置

  • 在 SPring 配置文件中开启注解扫描,需要添加 contextaop 名称空间。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <context:component-scan base-package="com.jk.aop.annotation"/>
    
    <aop:aspectj-autoproxy/>
beans>

如果有多个增强类对同一个方法进行增强,可以在增强类上面添加注解 @Order(value)value 是一个整数,值越小,优先级越高。

@Component
@Aspect     //生成代理对象
@Order(3)
public class UserProxy {...}

@Component
@Aspect
@Order(1)
public class personProxy {...}

3.3 基于 XML 配置文件的方式实现 AOP 操作(了解)

1 创建两个类,增强类和被增强类

@Component
public class Student {

    public void run(){
        System.out.println("run 方法执行了");
    }
}

@Component
@Aspect     //生成代理对象
public class StudentProxy {

    @Before(value = "execution(* com.jk.aop.aopxml.Student.run(..))")
    public void before() {
        System.out.println("before...");
    }
}

2 在 Spring 配置文件中创建两个类的对象,配置切入点。

    
    <bean id="student" class="com.jk.aop.aopxml.Student"/>
    <bean id="userProxy" class="com.jk.aop.aopxml.StudentProxy"/>

    
    <aop:config>
        
        <aop:pointcut id="p" expression="execution(* com.jk.aop.aopxml.Student.run(..))"/>
        
        <aop:aspect ref="userProxy">
            
            <aop:before method="before" pointcut-ref="p"/>
        aop:aspect>
    aop:config>

3.4 完全注解开发

1 创建被增强类和增强类

@Component
public class Student {
    public void run(){
        System.out.println("run 方法执行了");
    }
}

@Component
@Aspect     //生成代理对象
public class StudentProxy {
    @Before(value = "execution(* com.jk.aop.aopxml.Student.run(..))")
    public void before() {
        System.out.println("before...");
    }
}

2 创建配置类,不需要创建 xml 配置

@Configuration
@ComponentScan(value = {"com.jk.aop.aopxml"})   // 等价于    
@EnableAspectJAutoProxy(proxyTargetClass = true)    // 等价于    
public class AopConfig {

}

3 测试方法

@Test
public void testNoXml(){
    ApplicationContext context =
            new AnnotationConfigApplicationContext(AopConfig.class);
    Student student = context.getBean("student", Student.class);
    student.run();
}

AOP 基础部分到此结束,但还是感觉有点云,不结合实际项目的确很难理解~

你可能感兴趣的:(SSM框架,Spring,AOP,动态代理)