代理详解之静态代理、动态代理、SpringAOP实现

news2024/10/2 10:31:33

1、代理介绍

代理是指一个对象A通过持有另一个对象B,可以具有B同样的行为的模式。为了对外开放协议,B往往实现了一个接口,A也会去实现接口。但是B是“真正”实现类,A则比较“虚”,他借用了B的方法去实现接口的方法。A虽然是“伪军”,但它可以增强B,在调用B的方法前后都做些其他的事情。Spring AOP就是使用了动态代理完成了代码的动态“织入”。

使用代理好处还不止这些,一个工程如果依赖另一个工程给的接口,但是另一个工程的接口不稳定,经常变更协议,就可以使用一个代理,接口变更时,只需要修改代理,不需要一一修改业务代码。从这个意义上说,所有调外界的接口,我们都可以这么做,不让外界的代码对我们的代码有侵入,这叫防御式编程。代理其他的应用可能还有很多。

上述例子中,类A写死持有B,就是B的静态代理。如果A代理的对象是不确定的,就是动态代理。动态代理目前有两种常见的实现,jdk动态代理和cglib动态代理。

2、静态代理

静态代理是一种设计模式,属于代理模式的一种。在静态代理中,代理类在程序运行前就已经被定义,并且在编译时就确定了代理类和被代理类的关系。这意味着代理类和被代理类都实现了相同的接口或继承了相同的父类,代理类内部持有一个被代理类的实例,并在自己的方法中调用被代理类的方法,同时可以在调用前后添加一些自己的操作,例如日志记录、权限检查、事务处理等。

静态代理有三个组成部分:抽象接口、代理类、被代理类,其实现例子如下:

1)定义抽象接口

public interface TargetInteface {
    void method1();
    void method2();
    int method3(Integer i);
}

2)定义代理类

public class TargetProxy implements TargetInteface {

    private Target target =new Target();
    @Override
    public void method1() {
        System.out.println("执行方法前...");
        target.method1();
        System.out.println("执行方法后...");
    }

    @Override
    public void method2() {
        System.out.println("执行方法前...");
        target.method2();
        System.out.println("执行方法后...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("执行方法前...");
        int method3 = target.method3(i);
        System.out.println("执行方法后...");
        return method3;
    }
}

3)定义被代理类

public class Target implements TargetInteface {
    @Override
    public void method1() {
        System.out.println(" Target method1 running ...");
    }

    @Override
    public void method2() {
        System.out.println("Target method2 running ...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("Target  method3 running ...");
        return i;
    }
}

4)定义客户端,查看执行结果

public class TargetUser {

    public static void main(String[] args) {
        TargetInteface target = new TargetProxy();
        target.method1();
        System.out.println("-----------------------------");
        target.method2();
        System.out.println("-----------------------------");
        System.out.println(target.method3(3));
    }
}

结果输出:

执行方法前...
 Target method1 running ...
执行方法后...
-----------------------------
执行方法前...
Target method2 running ...
执行方法后...
-----------------------------
执行方法前...
Target  method3 running ...
执行方法后...
3

从静态代理的实现不难看出,静态代理的优点是实现简单,易于理解。但其缺点也很明显,即每当需要为一个新的类添加代理功能时,都需要手动创建一个新的代理类,这会导致类的数量急剧增加,维护成本也随之提高。同时,代理类与被代理类之间的耦合程度太高 ,当被代理类中增加、删除、修改方法后,那么代理类中的也必须增加、删除、修改相应的方法,提高了代码的维护成本。另一个问题就是当代理对象代理多个target接口的实现类时,多个实现类中必然出在不同的方法,由于代理对象要实现与目标对象一致的接口(其实是包含关系),必然需要编写众多的方法,极其容易造成臃肿且难以维护的代码。

3、动态代理

动态代理的核心思想是在不修改原始对象代码的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

