3.spring5-AOP

代理模式

Subject(抽象主题角色) :定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;

RealSubject(真实主题角色 ):也叫被代理角色/委托类/目标对象,是业务逻辑的具体执行者,真正实现业务逻辑的类;

Proxy(代理主题角色) :也叫代理类/代理对象,它负责对真实角色的应用,把所有抽象主题类定义的方法限制委托给真实主题角色实现,并在真实主题角色处理前后做一些预处理或善后工作。

java 复制代码
/**
 * 抽象主题接口
 */
public interface Subject {
    void doSomething();
}

/**
 * 真实主题角色
 */
public class RealSubject implements Subject{
 
    @Override
    public void doSomething() {
        //TODO 具体执行的事
    }
}

/**
 * 代理主题角色
 */
public class Proxy implements Subject{
    //要代理的具体实现类
    private Subject realSubject;
 
    public Proxy(Subject realSubject){
        this.realSubject = realSubject;
    }
    @Override
    public void doSomething() {
        this.before();
        realSubject.doSomething();
        this.after();
    }
 
    // 预处理
    private void before(){
        // TODO
    }
 
    // 善后处理
    private void after(){
        // TODO
    }
}
  • 静态代理模式

    所谓静态 也就是在程序运行前 就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。

    优点:

    • 通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码.

    缺点:

    • 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
    • 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
    • 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护
      代码参考:spring-sop staticproxy包
  • 动态代理模式

    不事先为每个原始类编写代理类,而是在运行的时候,动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

    • JDK动态代理

      JDK动态代理主要涉及两个类:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler

      java 复制代码
      package com.light.dynamicproxy;
      
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;
      import java.util.Date;
      
      public class DynamicProxyHandler implements InvocationHandler {
          //保证线程安全且符合封装原则,强制在构造器赋值。
          private final Object target;
      
          public DynamicProxyHandler(Object target) {
              this.target = target;
          }
      
          public Object getProxy(){
              // 用于定义代理类的类加载器
              ClassLoader classLoader = target.getClass().getClassLoader();
              // 代理类要实现的一系列接口
              Class<?>[] interfaces = target.getClass().getInterfaces();
              if (interfaces.length == 0) {
                  throw new IllegalArgumentException("目标对象未实现任何接口,无法使用JDK动态代理,请考虑CGLIB");
              }
              return Proxy.newProxyInstance(classLoader, interfaces, 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 after() {
              System.out.println(String.format("log end time [%s]", new Date()));
          }
      
          private void before() {
              System.out.println(String.format("log start time [%s]", new Date()));
          }
      }

      测试

      java 复制代码
      import com.light.dynamicproxy.DynamicProxyHandler;
      import com.light.staticproxy.UserService;
      import org.junit.jupiter.api.Test;
      import com.light.staticproxy.UserServiceImpl;
      import com.light.staticproxy.UserServiceProxyForExtends;
      import com.light.staticproxy.UserServiceProxyForInterface;
      
      import java.lang.reflect.Proxy;
      
      public class TestProxy {
          @Test
          public void testStaticProxy(){
              //基于接口的proxy
              UserServiceProxyForInterface serviceProxy = new UserServiceProxyForInterface(new UserServiceImpl());
              serviceProxy.select();
              serviceProxy.update();
              //基于继承的proxy
              UserServiceProxyForExtends proxyForExtends = new UserServiceProxyForExtends();
              proxyForExtends.select();
              proxyForExtends.update();
          }
      
          @Test
          public void testDynamicProxy()
          {
              //1.创建具体主题类
              UserServiceImpl userServiceImpl = new UserServiceImpl();
              //2.获取对应的invocationHandler(用于处理方法调用的处理器)
              DynamicProxyHandler dynamicProxyHandler = new DynamicProxyHandler(userServiceImpl);
              //3.获取代理类
              UserService proxy = (UserService) dynamicProxyHandler.getProxy();
              proxy.select();
              proxy.update();
          }
      }
    • CGLIB动态代理(通过继承类的方式)

      • maven引入CGLIB包,然后编写一个UserDao类,它没有接口,只有两个方法,select() 和 update()

        java 复制代码
        import java.lang.reflect.Method;
        import java.util.Date;
        
        public class LogInterceptor implements MethodInterceptor {
            /**
             * @param object 表示要进行增强的对象
             * @param method 表示拦截的方法
             * @param objects 数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
             * @param methodProxy 表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
             * @return 执行结果
             * @throws Throwable
             */
            @Override
            public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                before();
                Object result = methodProxy.invokeSuper(object, objects);   // 注意这里是调用 invokeSuper 而不是 invoke,否则死循环,methodProxy.invokesuper执行的是原始类的方法,method.invoke执行的是子类的方法
                after();
                return result;
            }
            private void before() {
                System.out.println(String.format("log start time [%s] ", new Date()));
            }
            private void after() {
                System.out.println(String.format("log end time [%s] ", new Date()));
            }
        }
    • JAVAssist:Javassist(Java编程助手)让Java字节码操作变得简单。它是一个用于在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义新类,并在 JVM 加载类文件时修改类文件。

概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。

AOP的作用:

  • 提供声明式事务管理。
  • 让用户实现自定义切面。

相关术语

横切关注点

分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点,通俗理解为我们要关注的地方。

为啥叫横切?因为程序是竖向执行,这里我们理解为横向切入。竖向执行某段程序的过程中,横向切入一些程序,让功能更加健壮。

通知(增强)

增强,通俗说,就是你想要增强的功能,比如 安全,事物,日志等。

  • 前置通知:在被代理的目标方法前执行某些操作
  • 返回通知:在被代理的目标方法成功结束后执行某些操作
  • 异常通知:在被代理的目标方法异常结束后执行某些操作
  • 后置通知:在被代理的目标方法最终结束后执行某些操作
  • 环绕通知:使用try....catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

切面

封装通知方法的类,代理类。

目标

被代理的目标对象,被通知的对象。

代理

向目标对象应用通知之后创建的代理对象。

连接点

这也是一个纯逻辑概念,不是语法定义的。

把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。通俗说,就是spring允许你使用通知的地方

Spring AOP

Spring AOP 默认使用标准的JDK 动态代理来实现 AOP 代理。这使得任何接口(或一组接口)都可以被代理。如果被代理类没有实现任何接口,Spring AOP 也可以使用 CGLIB 代理。

必须引入的包:

xml 复制代码
<!-- Source: https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.24</version>
</dependency>

springApi接口实现Aop

用户委托和接口类

java 复制代码
package com.light.aop1;

public interface UserService {
    public void add();
    public void update();
    public void delete();
    public void search();
}


package com.light.aop1;

public class UserServiceImpl implements UserService {

    @Override
    public void add() {
        System.out.println("添加了一个用户");
    }

    @Override
    public void update() {
        System.out.println("修改了一个用户");
    }

    @Override
    public void delete() {
        System.out.println("删除了一个用户");
    }

    @Override
    public void search() {
        System.out.println("查看了用户");
    }
}

通知

java 复制代码
package com.light.aop1.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class BeforeLog implements MethodBeforeAdvice {
    /**
     *
     * @param method the method being invoked
     * @param args the arguments to the method
     * @param target the target of the method invocation. May be {@code null}.
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
    }
}


package com.light.aop1.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice
{
    /**
     *
     * @param returnValue the value returned by the method, if any
     * @param method the method being invoked
     * @param args the arguments to the method
     * @param target the target of the method invocation. May be {@code null}.
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了, 返回结果为:"+returnValue);
    }
}

xml配置

xml 复制代码
<?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="userservice" class="com.light.aop1.UserServiceImpl" />
    <bean id="afterlog" class="com.light.aop1.log.AfterLog" />
    <bean id="beforelog" class="com.light.aop1.log.BeforeLog" />
    <!-- 方式1 使用原生spring API接口-->
    <!-- 配置aop,导入aop约束-->
    <aop:config>
        <!--pointcut切入点
        id:唯一标识
        expression:表达式
            execution表达式的完整格式为:execution(修饰符? 返回类型 类名?方法名(参数)),带 ? 的部分可以省略(异常匹配极少使用)
            核心通配符只有三个:
                - `*` 匹配任意一个名称或类型;
                - `..` 在包路径位置表示"包含所有子包"、在参数位置表示"任意个参数";
                - `+` 在类型后面表示"含其子类"
        -->
        <!--expression-->
        <aop:pointcut id="pointcut" expression="execution(* com.light.aop1.UserServiceImpl.*(..))"/>
        <!-- 执行环绕-->
        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut" />
        <aop:advisor advice-ref="beforelog" pointcut-ref="pointcut" />
    </aop:config>
</beans>

execution完整语法模板

execution 表达式由几个固定部分按顺序组成,格式如下:

复制代码
execution(修饰符匹配? 返回类型匹配 声明类型匹配?方法名匹配(参数匹配) 异常匹配?)

带 ? 的部分可以省略(异常匹配极少使用),所以最简形式是 execution(返回类型 方法名(参数))。你选中的这个表达式按位置拆解如下:

组成部分 选中表达式的值 含义
修饰符(可省略) 未写 不限定修饰符
返回类型(必写) * 任意返回类型
声明类型(可省略) com.xyz.service.* service 包下的所有类
方法名(必写) * 任意方法名
参数(必写) (..) 任意个数、任意类型的参数

整句含义就是:匹配 com.xyz.service 包下所有类的所有方法执行。

各部分的写法要点

返回类型只能是 *(任意)或具体类型,如 void、String、java.util.List。

声明类型(类路径)是写法最讲究的部分,关键在于区分 . 和 ..:

  • com.xyz.service.*:只匹配 service 包下一层的类,不含子包
  • com.xyz.service..*:匹配 service 包及其所有子包下的类
  • com.xyz.service.AccountService:精确匹配某一个类
  • com.xyz.service.AccountService+:匹配该类及其子类
  • *..service.*:任意路径下名为 service 的包

方法名支持任意位置通配:* 表示任意方法,get* 表示以 get 开头的方法,*ByName 表示以 ByName 结尾的方法。

参数部分的写法:

  • ():无参数
  • (..):任意个数(0 到多个)、任意类型
  • (*):恰好一个任意类型的参数
  • (String, ..):第一个参数必须是 String,后面任意
  • (String, Integer):精确匹配两个参数的类型

常用示例速查

表达式 匹配范围
execution(public * *(..)) 所有 public 方法
execution(* set*(..)) 所有以 set 开头的方法
execution(* com.xyz.service.AccountService.*(..)) 某个类的所有方法
execution(* com.xyz.service.*.*(..)) service 包下所有类的所有方法
execution(* com.xyz.service..*.*(..)) service 包及子包下所有类的所有方法
execution(String com.xyz.service.*.get*(..)) service 包下返回 String 且以 get 开头的方法
execution(* com.xyz.service.*.*(String, ..)) 第一个参数为 String 的方法

执行测试

java 复制代码
import com.light.aop1.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestProxy {
    @Test
    public void testAop1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserService bean = context.getBean("userservice", UserService.class);
        bean.add();
        bean.update();
    }
}

自定义实现Aop

自定义执行通知

java 复制代码
package com.light.aop.selfdefind;

public class PrintLog {
    void before(){
        System.out.println("=======开始执行前======");
    }
    void after(){
        System.out.println("=======执行完成======");
    }
}

xml配置

xml 复制代码
<?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="userservice" class="com.light.aop.UserServiceImpl" />
    <bean id="log" class="com.light.aop.selfdefind.PrintLog" />
    <aop:config>
        <!-- 自定义切面,ref表示要引用的类-->
        <aop:aspect ref="log">
            <!--切入点-->
            <aop:pointcut id="point" expression="execution(* com.light.aop.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="point" />
            <aop:after method="after" pointcut-ref="point" />
        </aop:aspect>
    </aop:config>
</beans>

注解实现Aop

实现一个切面类

java 复制代码
package com.light.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

//标注这个类是一个切面
@Aspect
public class PointCut {
    @Before("execution(* com.light.annotation.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("开始执行之前");
    }

    @After("execution(* com.light.annotation.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("执行结束");
    }

    @Around("execution(* com.light.annotation.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //获取签名
        Signature signature = jp.getSignature();
        System.out.println("signatrue:" + signature);
        Object proceed = jp.proceed();//执行方法
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}

xml配置

xml 复制代码
<?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="user-service" class="com.light.annotation.UserServiceImpl" />
    <bean id="pointcut" class="com.light.annotation.PointCut" />
    <!--开启注解的支持-->
    <!--proxy-target-class 默认false 使用jdk反射实现注解-->
    <!-- 改成true,使用cglib实现注解-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

测试

java 复制代码
import com.light.annotation.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class TestProxy {
    @Test
    public void testAopForAnnotation(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserService bean = context.getBean("user-service", UserService.class);
        bean.add();
        bean.update();
    }
}
相关推荐
东华果汁哥1 小时前
【Spring AI 实战】从零开始调用大模型接口,构建 Java AI 应用
java·人工智能·spring
java_nnnn6 小时前
Spring MVC小案例(1)
java·后端·spring·mvc
七夜zippoe10 小时前
Spring AI + Ollama 本地模型:数据不出内网的合规方案(落地全流程)
java·人工智能·spring·ai·ollama
captain37610 小时前
Spring Web MVC(2)
java·前端·spring boot·后端·spring·mvc
_Evan_Yao19 小时前
Redis 缓存穿透、击穿、雪崩,一次讲清!
java·spring·mybatis
paopaokaka_luck1 天前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
雷一言说1 天前
Spring AI 2.0 Tool Calling 实战:用一个订单查询跑通整个调用链
spring
xieliyu.1 天前
【JavaEE进阶】Spring Boot 日志:从 System.out 到 @Slf4j,6 个级别 + 4 类配置一篇讲透
java·spring boot·spring·java-ee·springboot·logback·slf4j
IT_Octopus1 天前
Apollo 配置中心入门:一所“小学校“读懂 Portal、Meta Server、Config Service
java·后端·spring