NYJRYv 发表于 2025-2-22 20:40:03

动态代理到AOP

动态代理

代理(proxy)是一种设计模式,通过了目标对象的另外访问方法,即通过代理对象访问目标对象.动态代理是再程序运行时动态地生成一个代理类代替原本的类.该类会拦截对目标对象的方法调用
为什么使用动态代理


[*]动态代理可以帮我们减少许多冗余的代码,当你必须在类中做相同的事时,如日志记录,权限校验.若不想在每个方法里重复写这些代码,动态代理 就是帮你实现这一目标的工具,它通过拦截方法调用,在方法执行前后做一些额外的事情比如记录日志,验证权限
动态代理的实现

动态代理分别分为:JDK动态代理和CGLIB动态代理
JDK动态代理


[*]适用于代理接口类型的对象
//业务接口public interface UserService {    void addUser();}//实现类public class UserServiceImpl implements UserService {    @Override    public void addUser() {      System.out.println("添加用户业务逻辑...");    }}public class LoggingInvocationHandler implements InvocationHandler {    private Object target;    //封装一个绑定方法获取    public Object newProxyInsatance(Object target) {      this.target = target;                                                                //Proxy.newProxyInstance()方法用于在运行时动态生成代理类和实例                                        return Proxy.newProxyInstance(                target.getClass().getClassLoader(),//被代理类类加载器                target.getClass().getInterfaces(),//被代理类需要实现的接口列表                this//方法调用处理器      );    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {      System.out.println("方法执行之前执行" + method.getName());      Object result = method.invoke(target, args);      System.out.println("方法执行之后执行" + method.getName());      return result;    }}public class Test01 {    public static void main(String[] args) {      LoggingInvocationHandler handler = new LoggingInvocationHandler();      UserService userService = new UserServiceImpl();      UserService insatance = (UserService) handler.newProxyInsatance(userService);      insatance.addUser();    }}//output//方法执行之前执行addUser//添加用户业务逻辑...//方法执行之后执行addUser

[*]Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h
)
[*]这是JDK动态代理和核心方法,用于在运行时动态生成代理类和实例,其需要传入三个参数,分别为ClassLoader loader被代理类的类加载器,Class<?>[] interfaces代理类需要实现的接口列表,InvocationHandler h,方法调用处理器
CGLIB动态代理


[*]CGLIB是通过生成目标类的子类来实现代理,Spring在目标类没有实现的接口时,就会采用CGLIB动态代理,CGLIB生成的代理类是目标类的子类,并覆盖目标类方法,因此其能代理没有接口的类
[*]导入CGLIB依赖
<!-- https://mvnrepository.com/artifact/cglib/cglib --><dependency>    <groupId>cglib</groupId>    <artifactId>cglib</artifactId>    <version>3.3.0</version></dependency>
一个没有接口的UserService
public class UserService {    public void addUser() {      System.out.println("添加逻辑...");    }}//实现MethodInterceptor类public class LoggingMethodInterceptor implements MethodInterceptor {    private Object target;    public Object newProxyInstance(Object target) {      this.target = target;      //创建CGLIB字节增强对象,用于生成目标类的子类代理      Enhancer enhancer = new Enhancer();      //设置代理类的父类      enhancer.setSuperclass(target.getClass());      enhancer.setCallback(this);      return enhancer.create();    }    @Override    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {      methodProxy.invokeSuper(o, objects);      return null;    }}public class Test {    public static void main(String[] args) {      UserService userService = new UserService();      LoggingMethodInterceptor interceptor = new LoggingMethodInterceptor();      UserService instance = (UserService) interceptor.newProxyInstance(userService);      instance.addUser();    }}AOP(Aspect Oriented Programming)

动态代理是AOP的底层实现,其作用是把横切关注点(如日志,事务)从核心业务逻辑中分离出,形成一个独立的模块.这就可以做到在不修改源代码的情况下,给程序动态统一地添加功能


[*]作用:减少代码冗余,提高开发效率,易于维护
[*]进一步理解
在Spring中,AOP基于动态代理实现,其会为目标对象创建一个代理对象,在目标方法执行之前,后,或异常时执行你的配置的增强


[*]如图:原有四个接口要验证参数,日志.我们发现这些代码太冗余.比较繁琐.使用切面后将验证参数,日志等功能提取出来,四个接口我们只需要通过配置就可以直接调用对应功能,让我们更加专注于实际业务逻辑


AOP 的关键术语


[*]目标对象(Target):

[*]目标对象指的是业务逻辑的实现类,这些实现类是切面增强的对象.
[*]作用:目标对象是我们通常定义的业务类,切面提供了切点的功能如(日志记录,业务管理等等)
[*]假设有一个UserService业务类,其包含了一个addUser()方法.UserService类就是目标对象,AOP会在addUser()中增强功能

[*]织入(Weaving):

[*]织入是将切面(通知和切入点)用于到目标对象的过程.
[*]作用:织入的过程是把定义好的增强逻辑插入到目标对象中去.这是AOP实现的关键步骤,Spring AOP通过动态代理实现

[*]切面(Aspect):

[*]切面是对横切关注点的模块化,其包含了切入点和通知,切面提取出业务中一些相同的功能进行统一管理
[*]作用:切面将横切关注点上的功能封装在一起,方便重用.一个切面可以有多个增强方法(通知),这些通知可以应用到目标对象的多个方法上
[*]例如一个LogAspect就可以包含多个通知,用于不同的方法前执行插入日志记录功能等等