动态代理的实现原理主要基于Java的反射机制。当使用动态代理时,需要定义一个接口或者一组接口,这些接口定义了被代理类(被代理对象)的行为。然后,需要编写一个实现了InvocationHandler接口的类,这个类中包含了在代理对象的方法调用前后执行的逻辑。当调用Proxy.newProxyInstance()方法时,传入接口的类加载器、接口数组和InvocationHandler对象,Java将会在运行时动态地生成一个实现了指定接口的代理类,并将方法调用委托给InvocationHandler对象来处理。当调用代理对象的方法时,实际上是调用了InvocationHandler接口的invoke()方法,在该方法中可以根据方法的名称、参数等信息执行一些预处理逻辑,然后再通过反射调用被代理对象的对应方法。

接下来介绍两种动态代理:JDK Proxy和CGLib

1)JDK Proxy

① JDK Proxy的内部机制

JDK Proxy通过Java的反射机制来动态生成代理类。具体来说,Proxy类会利用ProxyGenerator类(虽然这个类不是公开的API,但它是JDK内部实现动态代理的关键)来生成代理类的字节码,并将其加载到JVM中。生成的代理类会继承自java.lang.reflect.Proxy类,并实现指定的接口。在代理类的方法中,会调用InvocationHandlerinvoke方法,将方法调用转发给处理器处理。

此外,为了提高性能,JDK Proxy还提供了一个缓存机制,用于缓存已经生成的代理类的Class对象。这样,当需要创建相同类型的代理对象时,可以直接从缓存中获取代理类的Class对象,而无需重新生成。缓存是通过WeakCache类实现的,它利用弱引用来缓存对象,以便在JVM进行垃圾回收时能够自动清理不再使用的缓存项。

② JDK Proxy的实现步骤

  • 定义接口和被代理类:首先定义一个或多个接口,这些接口将被代理类实现。
public interface TargetInteface {
    void method1();
    void method2();
    int method3(Integer i);
}
public class Target implements TargetInteface {
    @Override
    public void method1() {
        System.out.println("method1 running ...");
    }

    @Override
    public void method2() {
        System.out.println("method2 running ...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}
  • 创建InvocationHandler:实现InvocationHandler接口,并重写invoke方法。在invoke方法中,可以添加自定义的逻辑,如日志记录、权限检查等,并通过反射调用原始类的方法。
  • 生成代理对象:调用Proxy.newProxyInstance方法,传入类加载器、接口数组以及InvocationHandler实例,来动态生成代理对象。该方法会返回一个实现了指定接口的代理类实例。
public class TargetProxy {
    public static  <T> Object getTarget(T t) {
        //新构建了一个 新的 代理类的对象
        return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
                        //比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
                        System.out.println("执行方法前...");
                        Object invoke = method.invoke(t, args);
                        System.out.println("执行方法后...");
                        return invoke;
            }
        });
    }
}
  • 使用代理对象:通过代理对象调用方法时,实际上是调用了InvocationHandlerinvoke方法,在该方法中执行自定义逻辑后,再调用原始类的方法。
public class TargetUser {

    public static void main(String[] args) {
        TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
        target.method1();
        System.out.println("-----------------------------");
        target.method2();
        System.out.println("-----------------------------");
        System.out.println(target.method3(3));
    }
}

结果输出:
 执行方法前...
method1 running ...
执行方法后...
-----------------------------
执行方法前...
method2 running ...
执行方法后...
-----------------------------
执行方法前...
method3 running ...
执行方法后...
3

③ JDK Proxy的特点

  1. 接口代理:JDK Proxy只能代理实现了接口的类,不能代理没有实现接口的普通类。
  2. 动态生成:代理类是在运行时动态生成的,开发者无需手动编写代理类的代码。
  3. 灵活性强:可以在不修改原始类代码的情况下,为原始类添加额外的功能或逻辑。
  4. 性能考虑:由于涉及到反射和动态类的生成,JDK Proxy的性能可能略低于静态代理或直接调用原始类的方法。

2)CGLib

