Spring Boot-003 Spring AOP

131 阅读6分钟

Spring AOP 的本质一种约定流程的编程

创建一个代理的过程
service

    package org.greenfred.springaop.service;  
      
    public interface HelloService {  
        public void sayHello(String name);  
    }

    package org.greenfred.springaop.service.impl;  
      
    import org.greenfred.springaop.service.HelloService;  
      
    public class HelloServiceImpl implements HelloService {  
      
        @Override  
        public void sayHello(String name) {  
            if (name == null || name.trim().isEmpty()) {  
                throw new IllegalArgumentException("名字不能为 null");  
            }  
            System.out.println("Hello " + name);  
        }  
    }

自定义拦截器,进行约定编程,useAround 为 true 进行取代原有方法

    package org.greenfred.springaop.service;  
      
      
    import org.greenfred.springaop.Invocation;  
      
    import java.lang.reflect.InvocationTargetException;  
      
    public interface Interceptor {  
        // 事前方法  
        public boolean before();  
        // 事后方法  
        public void after();  
      
        /**  
         * 取代原有事件方法  
         * @param invocation 回调参数,可以通过 proceed 方法 回调原有事件  
         * @return 原有事件返回对象  
         * @throws InvocationTargetException  
         * @throws IllegalAccessException  
         */    public Object around(Invocation invocation) throws Throwable;  
      
        // 是否返回方法,事件没有发生异常  
        public void afterReturning();  
      
        // 事后异常方法,当事件发生异常后执行  
        public void afterThrowing();  
      
        // 是否使用 around 方法取代原有方法  
        boolean useAround();  
    }

    package org.greenfred.springaop.service;  
      
    import org.aopalliance.intercept.Invocation;  
      
    import java.lang.reflect.InvocationTargetException;  
      
    public class MyInterceptor implements Interceptor {  
        @Override  
        public boolean before() {  
            System.out.println("执行之前");  
            return true;  
        }  
      
        @Override  
        public void after() {  
            System.out.println("执行之后");  
        }  
      
        @Override  
        public Object around(org.greenfred.springaop.Invocation invocation) throws Throwable {  
            System.out.println("around 之前");  
            Object object = invocation.proceed();  
            System.out.println("around 之后");  
            return object;  
        }  
      
      
      
      
        @Override  
        public void afterReturning() {  
            System.out.println("返回对象之后");  
        }  
      
        @Override  
        public void afterThrowing() {  
            System.out.println("抛异常之后");  
        }  
      
        @Override  
        public boolean useAround() {  
            return true;  
        }  
    }

创建调用类

    package org.greenfred.springaop;  
      
    import java.lang.reflect.InvocationTargetException;  
    import java.lang.reflect.Method;  
      
    public class Invocation {  
        private Object[] params;  
        private Method method;  
        private Object target;  
      
        public Invocation(Object target, Method method, Object[] params) {  
            this.params = params;  
            this.method = method;  
            this.target = target;  
        }  
      
        public Object proceed() throws InvocationTargetException, IllegalAccessException {  
            return method.invoke(target, params);  
        }  
      
        public Object[] getParams() {  
            return params;  
        }  
      
        public void setParams(Object[] params) {  
            this.params = params;  
        }  
      
        public Method getMethod() {  
            return method;  
        }  
      
        public void setMethod(Method method) {  
            this.method = method;  
        }  
      
        public Object getTarget() {  
            return target;  
        }  
      
        public void setTarget(Object target) {  
            this.target = target;  
        }  
    }

