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 接口的方法
为什么使用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 术语串起来做成流程图就大致如下
写一个使用 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);
}
}
断点停住
可以清楚的看到 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 方法
可以看到,目标对象和被 Spring 包装的代理对象
打印可以看到环绕事件的效果
自定义注解 + 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");
}
}
测试结果
仍然达到了拦截器加入业务的效果