springAOP常见四种实现方式详解

springAOP介绍

Spring AOP是Spring框架中的一个核心组件,它提供了一种在运行时动态地将代码织入到应用程序中的机制。通过AOP,可以在不修改原始代码的情况下,实现各种横切关注点的功能,例如日志记录、事务管理、安全性等。

springAOP实现方式

Spring AOP就是基于动态代理实现的, 分为两种代理,jdk动态代理(基于接口)和cglib代理(基于类的)。如果目标对象实现了接口,就用jdk动态代理,如果未实现接口就用cglib动态代理。

jdk&cglib动态代理详解

JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。 CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效, JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口 CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理

示例:

1.定义用户创建跟新增的两个接口

public interface UserManager {
    //新增用户抽象方法
    void addUser(String userName, String password);
    //删除用户抽象方法
    void delUser(String userName);
}

2.实现类

public class UserManagerImpl implements UserManager{

    //重写用户新增方法
    @Override
    public void addUser(String userName, String password) {
        System.out.println("调用了用户新增的方法!");
        System.out.println("传入参数:\nuserName = " + userName +", password = " + password);
    }

    //重写删除用户方法
    @Override
    public void delUser(String userName) {
        System.out.println("调用了删除的方法!");
        System.out.println("传入参数:\nuserName = "+userName);
    }
}

3.jdk实现

public class JdkProxy implements InvocationHandler {

    //需要代理的目标对象
    private  Object targetObject;

    public Object getJDKProxy (Object targetObject) {
        //为目标target赋值
        this.targetObject = targetObject;
        //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看来
        Object proxyObject = Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
        //返回代理对象
        return proxyObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理,监听开始!");
        // 调用invoke方法,result存储该方法的返回值
        Object result = method.invoke(targetObject,args);
        System.out.println("JDK动态代理,监听结束!");
        return result;
    }
}

4.cglib代理

//Cglib动态代理,实现MethodInterceptor接口
public class CglibProxy implements MethodInterceptor {
    private Object target;//需要代理的目标对象

    //重写拦截方法
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib动态代理,监听开始!");
        Object result = method.invoke(target,args);//方法执行参数:target 目标对象 arr参数数组
        System.out.println("Cglib动态代理,监听结束!");
        return result;
    }

    //定义获取代理对象的方法
    public UserManager getCglibProxy(Object targetObject) {
        this.target = targetObject;//为目标对象target赋值
        Enhancer enhancer = new Enhancer();
        //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
        enhancer.setSuperclass(targetObject.getClass()); //UserManagerImpl
        enhancer.setCallback(this);//设置回调
        Object result = enhancer.create();//创建并返回代理对象
        return (UserManager) result;
    }
}

5.测试

import com.proxy.CglibProxy.CglibProxy;
import com.proxy.JDKProxy.JdkProxy;
 
public class ClientTest {
    public static void main(String[] args) {
 
        JdkProxy jdkProxy = new JdkProxy();  //实例化JDKProxy对象
        UserManager userJdk = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());   //获取代理对象
        userJdk.addUser("admin","123456");
 
        CglibProxy cglibProxy = new CglibProxy(); //实例化CglibProxy对象
        UserManager userCglib = cglibProxy.getCglibProxy(new UserManagerImpl());//获取代理对象
        userCglib.delUser("admin");
    }
}

aop的通知方法

前置通知:在我们执行目标方法之前运行

后置通知:在我们目标方法运行结束之后,不管有没有异常

返回通知:在我们的目标方法正常返回值后运行

异常通知:在我们的目标方法出现异常后运行

环绕通知:目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象

AOP的几种实现方式

使用到的依赖包

<dependencies>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm</artifactId>
        <version>9.2</version>
    </dependency>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.1</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.1</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.6</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.6</version>
        <scope>compile</scope>
    </dependency>
</dependencies>
方式一:XML配置方式

1.定义一个Person类,类中有一个吃饭的方法

public class Person {
    public void Eat() {
        System.out.println("吃饭了");
    }
}

2.创建一个两个切面类,实现spring用于aop的接口