创建代理 Bean 实现 InvocationHandler,静态方法接收目标对象和拦截器,通过 newProxyInstance 生成代理实例,最后返回这个代理对象

    package org.greenfred.springaop;  
      
    import org.greenfred.springaop.service.Interceptor;  
      
    import java.lang.reflect.InvocationHandler;  
    import java.lang.reflect.Method;  
    import java.lang.reflect.Proxy;  
      
    public class ProxyBean implements InvocationHandler {  
        private Object target = null;  
        private Interceptor interceptor = null;  
        /**  
         * 绑定代理对象  
         * @param target 被代理对象  
         * @param interceptor 拦截器  
         * @return 代理对象  
         */  
        public static Object getProxyBean(Object target, Interceptor interceptor) {  
            ProxyBean proxyBean = new ProxyBean();  
            // 保存被代理对象  
            proxyBean.target = target;  
            // 保存拦截器  
            proxyBean.interceptor = interceptor;  
            // 生成代理对象  
            Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyBean);  
            return proxy;  
        }  
      
        @Override  
        public Object invoke(Object proxy, Method method, Object[] args) {  
            // 异常标识  
            boolean exceptionFlag = false;  
            Invocation invocation = new Invocation(target, method, args);  
            Object retObj = null;  
            try {  
                if (this.interceptor.before()) {  
                    retObj = this.interceptor.around(invocation);  
                } else {  
                    retObj = method.invoke(target, args);  
                }  
            } catch (Exception exception) {  
                // 产生异常  
                exceptionFlag = true;  
            } catch (Throwable e) {  
                throw new RuntimeException(e);  
            }  
            this.interceptor.after();  
            if (exceptionFlag) {  
                this.interceptor.afterReturning();  
            } else {  
                this.interceptor.afterThrowing();  
                return retObj;  
            }  
            return null;  
        }  
    }

测试,getProxyBean 传入 HelloService 的实现和自定义拦截器,遵守了 ProxyBean 的约定,ProxyBean 可以强制转换为 HelloService 类型,此时处理的是 ProxyBean 包装的代理对象,而不是传入的 HelloService 的实例。

    package org.greenfred.springaop;  
      
    import org.greenfred.springaop.service.HelloService;  
    import org.greenfred.springaop.service.MyInterceptor;  
    import org.greenfred.springaop.service.impl.HelloServiceImpl;  
    import org.junit.jupiter.api.Test;  
    import org.springframework.boot.test.context.SpringBootTest;  
      
    @SpringBootTest  
    class SpringAopApplicationTests {  
      
        @Test  
        void contextLoads() {  
            HelloService helloService = new HelloServiceImpl();  
      
            HelloService proxy = (HelloService) ProxyBean.getProxyBean(helloService, new MyInterceptor());  
            proxy.sayHello("aaaaa");  
        }  
      
    }

测试,会进入 ProxyBean 的 invoke 方法,打印出 HelloService 接口的方法 Pasted image 20241105111701.png Pasted image 20241103160358.png

为什么使用AOP

AOP可以处理数据库事务
AOP还可以减少大量重复的工作

AOP 术语和流程

连接点(join Point):被拦截对象的的特定方法。
切点(point cut):切面不单单应用于单个方法,也可能是多个类的不同方 法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点 。 切点就是提供这样 一 个功能 的概念 。
通知(advice):就是按照约定的流程下的方法,分为前置通知( before advice)、后置通知( after advice)、环绕通知 (around advice )、 事后返回通知( afterRetuming advice )和异常通知 (afterThrowing advice),它会根据约定织入流程中。
目标对象(target):被代理对象。
引入(introduction):是指引入新的类和其方法 , 增强现有 Bean 的功 能 。
织入(weaving):它是一个通过动态代理技术,为原有服务对象生成代理对象 ,然后将与切点定义匹配的连接点拦截 ,并按约定将各类通知织入约定流程的过程 。
切面(aspect):是一个可以 定义切点、各类通知和引入 的内 容 , SpringAOP 将通过它的信息 来增强 Bean 的功 能或者将对应的方法织入流程 。 把之前的 Demo 用 AOP 术语串起来做成流程图就大致如下 Pasted image 20241106151854.png

写一个使用 Spring AOP 的 Demo