① CGLib动态代理的核心原理

  1. 字节码操作:CGLib底层使用ASM(一个小而快的字节码操作框架)来动态生成新的Java类(通常是目标类的子类)。这些新生成的类继承自目标类,并在方法调用时插入代理逻辑。
  2. 方法拦截:CGLib的核心功能是实现方法级别的拦截。开发者通过实现MethodInterceptor接口来定义一个方法拦截器,该拦截器会在代理对象的方法调用前后执行自定义逻辑,如预处理、后处理、异常处理等。
  3. FastClass机制:为了提高性能,CGLib采用了FastClass机制。FastClass通过对目标类的方法进行索引,并在调用时直接通过索引来访问目标方法,这种方式比Java反射要快得多

②  CGLib动态代理的实现步骤

  • 引入CGLib依赖:在项目中引入CGLib的Maven或Gradle依赖。
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
  • 定义目标类:定义需要被代理的目标类。
public class Target {
    public void method1() {
        System.out.println("method1 running ...");
}

    public void method2() {
        System.out.println("method2 running ...");
    }

    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}
  • 实现MethodInterceptor接口:创建一个实现了MethodInterceptor接口的类,并重写intercept方法。在该方法中编写代理逻辑。
  • 创建代理对象:使用CGLib提供的Enhancer类来创建代理对象。需要设置被代理的类(通过setSuperclass方法)和回调(通过setCallback方法设置MethodInterceptor实现类)。
public class TargetProxy {
    public static <T> Object getProxy(T t) {
        Enhancer en = new Enhancer(); //帮我们生成代理对象
        en.setSuperclass(t.getClass());//设置要代理的目标类
        en.setCallback(new MethodInterceptor() {//代理要做什么
            @Override
            public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("执行方法前。。。");
                //调用原有方法  
                Object invoke = methodProxy.invokeSuper(object, args);
                // Object invoke = method.invoke(t, args);// 作用等同与上面。
                System.out.println("执行方法后。。。");
                return invoke;
            }
        });
        return en.create();
    }
}
  • 使用代理对象:通过代理对象调用目标类的方法时,会触发intercept方法中的代理逻辑。
public class TargetUser {

    public static void main(String[] args) {
        Target target = (Target) TargetProxy.getProxy(new Target());
        System.out.println(target.getClass().getName());
        target.method1();
    }

}

结果输出:

com.heaboy.aopdemo.cglibproxy.Target$$EnhancerByCGLIB$$f9f41fb8
执行方法前。。。
method1 running ...
执行方法后。。。 

③ CGLib动态代理的适用场景

  1. 需要代理未实现接口的类:当目标类没有实现任何接口时,可以使用CGLib进行代理。
  2. 性能要求较高:在性能要求较高的场景下,如果JDK动态代理无法满足需求,可以考虑使用CGLib。
  3. AOP框架实现:在面向切面编程框架中,如Spring AOP,当需要代理未实现接口的类时,通常会使用CGLib作为底层实现。

 ④ CGLib动态代理的优缺点

优点:

  1. 灵活性高:可以代理没有实现接口的类,拓宽了代理的适用范围。
  2. 性能较好:通过FastClass机制,调用效率高于JDK动态代理的反射机制。
  3. 功能强大:支持在运行时动态地为目标类添加额外功能或逻辑,无需修改原始类代码。

缺点:

  1. 字节码操作开销:动态生成字节码并加载到JVM中会带来一定的性能开销。
  2. 无法代理final类和方法:由于CGLib是通过继承目标类来实现代理的,因此无法代理final修饰的类和方法。
  3. 使用复杂度较高:相比于JDK动态代理,CGLib的使用复杂度较高,需要引入额外的依赖并处理字节码生成的问题。

3)JDK Proxy VS CGLib 

代理对象类型:JDK Proxy只能代理实现了接口的类;而CGLib可以直接代理普通类。

性能:CGLib在运行时生成代理类的子类,通常认为其性能略优于JDK Proxy。但在大多数场景下,这种性能差异不大。