接口 描述
MethodBeforeAdvice 前置通知
AfterAdvice 后置通知
AfterReturningAdvice 返回通知
ThrowsAdvice 异常通知
MethodInterceptor 环绕通知
public class BeforeEat implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("前置通知===先要做饭");
    }
}

public class AfterEat implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println("后置通知===吃完饭刷碗");
    }
}

3.配置切面,application.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: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">

    <!--1、注册bean,将实体类注册-->
    <bean id="afterEat" class="demo1.AfterEat"></bean>
    <bean id="beforeEat" class="demo1.BeforeEat"></bean>
    <bean id="person" class="demo1.Person"></bean>

    <!--2、配置AOP切面-->
    <aop:config>
        <!--切入点 expression为表达式,参数为切入的位置,demo1.Person.*表示类中所有方法都切入,*(..)两个点表示不指定参数(任意)-->
        <aop:pointcut id="pointcut" expression="execution(* demo1.Person.*(..))"/>

        <!--配置类切入到哪里:如beforeLog类切入到expression表达式指的位置,即demo1类下的所有方法-->
        <aop:advisor advice-ref="afterEat" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="beforeEat" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

4.测试

public class Test {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        //通过目标的bean id,获得代理对象
        Person person = (Person) context.getBean("person");//获取注册的bean对象,实例对象变成bean对象,就是代理对象
        person.eat();
    }
}

5.结果:


前置通知==吃饭前做饭
吃饭了
后置通知===吃完饭刷碗

进程已结束,退出代码0
方式2:自定义类来实现AOP

1.创建一个自定义切面类

public class diyPointCut {

    public void before(){
        System.out.println("=========方法执行前==========");
    }

    public void after(){
        System.out.println("=========方法执行后==========");
    }
}

2.xm配置

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

    <!--1、注册bean,将实体类注册-->
    <bean id="diy" class="demo1.diyPointCut"></bean>
    <bean id="person" class="demo1.Person"></bean>

    <aop:config>
        <!--自定义切面-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* demo1.Person.*(..))"/>
            <!--通知(指定diyPointCut类中的哪个method,aop:before就表示前置)-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>

</beans>

3.测试

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application2.xml");
        //通过目标的bean id,获得代理对象
        Person person = (Person) context.getBean("person");//获取注册的bean对象,实例对象变成bean对象,就是代理对象
        person.eat();
    }
}

4.结果

=========方法执行前==========
吃饭了
=========方法执行后==========
方式三:基于注解实现
aop注解
注解 描述
@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。
@Order 个自定义的AOP注解可以对应多个切面类,这些切面类执行顺序由@Order注解管理,该注解后的数字越小,所在切面类越先执行。
@Pointcut 注解,用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。
@Around 在方法执行之前和之后都执行通知。
@Before 注解指定的方法在切面切入目标方法之前执行
@After @After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。
@AfterReturning 解和 @After 有些类似,区别在于 @AfterReturning 注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理
@AfterThrowing 当被切方法执行过程中抛出异常时,会进入 @AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是 throwing 属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。

示例

1.新建了一个boot项目

2.依赖包

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3.切面逻辑

@Aspect
@Component
@Order(0)
public class LogHelp {


    @Pointcut("execution(* com.example.aopdemo2.demo.*.*(..))")
    public void addLog() {
        System.out.println("触发切面方法");
    }


    @Before("addLog()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + "前置加强");
    }

    @After("addLog()")
    public void logAfter(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + "后置加强");
    }

    @Around("addLog()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + "执行前加强");
        Object result = joinPoint.proceed();
        System.out.println("方法 " + methodName + "执行后加强");
        return result;
    }

    @AfterReturning(pointcut = "addLog()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + "返回值加强");
    }

    @AfterThrowing(pointcut = "addLog()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("方法 " + methodName + "报错加强");
    }
}

4.调用

@RestController
public class sw {

    @Autowired
    LogHelp logHelp;

    @RequestMapping("d")
    public String a () {
        try {
            ArrayList<Object> objects = new ArrayList<>(2);
            objects.get(10);
            logHelp.addLog();
            return "sadasds";
        } catch (Exception e) {
            return "sadas";
        }
    }
}