用户信息实体

    package org.greenfred.springaop.aspect.po;  
      
    public class User {  
        private int id;  
        private String name;  
      
        public int getId() {  
            return id;  
        }  
      
        public void setId(int id) {  
            this.id = id;  
        }  
      
        public String getName() {  
            return name;  
        }  
      
        public void setName(String name) {  
            this.name = name;  
        }  
    }

UserService 里面编写连接点的方法


    package org.greenfred.springaop.aspect.service;  
      
    import org.greenfred.springaop.aspect.po.User;  
      
    public interface UserService {  
        public void printUserInfo(User user);  
    }

    package org.greenfred.springaop.aspect.service.impl;  
      
    import org.greenfred.springaop.aspect.po.User;  
    import org.greenfred.springaop.aspect.service.UserService;  
    import org.springframework.stereotype.Service;  
      
    @Service  
    public class UserServiceImpl implements UserService {  
      
        @Override  
        public void printUserInfo(User user) {  
            if (user == null) {  
                throw new IllegalArgumentException("大哥 user 是空");  
            }  
            System.out.println(user.getId());  
            System.out.println(user.getName());  
        }  
    }

编写一个自定义切面,注意 @Aspect 等AOP注解需要额外引入 spring-boot-starter-aop 依赖,否则找不到相关注解

    package org.greenfred.springaop.aspect;  
      
    import org.aspectj.lang.annotation.*;  
      
    @Aspect  
    public class MyAspect {  
        @Before("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
        public void before() {  
            System.out.println("之前");  
        }  
      
        @After("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
        public void after() {  
            System.out.println("之后");  
        }  
      
        @AfterReturning("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
        public void afterReturning() {  
            System.out.println("返回之后");  
        }  
      
        @AfterThrowing("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
        public void afterThrowing() {  
            System.out.println("抛出异常之后");  
        }  
    }

这样使用过于繁琐,可以使用切点进行改造,改造后的效果如下


package org.greenfred.springaop.aspect;  
  
import org.aspectj.lang.annotation.*;  
  
@Aspect  
public class MyAspect {  
  
    @Pointcut("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
    public void pointCut() {  
  
    }  
  
    @Before("pointCut()")  
    public void before() {  
        System.out.println("之前");  
    }  
  
    @After("pointCut()")  
    public void after() {  
        System.out.println("之后");  
    }  
  
    @AfterReturning("pointCut()")  
    public void afterReturning() {  
        System.out.println("返回之后");  
    }  
  
    @AfterThrowing("pointCut()")  
    public void afterThrowing() {  
        System.out.println("抛出异常之后");  
    }  
}

通过 @PointCut 标注方法,其他注解中写这个被标注的方法即可

拆解 execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))

execution:在执行的时,候拦截里面的正则匹配的方法 *:表示任意的返回类型的方法 org.greenfred.springaop.aspect.service.impl.UserServiceImpl:指定目标对象的全限定名 (..):表示任意参数进行匹配

测试 AOP

Demo 准备基本完成,现在需要发送请求进行 AOP 的测试 创建一个 Controller

    package org.greenfred.springaop.aspect.controller;  
      
    import org.greenfred.springaop.aspect.po.User;  
    import org.greenfred.springaop.aspect.service.UserService;  
    import org.springframework.beans.factory.annotation.Autowired;  
    import org.springframework.stereotype.Controller;  
    import org.springframework.web.bind.annotation.RequestBody;  
    import org.springframework.web.bind.annotation.RequestMapping;  
    import org.springframework.web.bind.annotation.ResponseBody;  
      
    @Controller  
    @RequestMapping("/user")  
    public class UserController {  
        // 注入服务  
        @Autowired  
        private UserService userService = null;  
      
        @RequestMapping("/printUser")  
        @ResponseBody  
        private User printUser(Integer id, String userName) {  
            User user1 = new User();  
            user1.setId(id);  
            user1.setName(userName);  
            userService.printUserInfo(user1);  
            return user1;  
        }  
    }


    package org.greenfred.springaop;  
      
    import org.greenfred.springaop.aspect.MyAspect;  
    import org.springframework.boot.SpringApplication;  
    import org.springframework.boot.autoconfigure.SpringBootApplication;  
    import org.springframework.context.annotation.Bean;  
      
    @SpringBootApplication(scanBasePackages = {"org.greenfred.springaop.aspect"})  
    public class SpringAopApplication {  
      
        // 定义切面  
        @Bean("myAspect")  
        public MyAspect initMyAspect() {  
            return new MyAspect();  
        }  
      
        public static void main(String[] args) {  
      
            SpringApplication.run(SpringAopApplication.class, args);  
        }  
      
    }

断点停住 Pasted image 20241107154351.png Pasted image 20241107154512.png 可以清楚的看到 target 和 targetSource 都指向的是 UserServiceImpl 对象

环绕通知

    package org.greenfred.springaop.aspect;  
      
    import org.aspectj.lang.ProceedingJoinPoint;  
    import org.aspectj.lang.annotation.*;  
      
    @Aspect  
    public class MyAspect {  
      
        @Pointcut("execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..))")  
        public void pointCut() {  
      
        }  
      
        @Before("pointCut()")  
        public void before() {  
            System.out.println("之前");  
        }  
      
        @After("pointCut()")  
        public void after() {  
            System.out.println("之后");  
        }  
      
        @Around("pointCut()")  
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {  
            System.out.println("环绕之前");  
            // 回调目标对象的原有方法  
            joinPoint.proceed();  
            System.out.println("环绕之后");  
        }  
      
        @AfterReturning("pointCut()")  
        public void afterReturning() {  
            System.out.println("返回之后");  
        }  
      
        @AfterThrowing("pointCut()")  
        public void afterThrowing() {  
            System.out.println("抛出异常之后");  
        }  
    }

进行断点调试,请求后会进入到 proceed 方法 Pasted image 20241108104055.png 可以看到,目标对象和被 Spring 包装的代理对象 Pasted image 20241108104512.png 打印可以看到环绕事件的效果 Pasted image 20241108104807.png

自定义注解 + Spring AOP

execution(* org.greenfred.springaop.aspect.service.impl.UserServiceImpl.printUserInfo(..)) 这种写法非常不灵活,要么是一个一个方法匹配,要么是*全部匹配 采用注解编写 AOP 会变得很方便 来个Demo 编写个注解

    // 注解用于方法  
    @Target(ElementType.METHOD)  
    // @Retention 被注解保留多久,RUNTIME 到运行时  
    @Retention(RetentionPolicy.RUNTIME)  
    public @interface TestPrint {  
    }

编写个切面

    package org.greenfred.springaop.annoaspect.aspect;  
      
    import org.aspectj.lang.annotation.Aspect;  
    import org.aspectj.lang.annotation.Before;  
    import org.aspectj.lang.annotation.Pointcut;  
    import org.springframework.stereotype.Component;  
      
    @Aspect  
    @Component  
    public class PrintAspect {  
        @Pointcut("@annotation(org.greenfred.springaop.annoaspect.annotation.TestPrint)")  
        public void printPointcut() {  
      
        }  
    	// 各种通知
        @Before("printPointcut()")  
        public void printBefore() {  
        // 各种业务
            System.out.println("PrintBefore");  
        }  
    }

在需要的地方加上这个注解

    package org.greenfred.springaop.annoaspect.controller;  
      
    import org.greenfred.springaop.annoaspect.annotation.TestPrint;  
    import org.springframework.stereotype.Controller;  
    import org.springframework.web.bind.annotation.RequestMapping;  
    import org.springframework.web.bind.annotation.ResponseBody;  
      
    @Controller  
    @RequestMapping("/testPrint")  
    public class PrintController {  
      
        @TestPrint  
        @ResponseBody    
        @RequestMapping("/printInfo")  
        public void printInfo() {  
            System.out.println("PrintController");  
        }  
    }

测试结果 Pasted image 20241119111216.png 仍然达到了拦截器加入业务的效果