使用场景:如果目标对象已经实现了接口,使用JDK Proxy是一个简单直接的选择。如果需要代理没有实现接口的类,则必须使用CGLib。

依赖:JDK Proxy无需额外依赖,因为它是Java核心库的一部分;而CGLib需要添加CGLib库作为项目依赖。

JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;

Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;

JDK Proxy 是通过拦截器加反射的方式实现的;

JDK Proxy 只能代理继承接口的类;

JDK Proxy 实现和调用起来比较简单;

CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;

CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

4、静态代理 VS 动态代理

区别

静态代理: 静态代理是在编译期间就已经确定的,需要为每个被代理的类编写一个代理类,代理类和被代理类实现相同的接口或继承相同的父类。

静态代理的代理类在编译时就存在,所以在程序运行时只能代理特定的类,无法动态地决定代理哪些类。

静态代理对原始对象的方法调用进行了包装,可以在调用前后添加额外的逻辑,但代理类需要提前编写,会增加代码的量。

静态代理在代码中显式指定代理对象,使用起来相对直观,但增加新的代理类需要重新编译。

动态代理: 动态代理是在运行时创建代理对象,无需提前编写代理类。使用Java的反射机制来动态生成代理类和代理对象。

动态代理基于接口进行代理,通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。

动态代理可以代理多个接口的类,并动态决定代理哪些类。在运行时,可以根据需要为不同的对象生成代理,更具灵活性。

动态代理不需要为每个被代理类编写特定的代理类,更加灵活和节省代码量。

动态代理在代理对象的方法调用前后可以添加自定义的逻辑,例如日志记录、事务管理等。 动态代理的缺点是相对于静态代理而言,运行时生成代理对象需要一定的性能开销。

适用场景

静态代理适合以下场景:

当目标对象(被代理对象)数量有限且确定时,可以通过手动编写代理类来实现静态代理。静态代理在编译时就创建了代理类,因此在运行时性能较好。

静态代理对目标对象进行了封装,在不修改原有代码的情况下增加了额外的功能。这使得静态代理常被用于日志记录、事务管理等横切关注点。

动态代理适合以下场景:

当目标对象数量不确定或者无法提前确定时,动态代理可以更方便地生成代理对象。它在运行时生成代理类和代理对象,避免了手动编写多个代理类的繁琐工作。

动态代理可以灵活地在运行时为目标对象添加、删除或更改代理行为。这使得动态代理常被用于AOP(面向切面编程)、RPC(远程过程调用)等应用场景。

需要注意的是,由于动态代理在运行时通过反射机制创建代理类和代理对象,因此相比静态代理,其性能可能略低。此外,动态代理只能代理实现了接口的目标对象,而静态代理没有这个限制。

总结起来,静态代理适用于目标对象数量有限且确定、需要封装和增加额外功能的场景;而动态代理适用于目标对象数量不确定或无法提前确定、需要灵活添加、删除或更改代理行为的场景。根据具体需求和情况,选择合适的代理方式。

5、SpringAOP中的代理实现

1)SpringAOP介绍

谈谈对AOP的理解

Spring AOP是Spring框架中的一个重要模块,用于实现面向切面编程。

面对切面编程,这是一种编程模式,他允许程序员通过自定义的横切点进行模块化,将那些影响多个类的行为封装到课重用的模块中。例子:比如日志输出,不使用AOP的话就需要把日志的输出语句放在所有类中,方法中,但是有了AOP就可以把日志输出语句封装一个可重用模块,在以声明的方式将他们放在类中,每次使用类就自动完成了日志输出。

在面向切面编程的思想里面,把功能分为两种

  • 核心业务:登陆、注册、增、删、改、查、都叫核心业务

  • 周边功能:日志、事务管理这些次要的为周边业务

在面向切面编程中,核心业务功能和周边功能是分别独立进行开发,两者不是耦合的,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。

AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码降低模块间的耦合度,并有利于未来的可拓展性和可维护性

