前言 这篇博客先从AOP的代码编写讲起,接着介绍AOP的实现原理,其中包括动态代理的概念,最后介绍Spring中可以直接用的AOP注解。
代码实践 添加依赖包 1 2 3 4 5 <dependency >     <groupId > org.springframework.boot</groupId >      <artifactId > spring-boot-starter-aop</artifactId >  </dependency > 
 
创建切面类 1 2 3 4 5 @Component  @Aspect  public  class  MainAspect   {} 
 
定义切入点 切入点就是你要在哪个包下的哪个类的哪个方法执行切面逻辑,或者说指定切入哪里。Spring AOP的切入点只能是方法。  可以在一个切面类中定义多个切入点,再给每个切入点指定不同的Advice方法(Advice方法介绍见下一节)。 定义切入点有两种方式:
用execution表达式定义切入点 
用自定义注解定义切入点 
 
用execution表达式定义切入点 1 2 3 @Pointcut ("execution(* com.sample.service.impl..*.*(..))" )public  void  pointcut ()   {} 
 
这个示例表示在com.sample.service.impl包(含子包)下的所有类的所有方法切入。下面把这个示例拆解看看execution表达式的格式。
符号 
含义 
 
 
第一个”*”符号 
表示返回值的类型任意 
 
com.sample.service.impl 
要切入的类所在包名 
 
包名后面的”..” 
表示当前包及子包 
 
第二个”*“符号 
表示类名,*即任意类,也可指定具体的类,或带前后缀的类,如 *Service 
 
.*(..) 
表示任何方法名,括号表示参数,两个点表示任何参数类型 
 
如何使用这个切入点?示例:
1 2 3 4 @Before ("pointcut()" )public  void  doBefore ()   {     } 
 
若要同时指定多个切入点 ,用这个方法:
1 2 3 4 @Before ("pointcut1() || pointcut2()" )public  void  doBefore ()   {     } 
 
用自定义注解定义切入点 假设已有自定义注解:
1 2 3 4 5 6 7 @Documented @Target (ElementType.METHOD)@Inherited @Retention (RetentionPolicy.RUNTIME )public  @interface  Log {	String value ()  default  "" ; } 
 
定义切入点:
1 2 3 @Pointcut ("@annotation(com.example.demo.aop.Log)" ) public  void  pointcut ()   {} 
 
这个示例表示切入所有加了@Log属性的方法,比如下面这个方法:
1 2 3 4 5 6 7 8 @RestController public  class  UserController   {	@Log ("添加用户操作" ) 	@RequestMapping ("/user/add" ) 	public  String add ()   { 		return  "add" ; 	} } 
 
使用这个切入点的方法和用“execution表达式”定义的切入点没有区别:
1 2 3 4 @Before ("pointcut()" )public  void  doBefore ()   {     } 
 
但这只是“自定义注解”定义的切入点的其中一种使用方式,这种使用方式的一个缺点是不能访问自定义注解@Log的value属性。 另一种使用方式:
1 2 3 4 5 @Before ("@annotation(log)" )  public  void  doBefore (JoinPoint joinPoint, Log log)   {            System.out.println(log.value());   } 
 
这种使用方式的区别就是,切入点直接写在@Before注解中,不需要再用pointcut()方法定义切入点
和execution表达式相比,用自定义注解定义切入点,可以灵活安排切入的方法(想切哪里注解就加在哪里),且通过自定义注解的属性,在切面类中也能了解到被切方法的业务逻辑,若要在切面类中统一打印被切方法的日志,这点很好用。
根据业务逻辑编写Advice方法 Advice,有翻译为“增强处理”,也有翻译为“通知”,本质含义就是要执行的切面逻辑,如要在每个方法开始前打印入参日志,就可以编写Before类型的Advice方法,如要在每个方法抛出异常后统一处理,就可以编写AfterThrowing类型的Advice方法。Advice一共有五种类型:
Before 
After 
AfterReturning 
AfterThrowing 
Around 
 
