Spring AOP以及AspectJ

一、AOP的概念

AOP(Aspect Oriented Programming)即面向切面编程,它是指将业务流程中通用的功能单独抽取出来,形成“切面”,在合适的时机将这些切面切入到业务板块中
AOP是对OOP的补充,它大大降低了企业开发中的代码耦合度,使得业务板块更加简洁,令功能的复用性也得以提升。
在这里插入图片描述

二、AOP的术语

术语 含义
JoinPoint(连接点) 所有能被拦截的方法
Pointcut(切入点) 特指被拦截的方法
Advice(通知) 拦截后要执行的操作
Target(目标) 特指被AOP操作的对象
Weaving(织入) 将Advice应用到Target的过程
Proxy(代理) AOP完成后产生的代理对象
Aspect(切面) Pointcut + Advice

三、AOP的原理

1. JDK动态代理

JDK在底层通过字节码技术完成代理操作,它只能对实现接口的类进行代理。若类实现了接口,Spring框架将采用JDK的方式实现AOP。
请看示例:

public interface UserDao {

    void add();

    void update();

    void delete();

    void find();

}
public class UserDaoImpl implements UserDao {

    @Override
    public void add() {
        System.out.println("===增加数据===");
    }

    @Override
    public void update() {
        System.out.println("===修改数据===");
    }

    @Override
    public void delete() {
        System.out.println("===删除数据===");
    }

    @Override
    public void find() {
        System.out.println("===查找数据===");
    }
    
}

假设现在要对add方法增加权限校验模板,具体操作如下:

public class MyJdkProxy implements InvocationHandler {

    private UserDao userDao;

    public MyJdkProxy(UserDao userDao) {
        this.userDao = userDao;
    }

	/**
     * 创建代理类对象的方法
     * @return 代理类对象
     */
    public Object createProxy() {
        Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
        return proxy;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("add".equals(method.getName())) {
            System.out.println("————权限校验————");
            return method.invoke(userDao, args);
        }
        return method.invoke(userDao, args);
    }
}

最终执行代理类对象中的add方法:

UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();

proxy.add(); // 权限校验 + 增加数据

2. CGLib动态代理

CGLib包含在Spring框架中,采用了类继承的方式实现动态代理,没有接口实现类的限制。若类没有实现任何接口,Spring框架将采用CGLib的方式实现AOP。

public class UserDao {

    public void add() {
        System.out.println("===增加数据===");
    }

    public void update() {
        System.out.println("===修改数据===");
    }

    public void delete() {
        System.out.println("===删除数据===");
    }

    public void find() {
        System.out.println("===查找数据===");
    }

}

假设现在要对add方法增加权限校验模板,具体操作如下:

public class MyCglibProxy implements MethodInterceptor {

    private UserDao userDao;

    public MyCglibProxy(UserDao userDao) {
        this.userDao = userDao;
    }

    /**
     * 创建代理类对象的方法
     *
     * @return 代理类对象
     */
    public Object createProxy() {
        // 1.创建核心类
        Enhancer enhancer = new Enhancer();
        // 2.设置父类
        enhancer.setSuperclass(userDao.getClass());
        // 3.设置回调
        enhancer.setCallback(this);
        // 4.创建代理
        Object proxy = enhancer.create();
        return proxy;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        if ("add".equals(method.getName())) {
            System.out.println("————权限校验————");
            return methodProxy.invokeSuper(proxy, args);
        }
        return methodProxy.invokeSuper(proxy, args);
    }
}
UserDao userDao = new UserDao();
UserDao proxy = (UserDao) new MyCglibProxy(userDao).createProxy();

proxy.add(); // 权限校验 + 增加数据

四、传统Spring AOP

暂时省略,可参考其它博客


五、基于AspectJ框架

1. 注解方式

注解 含义
@Before 前置通知(BeforeAdvice)
@AfterReturning 后置通知(AfterReturningAdvice)
@Around 环绕通知(MethodInterceptor)
@AfterThrowing 异常抛出通知(ThrowAdvice)
@After 最终通知(类似try-catch中的finally)

配置文件需要使用AOP的约束,并且开启AspectJ的注解扫描

<?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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->

    <!-- 开启AspectJ的注解扫描 -->
    <aop:aspectj-autoproxy/>

</beans>

现定义一个UserDao类:

public class UserDao {

    public void add() {
        System.out.println("===增加数据===");
    }

    public String update() {
        System.out.println("===修改数据===");
        return "finished!";
    }

    public void delete() {
        System.out.println("===删除数据===");
    }

    public void find() {
        System.out.println("===查找数据===");
    }

}

通过execution函数可以完成切入点的定义,具体语法如下:
execution(<返回类型> <包名.类名.方法名>(参数))

现定义一个切面类:

@Aspect
public class MyAspectAnno {