在 AOP 中有以下几个概念:

  • AspectJ:切面,只是一个概念,没有具体的接口或类与之对应,是 Join point,Advice 和 Pointcut 的一个统称。

  • Join point:连接点,指程序执行过程中的一个点,例如方法调用、异常处理等。在 Spring AOP 中,仅支持方法级别的连接点。

  • Advice:通知,即我们定义的一个切面中的横切逻辑,有“around”,“before”和“after”三种类型。在很多的 AOP 实现框架中,Advice 通常作为一个拦截器,也可以包含许多个拦截器作为一条链路围绕着 Join point 进行处理。

  • Pointcut:切点,用于匹配连接点,一个 AspectJ 中包含哪些 Join point 需要由 Pointcut 进行筛选。

  • Introduction:引介,让一个切面可以声明被通知的对象实现任何他们没有真正实现的额外的接口。例如可以让一个代理对象代理两个目标类。

  • Weaving:织入,在有了连接点、切点、通知以及切面,如何将它们应用到程序中呢?没错,就是织入,在切点的引导下,将通知逻辑插入到目标方法上,使得我们的通知逻辑在方法调用时得以执行。

  • AOP proxy:AOP 代理,指在 AOP 实现框架中实现切面协议的对象。在 Spring AOP 中有两种代理,分别是 JDK 动态代理和 CGLIB 动态代理。

  • Target object:目标对象,就是被代理的对象。

Spring AOP 是基于 JDK 动态代理和 Cglib 提升实现的,两种代理方式都属于运行时的一个方式,所以它没有编译时的一个处理,那么因此 Spring 是通过 Java 代码实现的。

AOP解决了什么问题

一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等),这些行为通常被称为 横切关注点(cross-cutting concerns) 。如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。

AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从 核心业务逻辑(core concerns,核心关注点) 中分离出来,实现关注点的分离。

AOP的应用场景

  • 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。

  • 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。

  • 事务管理:@Transactional 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。

  • 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize 注解一行代码即可自定义权限校验。

  • 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。

  • 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。

AOP的实现方式

AOP 的常见实现方式有动态代理、字节码操作等方式。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

2)基于JDK Proxy动态代理实现SpringAOP

① 配置SpringAOP

spring-aop.xml配置文件中配置相关的bean和切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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">

    <bean id="target" class="com.xxhh.aopdemo.aop.Target"/>

    <bean id="targetAdvice" class="com.xxhh.aopdemo.aop.TargetAdvice"/>

    <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="target"/> <!--被代理的类-->
        <property name="interceptorNames" value="targetAdvice"/>  <!--如果用多种增强方式,value的值使用逗号(,)分割-->
        <property name="proxyTargetClass" value="false"/> <!--如果设置为true,则创建基于类的代理(使用CGLIB);如果设置为false,则创建基于接口的代理(使用JDK动态代理)。-->
        <property name="interfaces" value="com.xxhh.aopdemo.aop.TargetInteface"/>  <!--target实现的接口-->
    </bean>
</beans>

② 定义抽象接口

public interface TargetInteface {
    void method1();
    void method2();
    int method3(Integer i);
}

③ 定义被代理类

public class Target implements TargetInteface{

    /*
    * 需要增强的方法,连接点JoinPoint
    **/
    @Override
    public void method1() {
        System.out.println("method1 running ...");
    }

    @Override
    public void method2() {
        System.out.println("method2 running ...");
    }

    @Override
    public int method3(Integer i) {
        System.out.println("method3 running ...");
        return i;
    }
}

④ 定义代理类(增强方法) 

public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {

    /*
    * 通知/增强
    **/
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("前置环绕通知");
        Object proceed = methodInvocation.proceed();
        System.out.println("后置环绕通知");
        return proceed;
    }

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("后置返回通知");
    }

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("前置通知");
    }
}

⑤ 测试

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
        TargetInteface targetProxy = (TargetInteface) appCtx.getBean("targetProxy");
        targetProxy.method1();
    }
}