这些类型的共同特点:
所有类型的注解都有两个属性:value、argNames。value属性用于指定切入点,argNames属性可以用来访问目标方法的入参 
除Around之外,其他四种类型的方法的连接点参数都只能是JoinPoint,不能是ProceedingJoinPoint(ProceedingJoinPoint是JoinPoint的子类) 
只有Around方法能改变目标方法的入参和返回值 
 
下面分别介绍这五种类型的特点和使用方法。
Before 在目标方法执行前织入,不能访问目标方法的返回值,可选参数有JoinPoint。@Before注解属性:value、argNames
JoinPoint简单介绍: JoinPoint参数不必须,但需要时必须作为第一个参数! 常用方法有: Object[] getArgs:返回目标方法的参数 Signature getSignature:返回目标方法的签名(含方法名和参数表)
 
示例:
1 2 3 4 5 6 7 8 9 10 11 @Before ("pointcut()" )public  void  doBefore (JoinPoint joinPoint)   {    System.out.println("============before============" );     System.out.println("目标方法名为:"  + joinPoint.getSignature().getName());      System.out.println("目标方法所属类的类名:"  + joinPoint.getSignature().getDeclaringTypeName());          Object[] args = joinPoint.getArgs();     for  (int  i = 0 ; i < args.length; i++) {         System.out.println("第"  + (i+1 ) + "个参数为:"  + args[i]);     } } 
 
After 在目标方法结束后织入,不管目标方法如何结束(正常还是异常),它都会被织入,可选参数有JoinPoint。@After注解和@Before注解属性相同:value、argNames 示例:
1 2 3 4 5 @After ("pointcut()" )public  void  doAfter (JoinPoint joinPoint)   {    System.out.println("============after============" );      } 
 
AfterReturning 在目标方法正常完成后被织入,抛出异常了不织入,可选参数有JoinPoint和Object(目标方法的返回值)。@AfterReturning注解除了value、argNames这两个属性外,还有一个属性:
returning:指定一个返回值形参名,可以通过该形参名来访问目标方法的返回值,但不可修改目标方法的返回值 
 
示例:
1 2 3 4 5 6 7 @AfterReturning (pointcut = "pointcut()" , returning = "returnObject" )public  void  doAfterReturning (JoinPoint joinPoint, Object returnObject)   {     System.out.println("============afterReturning============" );               System.out.println("返回值:"  + returnObject); } 
 
AfterThrowing 在目标方法抛出异常时织入,正常完成不织入,可选参数有JoinPoint和Throwable(目标方法抛出的异常)。@AfterThrowing注解除了value、argNames这两个属性外,还有一个属性:
throwing:指定一个异常形参名,形参可用于访问目标方法抛出的异常 
 
示例:
1 2 3 4 5 6 7 @AfterThrowing (throwing = "e" , pointcut = "pointcut()" )public  void  doAfterThrowing (JoinPoint joinPoint, Throwable e)   { 	System.out.println("============afterThrowing============" ); 	 	 	System.out.println(e.getMessage()); } 
 
需要注意的是,这个AfterThrowing只能用在打印异常信息,不能对抛出的异常做更多处理,也不能针对异常来改变目标方法的返回值。 想要根据异常信息修改目标方法返回值,只能用下面讲的Around。
Around 
可以在执行目标方法前织入,也可以在执行后织入 
可以决定目标方法在什么时候执行,如何执行,可以完全阻止目标方法的执行 
可以修改目标方法的参数值,也可以修改目标方法的返回值 
至少包含一个参数,且第一个参数必须是ProceedingJoinPoint 
在方法体内,调用ProceedingJoinPoint的proceed()方法才会执行目标方法。如果方法体内没有调用这个proceed()方法,则目标方法不会执行 
最后必须把获得的目标方法的返回值,作为@Around方法的返回值return回去(因为如果无返回值的话,将不会继续执行目标方法) 
 