[*]通知(Advice):

[*]通知是指在切点执行时,执行增强的代码,通知可以在方法执行的不同阶段执行,包括方法执行前,执行后,围绕方法执行的前后
[*]常见分为

[*]前置通知(Before):在目标方法执行之前执行
[*]后置通知(After):在目标方法执行之后执行,无论是否抛出异常
[*]后置返回通知(AfterReturning):在目标方法执行之后执行,有异常时不执行
[*]后置异常通知(AfterThrowing):在目标方法抛出异常时执行
[*]环绕通知(Around):最为常用的通知,在目标方法执行前后执行


[*]切入点(PointCut):

[*]切入点指通知应用的具体位置条件,其决定了哪些连接点可以被增强,通常使用切入点达式来定义
[*]作用:切入点是AOP的核心,其决定了哪些方法可以被增强.通过切入点表达式,开发者可以灵活选择要增强的方法
[*]通过execution(* com.company.service.UserService.*(..))切入点表达式,可以表示UserService类中的所有方法都被增强,下面的内容会展开讲解

[*]连接点(JoinPoint):

[*]连接点是程序执行的一个具体位置,其指的是可以被切面拦截的方法.连接点是AOP的关键概念.所有的通知都会在某个连接点处执行.在Spring AOP中通常指的是方法调用
[*]作用:连接点定义了切面可以织入的程序位置,通常是目标方法执行的位置.在 Spring AOP中,连接点仅限于方法的执行.

AOP的使用

导入依赖


[*]对于普通的Spring项目导入的AOP依赖
<dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId>    <version>1.9.6</version></dependency>

[*]对于SpringBoot项目导入的AOP依赖
   <dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-aop</artifactId>    </dependency>AOP的实现分为XML文件配置和注解配置,XML文件配置都是老掉骨头的东西我们就不学了,注解配置避免了繁琐的XM 配置,使得开发更加灵活,易于理解.我们着重对注解配置进行掌握即可
使用注解实现AOP


[*]配置类

[*]配置类是用于启动Spring AOP的注解和扫描指定位置的包.我们创建一个AppConfig类,使用@Comfiguration,@ComponentScan和EnableAspectAutoProxy来启动Spring AOP
@Configuration@ComponentScan({"com.mashang.service", "com.mashang.aspect"})@EnableAspectJAutoProxypublic class APPConfig {}

[*]@Comfiguration:指明当前类为一个配置类,类似于XML配置文件
[*]@EnableAspectAutoProxy:启用Spring AOP注解支持,允许在上下文中应用@Aspect注解标记切面
[*]@ComponentScan({"com.mashang.service", "com.mashang.aspect"}):扫描指定包中的组件

[*]切面类

[*]接着创建切面类,使用@Aspect注解来标记该类是切面类,切面类中可以通过注解标记不同的增强方法(如@Before,@After,@Around)
@Component//配置切面类得注解@Aspect@Order(1)public class LogAspect {    //定义切点,拦截service包下所有方法    @Pointcut("execution(* com.mashang.service.*.*(..))")    public void serviceMethod() {    }    //前置通知    @Before("serviceMethod()")    public void before() {      System.out.println("[前置通知] 方法执行");    }    //环绕通知,通常用于记录性能耗时    @Around("serviceMethod()")    public Object around(ProceedingJoinPoint pjp) throws Throwable {      Long startTime = System.currentTimeMillis();      for (int i = 0; i < 100000; i++) {      }      System.out.println("[环绕通知]开始执行方法 " + pjp.getSignature().getName());      Object proceed = pjp.proceed();      Long endTime = System.currentTimeMillis();      System.out.println("方法耗时: " + (endTime - startTime) + "ms");      return proceed;    }                    //后置通知    @After("serviceMethod()")    public void after() {      System.out.println("[后置通知] 方法执行");    }    //异常通知    @AfterThrowing(pointcut = "serviceMethod()", throwing = "e")    public void afterThrowing(Exception e) {      System.out.println("[异常通知]" + e.getMessage());    }}

[*]@Aspect表示该类为切面类
[*]@Order(int):控制切面类的顺序
[*]@Component标记交为Spring容器管理,允许注入容器中
[*]@Before,@After,@Around,@AroundThrowing表明不同类型的通知
[*]切入点表达式

[*]切入点表达式是AOP的核心语法,用于定义哪些方法需要被增强,与@PointCut注解一同使用
[*]核心语法结构
execution([修饰符] 返回类型 [包名.类名.方法名(参数列表) ])

[*]可以使用通配符

[*]*表示匹配任意字符除了.
[*]..表示任意子包或任意参数

[*]如匹配com.mashang.service下包的所有方法
@PointCut("excution(* com.mashang.service.*.*(..))")


[*]测试
public class Test {    public static void main(String[] args) {      //加载Spring配置      AnnotationConfigApplicationContext cxt                = new AnnotationConfigApplicationContext(APPConfig.class);      //获取Userservice实例      UserService userService = cxt.getBean(UserService.class);      System.out.println("=======测试正常方法=========");      userService.getUser("张三");      System.out.println("=======测试异常方法=========");      try {            userService.throwException();      } catch (Exception e) {            e.printStackTrace();      }      cxt.close();    }}
页: [1]
查看完整版本: 动态代理到AOP