输出结果:

前置环绕通知
前置通知
method1 running ...
后置返回通知
后置环绕通知 

3)基于CGLib动态代理实现SpringAOP

① 配置SpringAOP

spring-confaop.xml配置文件中配置相关的bean和切面

<?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: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">

    <!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
    <!--扫包-->
    <context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/>

</beans>

② 定义被代理类

/*
* 目标类
**/
public class Target {

    public void method1() {
        System.out.println("method1 running ...");
    }

    public void method2() {
        System.out.println("method2 running ...");
    }

    /*
    * 连接点JoinPoint
    **/
    public int method3(Integer i) {
        System.out.println("method3 running ...");
//        int i1 = 1 / i;
        return i;
    }
}

③ 定义代理类(切面类)

import org.aspectj.lang.ProceedingJoinPoint;
/*
* 切面类
**/
public class TargetAspect {

    /*
    * 前置通知
    **/
    public void before() {
        System.out.println("conf前置通知");
    }

    public void after() {
        System.out.println("conf后置通知");
    }

    public void afterReturning() {
        System.out.println("conf后置返回通知");
    }

    public void afterThrowing(Exception ex) throws Exception {
//        System.out.println("conf异常通知");
//        System.out.println(ex.getMessage());
    }

    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        if (!"".equals("admin")) {
            System.out.println("conf环绕前置");
            proceed = pjp.proceed(pjp.getArgs());
            System.out.println("conf环绕后置");
        }
        return proceed;
    }
}

④ 测试

public class AopTest {
    public static void main(String[] args) {
        ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-confaop.xml");
        Target targetProxy = (Target) appCtx.getBean("target");
        System.out.println(targetProxy.method3(0));
    }
}

输出结果:

conf前置通知
conf环绕前置
method3 running ...
conf后置返回通知
conf环绕后置
conf后置通知

4)基于注解动态代理实现SpringAOP

① 配置SpringAOP

<?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: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">

    <!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
    <!--扫包-->
    <context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/>

</beans>

② 定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation{
}

③ 定义切面类

/*
* 切面类
**/
@Aspect
@Component
public class AnnotationAspect {

    // 定义一个切点:所有被RequestMapping注解修饰的方法会织入advice
    @Pointcut("@annotation(TestAnnotation)")
    private void advicePointcut() {}

    /*
    * 前置通知
    **/
    @Before("advicePointcut()")
    public void before() {
        System.out.println("annotation前置通知");
    }

    @After("advicePointcut()")
    public void after() {
        System.out.println("annotation后置通知");
    }

    @AfterReturning(pointcut = "advicePointcut()")
    public void afterReturning() {
        System.out.println("annotation后置返回通知");
    }

    @AfterThrowing(pointcut = "advicePointcut()", throwing = "ex")
    public void afterThrowing(Exception ex) throws Exception {
        System.out.println("annotation异常通知");
        System.out.println(ex.getMessage());
    }

    @Around("advicePointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed = null;
        if (!"".equals("admin")) {
            System.out.println("annotation环绕前置");
            proceed = pjp.proceed(pjp.getArgs());
            System.out.println("annotation环绕后置");
        }
        return proceed;
    }
}

④ controller添加注解

@Controller
public class TestController {

    @RequestMapping("/test.do")
    @ResponseBody
    public String testController() {
        TestController o = (TestController) AopContext.currentProxy();
        o.test();
//        System.out.println("tewt");
        return "ok";
    }

    @TestAnnotation
    public void test() {
        System.out.println("test running");
    }

}

⑤ 测试

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1912666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大小端详解

引例 我们知道整形(int)是4个字节&#xff0c;例如随便举个例子&#xff1a;0x01020304&#xff0c;它一共占了四个地址位&#xff0c;01,02,03,04分别占了一个字节&#xff08;一个字节就对应了一个地址&#xff09;。 那么就会有个问题&#xff1a;我们的01到底是存储在高地…