@Around注解属性:value、argNames
第一个示例,没修改目标方法入参:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23   @Around ("pointcut()" )   public  Object doAround (ProceedingJoinPoint joinPoint)  throws  Throwable  { String result = "success" ;        try  {           System.out.println("============around start============" );                      Object[] args = joinPoint.getArgs();           for  (int  i = 0 ; i < args.length; i++) {               System.out.println("第"  + (i+1 ) + "个参数为:"  + args[i]);           }                                 Object re = joinPoint.proceed();           if (re instanceof  String) result = (String) re;           return  result;       } catch  (Throwable e) {            e.printStackTrace();           result = "fail" ;            return  result;       } finally  {       	System.out.println("============around end============" );       }   } 
 
第二个示例,修改了目标方法入参(入参为基本类型和String):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Around ("pointcut()" )public  Object doAround (ProceedingJoinPoint joinPoint)  throws  Throwable  {	String result = "success" ;      try  {         System.out.println("============around start============" );                  Object[] args = joinPoint.getArgs();         for  (int  i = 0 ; i < args.length; i++) {                          args[i] += "updated" ;         }                           Object re = joinPoint.proceed(args);          if (re instanceof  String) result = (String) re;         return  result;     } catch  (Throwable e) {          e.printStackTrace();         result = "fail" ;          return  result;     } finally  {     	System.out.println("============around end============" );     } } 
 
第三个示例,修改了目标方法入参(入参为对象):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Around ("pointcut()" )public  Object doAround (ProceedingJoinPoint joinPoint)  throws  Throwable  {	String result = "success" ;      try  {         System.out.println("============around start============" );                  Object[] args = joinPoint.getArgs();         Object param = args[0 ];         Person person = new  Person();         if (param instanceof  Person)         	person = (Person) param;         person.setAddress("address" );                            Object re = joinPoint.proceed();          if (re instanceof  String) result = (String) re;         return  result;     } catch  (Throwable e) {          e.printStackTrace();         result = "fail" ;          return  result;     } finally  {     	System.out.println("============around end============" );     } } 
 
注意 ,关于Around中的异常捕获:
只有从目标方法中抛出的异常才会被捕获,若目标方法内自己try-catch异常了没有抛出,就不会触发Around的异常捕获 
Around中处理异常,返回的响应类型必须和目标方法声明的一致,即必须是目标方法的返回类及其子类,否则会出现强制转换报错 
 
AOP原理 AOP的全称是Aspect Oriented Programming,面向切面编程,它是通过动态代理技术,将Aspect方法中的逻辑完整织入到切入点中。下面开始先介绍静态代理(即设计模式中的代理模式),再介绍动态代理,最后再说说Spring是怎么用动态代理实现AOP的。
静态代理 在设计模式中,常用到代理模式,它一般由一个接口和这个接口的两个实现类组成。 接口:
1 2 3 public  interface  Service   {	public  void  printMessage (String msg)  ; } 
 
被代理类:
1 2 3 4 5 6 public  class  ServiceImpl  implements  Service   {	@Override  	public  void  printMessage (String msg)   { 		System.out.println("message: "  + msg); 	} } 
 
代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public  class  ServiceProxy  implements  Service   {	private  Service service;  	public  ServiceProxy (Service service)   { 		super (); 		this .service = service; 	} 	@Override  	public  void  printMessage (String msg)   { 		doBefore(); 		this .service.printMessage(msg); 		doAfter(); 	} 	 	 	private  void  doBefore ()   { 		System.out.println("========proxy start========" ); 	} 	 	private  void  doAfter ()   { 		System.out.println("========proxy end========" ); 	} } 
 
