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

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.mfbz.cn/a/788180.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

救生拉网的使用方法及注意事项_鼎跃安全

水域救援在夏季尤为重要&#xff0c;随着气温的升高&#xff0c;人们更倾向于参与水上活动&#xff0c;如游泳、划船、垂钓等&#xff0c;这些活动虽然带来了乐趣和清凉&#xff0c;但同时也增加了水域安全事故的风险。救生拉网作为水域安全的重要工具之一&#xff0c;其重要性…

ProFuzzBench入门教学——使用(Ubuntu22.04)

ProFuzzBench是网络协议状态模糊测试的基准测试。它包括一套用于流行协议&#xff08;例如 TLS、SSH、SMTP、FTP、SIP&#xff09;的代表性开源网络服务器&#xff0c;以及用于自动执行实验的工具。详细参考&#xff1a;阅读笔记——《ProFuzzBench: A Benchmark for Stateful …

Thinking--在应用中添加动态水印,且不可删除

Thinking系列&#xff0c;旨在利用10分钟的时间传达一种可落地的编程思想。 水印是一种用于保护版权和识别内容的技术&#xff0c;通常用于图像、视频或文档中。它可以是文本、图像或两者的组合&#xff0c;通常半透明或以某种方式嵌入到内容中&#xff0c;使其不易被移除或篡改…

非营利组织的数据治理之路

在非营利组织的日常运营中&#xff0c;数据不仅是记录过去活动的工具&#xff0c;更是指导未来决策、衡量项目成效、增强公众信任以及优化资源配置的关键要素。 然而&#xff0c;随着数据量的不断增长和复杂性的提升&#xff0c;非营利组织在享受数据带来的便利的同时&#xf…

文件操作和IO流

前言&#x1f440;~ 上一章我们介绍了多线程进阶的相关内容&#xff0c;今天来介绍使用java代码对文件的一些操作 文件&#xff08;file&#xff09; 文件路径&#xff08;Path&#xff09; 文件类型 文件操作 文件系统操作&#xff08;File类&#xff09; 文件内容的读…

一、openGauss详细安装教程

一、openGauss详细安装教程 一、安装环境二、下载三、安装1.创建omm用户2.授权omm安装目录3.安装4.验证是否安装成功5.配置gc_ctl命令 四、配置远程访问1.配置pg_hba.conf2.配置postgresql.conf3.重启 五、创建用户及数据库 一、安装环境 Centos7.9 x86openGauss 5.0.1 企业版…

我的FPGA

1.安装quartus 2.更新usb blaster驱动 3.新建工程 1.随便找一个文件夹&#xff0c;里面新建demo文件夹&#xff0c;表示一个个工程 在demo文件夹里面&#xff0c;新建src&#xff08;源码&#xff09;&#xff0c;prj&#xff08;项目&#xff09;&#xff0c;doc&#xff…

RedHat Linux8 修改root管理员账户密码命令

RedHat Linux8 修改root管理员账户密码命令&#xff1a; sudo passwd root RedHat重置root管理员密码&#xff1a; 1. 查看Linux系统版本信息 cat /etc/redhat-release2. 重置密码 2.1 进入内核编辑界面 重启Linux系统并出现引导界面&#xff0c;按下键盘上的e键进入内…

数据结构双向循环链表

主程序 #include "fun.h" int main(int argc, const char *argv[]) { double_p Hcreate_head(); insert_head(H,10); insert_head(H,20); insert_head(H,30); insert_head(H,40); insert_tail(H,50); show_link(H); del_tail(H); …

阈值分割后配合Connection算子和箭头工具快速知道区域的ID并选择指定区域

代码 dev_close_window () read_image (Image, E:/机器视觉学习/海康视觉平台/二期VM视觉学习/二期VM视觉学习/机器视觉程序/标定相机找圆心和焊头修正相机找圆心之算法软件/标定相机找圆心和焊头修正相机找圆心之算法软件/03 标定相机找圆心/S2/1号机/1.bmp) get_image_size …

【技术选型】MySQL、Oracle、Postgresql如何选择