Apache Flink核心特性应用场景

Flink的定义 Apache Flink是一个分布式处理引擎&#xff0c;用于处理 无边界数据流&#xff0c; 有边界数据流上金秀贤有状态的计算。Flink能在所有常见的集群环境中运行&#xff0c;并能以内存速度和任意规模进行计算如下Flink官网的一张图 Flink 与Spark的区别 Flink 中处…

Global Mapper:地理信息的温柔探索

引言 在这纷繁复杂的世界里&#xff0c;地理信息系统&#xff08;GIS&#xff09;如同一把利器&#xff0c;帮助我们剖析、理解和改造这个世界。而在众多GIS软件中&#xff0c;Global Mapper无疑是其中的佼佼者。作为一款功能全面且易于使用的GIS应用程序&#xff0c;Global M…

【Python基础】代码如何打包成exe可执行文件

本文收录于 《一起学Python趣味编程》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、安装PyInstaller三、使用PyInstaller打包四、验证打包是否成功五、总结 一、前言 本文介绍如何…

Sharding-JDBC分库分表之SpringBoot主从配置

Sharding-JDBC系列 1、Sharding-JDBC分库分表的基本使用 2、Sharding-JDBC分库分表之SpringBoot分片策略 3、Sharding-JDBC分库分表之SpringBoot主从配置 前言 在开发中&#xff0c;如果对数据库的读和写都在一个数据服务器中操作&#xff0c;面对日益增加的访问量&#x…

在Windows中使用开源高性能编辑器Zed(持续更新)

简介 “Zed is a high-performance, multiplayer code editor from the creators of Atom and Tree-sitter. It’s also open source.” “Zed是一款高性能的支持多人协作的代码编辑器&#xff0c;由Atom和Tree-sitter的创建者开发。它也是开源的。” Zed主打“高性能”&…

4. kvm存储虚拟化

kvm存储虚拟化 一、命令行工具管理虚拟磁盘1、查看虚拟磁盘2、添加磁盘3、删除磁盘 二、qcow2格式的磁盘文件1、创建磁盘文件2、差量镜像/快速创建虚机2.1 创建差量镜像2.2 准备配置文件2.3 创建虚拟机2.4 批量部署虚拟机 三、存储池 storage pool1、类型2、在线迁移2.1 规划后…

CentOS6用文件配置IP模板

CentOS6用文件配置IP模板 到 CentOS6.9 , 默认还不能用 systemctl , 能用 service chkconfig sshd on 对应 systemctl enable sshd 启用,开机启动该服务 ### chkconfig sshd on 对应 systemctl enable sshd 启用,开机启动该服务 sudo chkconfig sshd onservice sshd start …

LLM应用构建前的非结构化数据处理(三)文档表格的提取

1.学习内容 本节次学习内容来自于吴恩达老师的Preprocessing Unstructured Data for LLM Applications课程&#xff0c;因涉及到非结构化数据的相关处理&#xff0c;遂做学习整理。 本节主要学习pdf中的表格数据处理 2.环境准备 和之前一样&#xff0c;可以参考LLM应用构建前…

墨西哥:海外新闻稿媒体分发-海外pr发稿干货分享-大舍传媒

大舍传媒&#xff1a;海外新闻稿媒体分发平台 墨西哥观查者 (mexicoviewer) 墨西哥观查者是墨西哥一家知名的新闻媒体平台&#xff0c;该平台专注于报道墨西哥国内外的时事新闻、政治、经济、文化等多个领域的内容。其更新速度快&#xff0c;报道对象广泛&#xff0c;深受墨西…

WindowsMac共享文件夹设置

共享文件夹设置 共享文件夹设置Windows系统设置步骤一&#xff1a;设置共享文件夹步骤二: 访问共享文件夹 Mac系统中设置共享文件夹步骤一&#xff1a;设置共享文件夹步骤二&#xff1a;访问共享文件夹 小贴士结论 共享文件夹设置 有时需要在多台电脑之间共享文件夹&#xff0…