使用代理类是这样的:
1 2 3 4 5 6 7 public  class  MainTest   {	public  static  void  main (String[] args)   { 		Service service = new  ServiceImpl(); 		ServiceProxy proxy = new  ServiceProxy(service); 		proxy.printMessage("hello" ); 	} } 
 
可以看出,静态代理需要我们自己编写代理类ServiceProxy,在它的printMessage方法中加上前置处理和后置处理。而动态代理,就不用我们自己写代理类,就能把我们指定的前置后置处理方法加到被代理方法的前后流程中。下面介绍动态代理的实现。
动态代理 动态代理,简单地说,就是不用自己写代理类,而是在运行时 自动生成代理类和代理类对象。 接口Service和实现类ServiceImpl代码同上,不再赘述。
动态代理类: 作用:在运行时生成被代理类对象,规定执行被代理对象的目标方法的流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public  class  DynamicProxy  implements  InvocationHandler   {	 	private  Object _obj = null ;  	 	public  DynamicProxy (Object _obj)   { 		super (); 		this ._obj = _obj; 	} 	 	 	@Override  	public  Object invoke (Object proxy, Method method, Object[] args)  throws  Throwable  { 		 		return  method.invoke(_obj, args);  	} } 
 
使用动态代理类是这样的:
1 2 3 4 5 6 7 8 9 10 public  class  MainTest   {	public  static  void  main (String[] args)   { 		Service service = new  ServiceImpl(); 		DynamicProxy dynamicProxy = new  DynamicProxy(service);  		 		Service proxy = (Service) Proxy.newProxyInstance(service.getClass().getClassLoader(), 				service.getClass().getInterfaces(), dynamicProxy);  		proxy.printMessage("hello" );  	} } 
 
以上代码很简单地实现了动态代理:自己不用写Service类的代理类,运行时才生成Service类的代理类和代理对象。
Proxy.newProxyInstance 生成代理类的原理在这篇博客: https://blog.csdn.net/weixin_45505313/article/details/106399906  大致的原理就是,代理类由代理类工厂 ProxyClassFactory 调用 native 方法生成代理类的 .class 文件,放入内存,这样不需要重复生成。生成的代理类的名字是‘包名+$Proxy+id’
 
当然AOP的实现没有这么简单,它还需要把切面类织入到切入点中。
AOP的动态代理 以下代码可以简单实现AOP逻辑。 接口Service和实现类ServiceImpl代码同上,不再赘述。
切面接口:
1 2 3 4 5 6 7 8 9 public  interface  Aspect   {	public  void  doBefore ()  ; 	public  void  doAfter ()  ; 	public  void  doAfterReturning ()  ; 	public  void  doAfterThrowing ()  ; 	public  Object doAround (Object target, Method method, Object[] args)  			throws  IllegalAccessException, IllegalArgumentException, InvocationTargetException ;	public  boolean  useAround ()  ; } 
 
切面类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public  class  AspectImpl  implements  Aspect  {	@Override  	public  void  doBefore ()   { 		System.out.println("========doBefore========" ); 	} 	 	@Override  	public  void  doAfter ()   { 		System.out.println("========doAfter========" ); 	} 	 	@Override  	public  void  doAfterReturning ()   { 		System.out.println("========doAfterReturning========" ); 	} 	 	@Override  	public  void  doAfterThrowing ()   { 		System.out.println("========doAfterThrowing========" ); 	} 	 	@Override  	public  Object doAround (Object target, Method method, Object[] args)  			throws  IllegalAccessException, IllegalArgumentException, InvocationTargetException  {		System.out.println("========doAround Start========" ); 		Object result = method.invoke(target, args); 		System.out.println("========doAround End========" ); 		return  result; 	} 	@Override  	public  boolean  useAround ()   { 		return  true ; 	} } 
 
