实现 AOP 的三种方式

2020-08-27 10:08:33 浏览数 (1)

目录

  • 1. 什么是 AOP
  • 2. 利用 Spring API 实现 AOP
  • 3. 自定义类实现 AOP
  • 4. 利用注解实现 AOP

1. 什么是 AOP

AOP(Aspect Oriented Programming),即面向切面编程,就是程序运行过程中在不改变程序源码的情况下,通过 预编译运行期动态代理 来增强方法。利用 AOP 能够对业务逻辑的各部分进行隔离,从而降低业务逻辑各部分之间的耦合度,提高程序的可重用性,提高开发效率。

在 AOP 中,常见概念有如下几点:

  • 横切关注点:横跨于应用程序多个模块的方法或功能,大多与我们的业务逻辑无关,但我们需要关注的部分,如日志、安全、缓存、事务等;
  • 切入点(PointCut):即要添加代码的地方;
  • 通知(Advice):即向切点动态添加的代码;
  • 切面(ASPECT):切点 通知,是一个类;
  • 连接点(JointPoint):和切点一样,是要添加代码的地方;
  • 代理(Proxy):向目标对象应用通知后创建的对象;
  • 目标(Target):被通知的对象;

Spring AOP 中,通过通知(Advice)来定义横切逻辑,支持 5 种类型的 Advice;

通知类型

连接点

接口

前置通知

方法前

org.springframework.aop.MethodBeforeAdvice

后置通知

方法后

org.springframework.aop.AfterReturningAdvice

环绕通知

方法前后

org.springframework.aop.MethodInterceptor

异常抛出通知

方法抛出异常

org.springframework.aop.ThrowsAdvice

引介通知

类中添加新的方法属性

org.springframework.aop.IntroductionInterceptor

AOP 实际上是基于 Java 动态代理来实现,因此主要有以下三种实现方式:

  1. 使用 Spring API 实现
  2. 自定义类来实现 AOP
  3. 使用注解实现

2. 利用 Spring API 实现 AOP

  1. 准备业务接口
代码语言:javascript复制
package com.cunyu.service;

/**
 * @InterfaceName : UserService
 * @Author : cunyu
 * @Date : 2020/7/20 8:36
 * @Version : 1.0
 * @Description : 业务接口
 **/

public interface UserService {
    public void add();

    public void delete();

    public void update();

    public void search();
}

  1. 实现业务接口
代码语言:javascript复制
package com.cunyu.service;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : UserServiceImpl
 * @date : 2020/7/20 8:37
 * @description : 业务接口实现类
 */

public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("add user");
    }

    @Override
    public void delete() {
        System.out.println("delete user");
    }

    @Override
    public void update() {
        System.out.println("update user");
    }

    @Override
    public void search() {
        System.out.println("search user");
    }
}
  1. 编写增强类
代码语言:javascript复制
package com.cunyu.service;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : StrenthUSerAfter
 * @date : 2020/7/20 8:48
 * @description : 后置增强
 */

public class StrenthUSerAfter implements AfterReturningAdvice {
    /**
     * @param o       返回值
     * @param method  被调用的方法
     * @param objects 被调用方法的对象的参数
     * @param o1      被调用的目标对象
     * @return
     * @description TODO
     * @date 2020/7/20 8:49
     * @author cunyu1943
     * @version 1.0
     */
    @Override
    public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
        System.out.println(o1.getClass().getName()   " 类的 "   method.getName()   " 方法被执行,返回值是 "   o);
    }
}
  1. 配置 Spring 配置文件
代码语言:javascript复制
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- Bean 注册 -->
    <bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
    <bean id="userBefore" class="com.cunyu.service.StrenthUSerBefore"/>
    <bean id="userAfter" class="com.cunyu.service.StrenthUSerAfter"/>

    <!--  AOP 配置  -->
    <aop:config>
        <!--切入点 expression:表达式匹配要执行的方法-->
        <aop:pointcut id="pointcut" expression="execution(* com.cunyu.service.UserServiceImpl.*(..))"/>
        <!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
        <aop:advisor advice-ref="userAfter" pointcut-ref="pointcut"/>
    </aop:config>
</beans>

  1. 进行单元测试
代码语言:javascript复制
package com.cunyu.service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : UserServiceTest
 * @date : 2020/7/20 8:58
 * @description : 测试类
 */


public class UserServiceTest {
    @Test
    public void testAdd() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.add();
    }
}

3. 自定义类实现 AOP

  1. 准备业务接口(同第一种方法)
  2. 实现业务接口(同第一种方法)
  3. 准备我们的自定义切入类
代码语言:javascript复制
package com.cunyu.service;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : MyPointCut
 * @date : 2020/7/20 9:08
 * @description : 自定义切入类
 */

public class MyPointCut {
    public void after() {
        System.out.println("方法执行后...");
    }
}
  1. Spring 配置文件中去配置
代码语言:javascript复制
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- Bean 注册 -->
    <bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
    <bean id="myPointCut" class="com.cunyu.service.MyPointCut"/>

    <!--  AOP 配置  -->
    <aop:config>
        <aop:aspect ref="myPointCut">
            <aop:pointcut id="mypointcut1" expression="execution(* com.cunyu.service.UserServiceImpl.*(..))"/>
            <aop:after pointcut-ref="mypointcut1" method="after"/>
        </aop:aspect>
    </aop:config>
</beans>

  1. 单元测试
代码语言:javascript复制
package com.cunyu.service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : UserServiceTest
 * @date : 2020/7/20 8:58
 * @description : 测试类
 */


public class UserServiceTest {

    @Test
    public void testDelete() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.delete();
    }
}

4. 利用注解实现 AOP

  1. 准备业务接口(同第一种方法)
  2. 实现业务接口(同第一种方法)
  3. 编写一个使用注解实现的增强类
代码语言:javascript复制
package com.cunyu.config;

import org.aspectj.lang.annotation.After;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : AnnotationPointCut
 * @date : 2020/7/20 9:39
 * @description : 基于注解实现的增强类
 */

public class AnnotationPointCut {
    @After("execution(* com.cunyu.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("注解实现,方法执行后...");
    }
}
  1. 配置 Spring 配置
代码语言:javascript复制
<?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 https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- Bean 注册,并增加支持注解的配置 -->
    <bean id="userService" class="com.cunyu.service.UserServiceImpl"/>
    <bean id="annotationPointCut" class="com.cunyu.config.AnnotationPointCut"/>
    <aop:aspectj-autoproxy/>
</beans>

  1. 单元测试
代码语言:javascript复制
package com.cunyu.service;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author : cunyu
 * @version : 1.0
 * @className : UserServiceTest
 * @date : 2020/7/20 8:58
 * @description : 测试类
 */


public class UserServiceTest {

    @Test
    public void testUpdate() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.update();
    }
}

注意: <aop:aspectj-autoproxy/> 声明会自动为 Spring 容器中配置 @Aspect 切面的 Bean 创建代理,植入切面。但在 Spring 内部,依然采用AnnotationAwareAspectJAutoProxyCreator 进行自动代理的创建工作,只是具体实现的细节已经被 <aop:aspectj-autoproxy /> 隐藏起来了。此外,还有一个proxy-target-class 属性,默认为false,表示使用 Jdk 动态代理注入增强,此时如果目标类没有声明接口,则 Spring 将自动使用CGLib 动态代理。当为 true 时,表示使用 CGLib 动态代理技术注入增强。

- END -

往期精选

Spring 代理模式知多少

利用注解进行 Spring 开发

Spring 中的自动装配

0 人点赞