14-58 剑和诗人32 - 使用矢量数据库增强 LLM 应用程序

GPT-4、Bloom、LaMDA 等大型语言模型 (LLM) 在生成类似人类的文本方面表现出了令人印象深刻的能力。然而,它们在事实准确性和推理能力等方面仍然面临限制。这是因为,虽然它们的基础是从大量文本数据中提取统计模式,但它们缺乏结构化的知识源来为其输出提供依据。 最近,我们…

生产者消费者模型和线程同步问题

文章目录 线程同步概念生产者消费者模型条件变量使用条件变量唤醒条件变量 阻塞队列 线程同步概念 互斥能保证安全,但是仅有安全不够,同步可以更高效的使用资源 生产者消费者模型 下面就基于生产者消费者来深入线程同步等概念: 如何理解生产消费者模型: 以函数调用为例: 两…

VBA实现Excel数据排序功能

前言 本节会介绍使用VBA如何实现Excel工作表中数据的排序功能。 本节会通过下表数据内容为例进行实操&#xff1a; 1. Sort 单列排序 语法&#xff1a;Sort key1,Order1 说明&#xff1a; Key1&#xff1a;表示需要按照哪列进行排序 Order1&#xff1a;用来指定是升序xlAsce…

AI提示词:AI辅导「数学作业」

辅导孩子作业对许多家长来说可能是一件头疼的事&#xff0c;但这部分工作可以在一定程度上交给AI来完成。 打开ChatGPT4,输入以下内容&#xff1a; # Role 数学辅导专家## Profile - author: 姜小尘 - version: 02 - LLM: Kimi - language: 中文 - description: 专门为小学生…

53-4 内网代理6 - frp搭建三层代理

前提:53-3 内网代理5 - frp搭建二级代理-CSDN博客 三级网络代理 在办公区入侵后,发现需要进一步渗透核心区网络(192.168.60.0/24),并登录域控制器的远程桌面。使用FRP在EDMZ区、办公区与核心区之间建立三级网络的SOCKS5代理,以便访问核心区的域控制器。 VPS上的FRP服…

力扣hot100 -- 动态规划(上)

目录 ❄技巧 &#x1f33c;爬楼梯 &#x1f354;杨辉三角 &#x1f30a;打家劫舍 &#x1f40e;完全平方数 &#x1f33c;零钱兑换 &#x1f33c;单词拆分 ❄技巧 动态规划dp-CSDN博客 &#x1f446;花 5 分钟快速刷一遍 花 10 分钟浏览一下 线性DP 背包DP&#x1f447…

算法金 | 12 个最佳 Python 代码片段,帮我完成工作自动化,香~

​大侠幸会幸会&#xff0c;我是日更万日 算法金&#xff1b;0 基础跨行转算法&#xff0c;国内外多个算法比赛 Top&#xff1b;放弃 BAT Offer&#xff0c;成功上岸 AI 研究院 Leader&#xff1b; Python是一种多功能的编程语言&#xff0c;它提供了各种功能和库来有效地自动化…

STM32F103RB多通道ADC转换功能实现(DMA)

目录 概述 1 硬件 1.1 硬件实物介绍 1.2 nucleo-f103rb 1.3 软件版本 2 软件实现 2.1 STM32Cube配置参数 2.2 项目代码 3 功能代码实现 3.1 ADC功能函数 3.2 函数调用 4 测试 4.1 DMA配置data width&#xff1a;byte 4.2 DMA配置data width&#xff1a;Half wor…

Qt常用基础控件总结—输入部件(QComboBox类和QLineEdit)

输入部件 下拉列表控件QComboBox 类 QComboBox 类是 QWidget 类的直接子类,该类实现了一个下拉列表(组合框)。 QComboBox 类中的属性函数 1)count:const int 访问函数:int count() const; 获取组合框中的项目数量,默认情况下,对于空组合框或未设置当前项目的组合框,…