动态代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public  class  DynamicProxy  implements  InvocationHandler   {	private  Object _obj = null ;  	private  Aspect _aspect;  	public  Object getProxy (Object _obj, Aspect _aspect)   { 		this ._obj = _obj; 		this ._aspect = _aspect; 		Object proxy = Proxy.newProxyInstance(_obj.getClass().getClassLoader(), _obj.getClass().getInterfaces(), this );  		return  proxy; 	} 	 	@Override  	public  Object invoke (Object proxy, Method method, Object[] args)  throws  Throwable  { 		boolean  hasException = false ; 		Object result = null ; 		try  { 			_aspect.doBefore(); 			if  (_aspect.useAround()) { 				result = _aspect.doAround(_obj, method, args); 			} else  { 				result = method.invoke(_obj, args); 			} 		} catch  (Exception e) { 			hasException = true ; 		} 		_aspect.doAfter(); 		if  (hasException) { 			_aspect.doAfterThrowing(); 		} else  { 			_aspect.doAfterReturning(); 		} 		return  result; 	} } 
 
使用动态代理类是这样的:
1 2 3 4 5 6 7 8 public  class  MainTest   {	public  static  void  main (String[] args)   { 		Service service = new  ServiceImpl(); 		DynamicProxy dynamicProxy = new  DynamicProxy(); 		Service proxy = (Service) dynamicProxy.getProxy(service, new  AspectImpl()); 		proxy.printMessage("hello" ); 	} } 
 
打印结果:
1 2 3 4 5 6 ========doBefore======== ========doAround Start======== message: hello ========doAround End======== ========doAfter======== ========doAfterReturning======== 
 