【技术选型】MySQL、Oracle、Postgresql如何选择 开篇词&#xff1a;干货篇&#xff1a;MySQL&#xff1a;Oracle&#xff1a;PostgreSQL&#xff1a; 总结篇&#xff1a;我是杰叔叔&#xff0c;一名沪漂的码农&#xff0c;下期再会&#xff01; 开篇词&#xff1a; 常见几种关…

uniapp+vue3嵌入Markdown格式

使用的库是towxml 第一步&#xff1a;下载源文件&#xff0c;那么可以git clone&#xff0c;也可以直接下载压缩包 git clone https://github.com/sbfkcel/towxml.git 第二步&#xff1a;设置文件夹内的config.js&#xff0c;可以选择自己需要的格式 第三步&#xff1a;安装…

每日Attention学习9——Efficient Channel Attention

模块出处 [CVPR 20] [link] [code] ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks 模块名称 Efficient Channel Attention (ECA) 模块作用 通道注意力 模块结构 模块代码 import torch import torch.nn as nn import torch.nn.functional …

CSS【详解】层叠 z-index (含 z-index 的层叠规则,不同样式的层叠效果)

仅对已定位的元素&#xff08; position:relative&#xff0c;position:absolute&#xff0c;position:fixed &#xff09;有效&#xff0c;默认值为0&#xff0c;可以为负值。 z-index 的层叠规则 z-index 值从小到大层叠 兄弟元素 z-index 值相同时&#xff0c;后面的元素在…

【Unity2D 2022:Audio】添加游戏音乐和音效

一、添加背景音乐 1. 创建空的游戏物体&#xff0c;名为BackgroundMusic 2. 为音频播放器添加音频源&#xff08;Audio Source&#xff09;组件 3. 将背景音乐音频赋值到AudioClip&#xff08;红色&#xff09; 4. 设置循环播放&#xff08;蓝色&#xff09; 二、添加草莓拾取…

Android Constant expression required (case R.id.xxx)

gradle更新到8.0后&#xff0c;遇到了这个报错 有两种解决方式&#xff1a; 1、在gradle.properties中添加下面代码 android.nonFinalResIdsfalse 2、使用if-else来判断 int id view.getId(); if (id R.id.setting_iv_back) {} else if (id R.id.setting_tv_clear) {}

【组件库】element-plus组件库

文章目录 0. 启动项目1. gc.sh 新增组件2. 本地验证(组件注册的方式)3. 官方文档修改3-1. 左侧菜单3-2 . 配置md文档3-3. 代码问题:文档修改----------------------------------------------4. 将naiveui的split 分割组件【 复制、迁移】到 element-ui-plus组件库4.1 naiveu…

13 - matlab m_map地学绘图工具基础函数 - 介绍创建管理颜色映射的函数m_colmap和轮廓图绘制颜色条的函数m_contfbar

13 - matlab m_map地学绘图工具基础函数 - 介绍创建管理颜色映射的函数m_colmap和轮廓图绘制颜色条的函数m_contfbar 0. 引言1. 关于m_colmap2. 关于m_contfbar3. 结语 0. 引言 本篇介绍下m_map中用于创建和管理颜色映射函数&#xff08;m_colmap&#xff09;和 为轮廓图绘制颜…

从Helm到 Operator:Kubernetes应用管理的进化

&#x1f9f0;Helm 的作用 在开始前需要先对 kubernetes Operator 有个简单的认识。 以为我们在编写部署一些简单 Deployment 的时候只需要自己编写一个 yaml 文件然后 kubectl apply 即可。 apiVersion: apps/v1 kind: Deployment metadata: labels: app: k8s-combat …

【系统架构设计】计算机组成与体系结构(二)

计算机组成与体系结构 计算机系统组成存储器系统前言主存储器存储器存储数量&#xff08;计算&#xff09; 辅助存储器&#xff08;以磁盘为例&#xff09;Cache存储器 流水线 计算机系统组成 存储器系统 前言 存储器用来存放程序和数据的部件&#xff0c;是一个记忆装置&am…