    /**
     * 前置增强的方法
     * @param joinPoint 切入点信息
     */
    @Before(value = "execution(* aspect1.UserDao.add(..))")
    public void before(JoinPoint joinPoint) {
        System.out.println("————前置通知————" + joinPoint);
    }

    /**
     * 后置增强的方法
     * @param returning 目标方法的返回值
     */
    @AfterReturning(value = "execution(* aspect1.UserDao.update(..))", returning = "returning")
    public void afterReturning(Object returning) {
        System.out.println("————后置通知————" + returning);
    }

    /**
     * 环绕增强的方法
     * @param joinPoint 用于控制目标方法执行的对象
     * @return 目标方法的返回值
     * @throws Throwable
     */
    @Around(value = "execution(* aspect1.UserDao.delete(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("————环绕前通知————");
        Object obj = joinPoint.proceed();
        System.out.println("————环绕后通知————");
        return obj;
    }

    /**
     * 异常抛出增强的方法
     * @param e 异常信息
     */
    @AfterThrowing(value = "execution(* aspect1.UserDao.find(..))", throwing = "e")
    public void afterThrowing(Throwable e) {
        System.out.println("————异常抛出通知————" + e);
    }

    /**
     * 最终增强的方法
     */
    @After(value = "execution(* aspect1.UserDao.find(..))")
    public void after() {
        System.out.println("————最终通知————");
    }

}

完成相关配置后,依次运行UserDao的各个方法,结果如下:
在这里插入图片描述
(注:异常抛出通知仅在目标方法发生异常时会作用,而最终通知即使发生异常也会作用)


附:也可以通过@Pointcut单独定义切点,以方便注解的书写与维护:

/**
 * 自定义切点1
 */
@Pointcut(value = "execution(* aspect1.UserDao.add(..))")
private void myPointcut1() {

}

/**
 * 前置增强的方法
 */
@Before(value = "myPointcut1()")
public void before() {
	System.out.println("————前置通知————");
}

2. XML方式

沿用例1中的UserDao,并实现一个无注解的切面类

public class MyAspectXml {

    /**
     * 前置增强的方法
     */
    public void before(JoinPoint joinPoint) {
        System.out.println("————前置通知————" + joinPoint);
    }

    /**
     * 后置增强的方法
     * @param returning 目标方法的返回值
     */
    public void afterReturning(Object returning) {
        System.out.println("————后置通知————" + returning);
    }

    /**
     * 环绕增强的方法
     * @param joinPoint 用于控制目标方法执行的对象
     * @return 目标方法的返回值
     * @throws Throwable
     */
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("————环绕前通知————");
        Object obj = joinPoint.proceed();
        System.out.println("————环绕后通知————");
        return obj;
    }

    /**
     * 异常抛出增强的方法
     */
    public void afterThrowing(Throwable e) {
        System.out.println("————异常抛出通知————" + e);
    }

    /**
     * 最终增强的方法
     */
    public void after() {
        System.out.println("————最终通知————");
    }

}

完成XML配置文件:

<!-- 配置目标类 -->
<bean id="userDao" class="aspect2.UserDaoImpl"/>

<!-- 配置切面类 -->
<bean id="myAspectXml" class="aspect2.MyAspectXml"/>

<!-- 配置AOP -->
<aop:config>
	<!-- 配置切入点 -->
	<aop:pointcut id="pointcut1" expression="execution(* aspect2.UserDao.add(..))"/>
	<aop:pointcut id="pointcut2" expression="execution(* aspect2.UserDao.update(..))"/>
	<aop:pointcut id="pointcut3" expression="execution(* aspect2.UserDao.delete(..))"/>
 	<aop:pointcut id="pointcut4" expression="execution(* aspect2.UserDao.find(..))"/>
	<!-- 配置切面 -->
    <aop:aspect ref="myAspectXml">
    	<!-- 配置前置通知 -->
        <aop:before method="before" pointcut-ref="pointcut1"/>
       	<!-- 配置后置通知 -->
       	<aop:after-returning method="afterReturning" pointcut-ref="pointcut2" returning="returning"/>
        <!-- 配置环绕通知 -->
        <aop:around method="around" pointcut-ref="pointcut3"/>
        <!-- 配置异常抛出通知 -->
        <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="e"/>
        <!-- 配置最终通知 -->
        <aop:after method="after" pointcut-ref="pointcut4"/>
	</aop:aspect>
</aop:config>

最终效果与例1完全一致





附:

  • 要使用Spring AOP,需要引入spring-aop、aopalliance
  • 要使用SpringJUnit以及Context注解,需要引入spring-test
  • 要使用AspectJ,需要在AOP的基础上引入aspectjweaver、spring-aspects
发布了48 篇原创文章 · 获赞 4 · 访问量 6148

猜你喜欢

转载自blog.csdn.net/Knightletter/article/details/103915434