可以看出,以上代码成功把切面类AspectImpl的逻辑织入到了被代理对象的目标方法中。因此,Spring AOP的原理就是,Spring中有一个类似于动态代理类DynamicProxy的类,帮我们把切面类织入到切入点了。
Spring中已实现的AOP Spring中有一些已经写好的切面逻辑,可以直接拿来用。
@ControllerAdvice 用@ControllerAdvice注解修饰的类,可以对Controller中用@RequestMapping修饰的方法做切面处理,最常用的是统一处理Controller方法抛出的异常。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 @ControllerAdvice public  class  ControllerExceptionHandler   {	 	@ResponseBody   	@ExceptionHandler (value = Exception.class ) // 指定抛出的异常类型  	public  JSONObject  handleException (Exception  ex )  { 		System.out.println("=============ControllerExceptionHandler===============" ); 		JSONObject json = new  JSONObject(); 		json.put("code" , "9999" ); 		json.put("msg" , "System Error" ); 		return  json; 	} } 
 
这个示例是指,当Conrtroller方法中抛出异常时,统一返回{“msg”:”System Error”,”code”:”9999”} 响应体。 若需要统一跳到某个页面,可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @ControllerAdvice public  class  ControllerExceptionHandler   {	     @ExceptionHandler (value = Exception.class ) // 指定抛出的异常类型      public  ModelAndView  handleException (Exception  ex )  {     	System.out.println("=============ControllerExceptionHandler===============" );         ModelAndView mav = new  ModelAndView();                  mav.setViewName("error" );         mav.addObject("code" , ex.getCode());         mav.addObject("msg" , ex.getMsg());         return  mav;     } } 
 
注意 :当AOP切面和@ControllerAdvice同时存在,且AOP切面里也会统一处理Controller抛出的异常,@ControllerAdvice就不一定会执行。请看下面的例子。 AOP切面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Component @Aspect public  class  MainAspect   {	     @Around ("@annotation(log)" )     public  Object doAround (ProceedingJoinPoint joinPoint, Log log)  throws  Throwable  {     	JSONObject result = new  JSONObject();         try  {             System.out.println("============around start============" );                          Object re = joinPoint.proceed();             if (re instanceof  JSONObject)             	result = (JSONObject) re;             return  result;         } catch  (Throwable e) {              e.printStackTrace();             result.put("code" , "9998" );             result.put("msg" , "error" );             return  result;         } finally  {         	System.out.println("============around end============" );         }     } } 
 
@ControllerAdvice的修饰类同上,不再赘述。 运行时发现,目标方法抛出异常时,会统一返回{“msg”:”error”,”code”:”9998”} ,而不会返回@ControllerAdvice中的{“msg”:”System Error”,”code”:”9999”} 。原因就是AOP中已经处理好异常了。 若仍然想把异常交给@ControllerAdvice处理,AOP切面可以这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Component @Aspect public  class  MainAspect   {	     @Around ("@annotation(log)" )     public  Object doAround (ProceedingJoinPoint joinPoint, Log log)  throws  Throwable  {     	JSONObject result = new  JSONObject();         try  {             System.out.println("============around start============" );                          Object re = joinPoint.proceed();             if (re instanceof  JSONObject)             	result = (JSONObject) re;             return  result;         } catch  (Throwable e) {              e.printStackTrace();             throw  e;          } finally  {         	System.out.println("============around end============" );         }     } } 
 
这样,目标方法抛出异常时,会统一返回@ControllerAdvice中的{“msg”:”System Error”,”code”:”9999”} 。原因就是AOP中把异常抛出了。
404异常处理 没有什么特殊配置的情况下,Spring Boot遇到404就会自动跳到Spring Boot的error页面。若想要自己处理404异常,可以使用@ControllerAdvice。顺便一提,由于接口不存在,所以404异常肯定不会被AOP切面处理。 要处理404异常,必须在配置文件中加上:
1 2 3 4 spring.mvc.throw-exception-if-no-handler-found:  true spring.resources.add-mappings:  false 
 
这个配置一定要加,否则Spring Boot总是会帮我们处理404异常,而不会进入我们定义的方法中。 然后就是在@ControllerAdvice中处理404,示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @ControllerAdvice public  class  ControllerExceptionHandler   {	@ResponseBody  	@ExceptionHandler (value = Exception.class )  	public  JSONObject  handleException (Exception  ex )  {		System.out.println("=============ControllerExceptionHandler===============" ); 		JSONObject json = new  JSONObject(); 		if (ex instanceof  NoHandlerFoundException) {  			json.put("code" , "9997" ); 			json.put("msg" , "No Found" ); 			return  json; 		} 		json.put("code" , "9999" ); 		json.put("msg" , "System Error" ); 		return  json; 	} } 
 
运行后可以发现,访问不存在的URI时,会统一返回{“msg”:”No Found”,”code”:”9997”} 。
@Transactional 这个注解帮你实现了数据库事务逻辑,尤其是事务回滚。 举个例子,删除用户时,要把用户关联的权限一起删除,当删除用户成功但删除权限不成功时,应该把删除用户的操作回滚,使得数据一致。 服务层示例:
1 2 3 4 5 6 7 8 9 10 11 12 @Service public  class  MaintainService  implements  IMaintainService  {	@Resource  	private  PersonMapper personMapper; 	 	@Override  	@Transactional   	public  void  deletePerson (int  personId)   { 		personMapper.deleteById(personId); 		personMapper.deletePersonPrivilege(personId); 	} } 
 
以上代码,当deletePersonPrivilege方法抛出异常时,deleteById方法会被回滚,即数据库中用户和用户权限依然存在,说明回滚成功。另外,回滚成功后,依然会进入AOP切面中的异常捕获。 下面介绍@Transactional注解使用的注意点。
@Transactional只能用在public方法上 Spring在回滚前会检查方法修饰符是不是public,是才回滚。
@Transactional的rollbackFor 属性 默认情况下,只有抛出Error类,或RuntimeException类及其子类的异常,Spring才会回滚。其他类型的异常不会回滚。 若需要在抛出其他异常时回滚,可以指定rollbackFor 属性,如:
1 2 3 4 5 @Transactional (rollbackFor=Exception.class ) public  void  deletePerson (int  personId )  {	personMapper.deleteById(personId); 	personMapper.deletePersonPrivilege(personId); } 
 
这样,只有抛出Exception类及其子类的异常,Spring才会回滚。
自调用问题 上面的示例,@Transactional注解修饰的方法会直接被Controller层接口调用,这种情况下都能回滚成功。但也有一些特殊情况。 情况1:
1 2 3 4 5 6 7 8 9 public  void  deletePerson (int  personId)   {	doDeletePerson(personId); } @Transactional public  void  doDeletePerson (int  personId)   {	personMapper.deleteById(personId); 	personMapper.deletePersonPrivilege(personId); } 
 
这种情况下,@Transactional不会生效。应改为下面这样才能生效:
1 2 3 4 5 6 7 8 9 @Transactional public  void  deletePerson (int  personId)   {	doDeletePerson(personId); } public  void  doDeletePerson (int  personId)   {	personMapper.deleteById(personId); 	personMapper.deletePersonPrivilege(personId); } 
 
情况2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public  void  deletePerson (int  personId)   {	new  Thread(new  Runnable() {  		@Override  		public  void  run ()   { 			doDeletePerson(personId); 		} 	}).start(); } @Transactional public  void  doDeletePerson (int  personId)   {	personMapper.deleteById(personId); 	personMapper.deletePersonPrivilege(personId); } 
 
以及
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Transactional public  void  deletePerson (int  personId)   {	new  Thread(new  Runnable() {  		@Override  		public  void  run ()   { 			doDeletePerson(personId); 		} 	}).start(); } public  void  doDeletePerson (int  personId)   {	personMapper.deleteById(personId); 	personMapper.deletePersonPrivilege(personId); } 
 
这种有异步线程存在的情况下,@Transactional无论加在哪个方法都不会生效。 因此,只有把@Transactional注解加在直接被外部调用的方法才能生效。
Spring AOP和AspectJ的关系 AspectJ是一个独立的AOP框架,它有自己的一套语法用来实现AOP,用它的语法写的代码文件是.aj文件,还有自己的编译器ajc(Java编译器是javac),负责把.aj编译为.class文件。AspectJ在编译时就生成了代理类,所以它是静态代理。Spring的切入点只能是方法,但AspectJ可以用于字段、类等等,它的实现比Spring AOP要复杂的多。
在我们上面的实践中,都是用注解来编写切面类,这些注解是AspectJ的jar包提供的,但对于AOP功能的实现,用的是JDK或CGLIB的动态代理。因此,Spring AOP和AspectJ的关系就是,Spring用到了AspectJ的注解,但没有用它的语法和编译器,也没有用AspectJ的静态代理来实现功能。
JDK和CGLIB 
JDK动态代理。只能代理实现了接口的类。上面代码所展现的就是这个机制。它是用Proxy.newProxyInstance()方法生成代理类,用InvocationHandler向代理类织入AOP逻辑 
CGLIB动态代理。不要求被代理类必须实现接口,但不能代理final类。它是用Enhancer类创建被代理类的子类 作为代理类,底层是用字节码创建子类,用MethodInterceptor向子类织入AOP逻辑 
 
在SpringBoot2.0之前,Spring默认用JDK动态代理,只有当被代理类没有实现接口时,Spring才用CGLIB动态代理。在SpringBoot2.0之后,无论被代理类是否实现接口,Spring默认都用CGLIB动态代理。
想要自行实现CGLIB动态代理,看这篇博客:https://www.jianshu.com/p/13fa41aa18d8 
 
JDK为什么只能代理实现了接口的类?
 
JDK用Proxy.newProxyInstance()方法生成的代理类类似于:
1 public  final  class  $Proxy0  extends  Proxy  implements  HelloService  {...}
 
因为既要继承Proxy,又要继承接口,所以另一个只能是接口。
SpringBoot2.0之后,为什么默认用CGLIB动态代理?
 
官方的回答是,We’ve generally found cglib proxies less likely to cause unexpected cast exceptions.他们认为使用cglib更不容易出现转换错误。 如果我们的代码写成了:
1 2 @Autowired UserServiceImpl userService; 
 
这个时候,如果是JDK动态代理,那在启动时就会报错:因为JDK动态代理是基于接口的,代理生成的对象只能赋值给接口类型。CGLIB就不会报错 如果想设置默认使用JDK动态代理,可以加上配置项spring.aop.proxy-target-class=false。
性能问题 
JDK和CGLIB都是在运行期生成代理类的字节码。区别在于,JDK是直接写Class字节码,CGLIB使用ASM框架写Class字节码,CGLIB代理实现更复杂,生成代理类的效率比JDK低。 
JDK调用代理方法,是通过反射机制调用。CGLIB是通过FastClass机制直接调用方法,CGLIB执行效率更高。但,在JDK1.8后,Java的反射调用效率有所改善,整体的动态代理速度已经可以和CGLIB媲美了。 
 
什么是FastClass机制?
 
FastClass机制的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。
JDK1.8后,Java的反射调用效率有哪些改善?
 
在1.8之前,JVM把缓存放在Class的属性SoftReference reflectionData。这个属性的类型是SoftReference(软引用),所谓软引用就是在资源紧张的情况下GC会进行回收,这就可能导致缓存丢失。SoftReference的泛型是ReflectionData,ReflectionData就是缓存数据的真正格式。ReflectionData将所有的Constructor、Method、Field对象存储下来供反射进行使用。 
在1.8,放弃使用ReflectionData存储,而是在Class中直接将Constructor、Method、Field数组放进软引用中作为缓存。这样就将缓存分散,当资源紧张时,缓存不会全部被GC回收。
SpringBoot使用CGLIB创建代理类的原理 https://blog.csdn.net/weixin_45505313/article/details/103495439 
https://blog.csdn.net/weixin_43732955/article/details/99196229 
使用CGLIB实现AOP https://www.jianshu.com/p/7cc8ffe4372b 
一、注册 AOP 的自动配置类AopAutoConfiguration,该配置类中配置 Bean 时使用@EnableAspectJAutoProxy注解,该注解 import 类AspectJAutoProxyRegistrar,该类中手动注册BeanAnnotationAwareAspectJAutoProxyCreator
二、由于 AnnotationAwareAspectJAutoProxyCreator 实现了 BeanPostProcess 接口,所以在每个 Bean 初始化后,会经过它的postProcessAfterInitialization方法,在这个方法中调用wrapIfNecessary方法。在 wrapIfNecessary 方法中为当前 Bean 创建代理类()。
扫描切面类的方法最终位于BeanFactoryAspectJAdvisorsBuilder.buildAspectJAdvisors(),它先扫描出项目中的几乎所有Bean,一个一个判断它是否是切面Bean(判断其是否有@Aspect注解),是就调用AspectJAdvisorFactory.getAdvisors(),取这个切面Bean中的所有切面方法,一个切面方法就封装成一个 Advisor。最后的结果就是,BeanFactoryAspectJAdvisorsBuilder 将所有切面Bean的所有切面方法,封装成好几个 Advisor 并返回(并且有做缓存,不会出现找到了一个bean的) 
得到 BeanFactoryAspectJAdvisorsBuilder 返回的好几个 Advisor 后,会筛选出匹配当前 Bean 的几个 Advisor 
调用ProxyFactory.getProxy()创建代理类(ProxyFactory 中已保存了前面得到的 Advisor),使用的是ObjenesisCglibAopProxy(调用其构造函数时,ProxyFactory 把自己作为参数传入了,所以 Advisor 也传入了),它继承自CglibAopProxy,所以创建代理类的方法就是CglibAopProxy.getProxy 
 
CGLIB 代理类原理简述 CGLIB 有个类Enhancer,它可以保存