前言 这篇博客先从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,它可以保存