谈起 AOP, 大家都知道是面向切面编程, 但你真的了解 Spring 中的 AOP 吗? Spring AOP,JDK 动态代理, CGLIB,AspectJ 之间又有什么关联和区别?
在 Spring 中 AOP 包含两个概念, 一是 Spring 官方基于 JDK 动态代理和 CGLIB 实现的 Spring AOP; 二是集成面向切面编程神器 AspectJ.Spring AOP 和 AspectJ 不是竞争关系, 基于代理的框架的 Spring AOP 和成熟框架 AspectJ 都是有价值的, 它们是互补的.
Spring 无缝地将 Spring AOP,IoC 与 AspectJ 集成在一起, 从而达到 AOP 的所有能力. Spring AOP 默认将标准 JDK 动态代理用于 AOP 代理, 可以代理任何接口. 但如果没有面向接口编程, 只有业务类, 则使用 CGLIB. 当然也可以全部强制使用 CGLIB, 只要设置 proxy-target-class="true".
AOP 中的术语
通知 (Advice)
Spring 切面可以应用 5 种类型的通知:
前置通知 (Before): 在目标方法被调用之前调用通知功能;
后置通知 (After): 在目标方法完成之后调用通知, 此时不会关心方法的输出是什么;
返回通知 (After-returning): 在目标方法成功执行之后调用通知;
异常通知 (After-throwing): 在目标方法抛出异常后调用通知;
环绕通知 (Around): 通知包裹了被通知的方法, 在被通知的方法调用之前和调用之后执行自定义的行为.
连接点 (Join point)
切点 (Poincut)
切面 (Aspect)
引入 (Introduction)
织入 (Weaving)
这些术语的解释, 其他博文中很多, 这里就不再赘述.
用 2 个例子来说明 Spring AOP 和 AspectJ 的用法
现在有这样一个场景, 页面传入参数当前页 page 和每页展示多少条数据 rows, 我们需要写个拦截器将 page,limit 参数转换成 MySQL 的分页语句 offset,rows.
先看 Spring AOP 实现
1, 实现 MethodInterceptor, 拦截方法
- public class MethodParamInterceptor implements MethodInterceptor {
- @Override
- @SuppressWarnings("unchecked")
- public Object invoke(MethodInvocation invocation) throws Throwable {
- Object[] params = invocation.getArguments();
- if (ArrayUtils.isEmpty(params)) {
- return invocation.proceed();
- }
- for (Object param : params) {
- // 如果参数类型是 Map
- if (param instanceof Map) {
- Map paramMap = (Map) param;
- processPage(paramMap);
- break;
- }
- }
- return invocation.proceed();
- }
- /**
- *
- * @param paramMap
- */
- private void processPage(Map paramMap) {
- if (!paramMap.containsKey("page") && !paramMap.containsKey("limit")) {
- return;
- }
- int page = 1;
- int rows = 10;
- for (Map.Entry entry : paramMap.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue().toString();
- if ("page".equals(key)) {
- page = NumberUtils.toInt(value, page);
- } else if ("limit".equals(key)) {
- rows = NumberUtils.toInt(value, rows);
- }else {
- //TODO
- }
- }
- int offset = (page - 1) * rows;
- paramMap.put("offset", offset);
- paramMap.put("rows", rows);
- }
- }
2, 定义后置处理器, 将方法拦截件加入到 advisor 中. 我们通过注解 @Controller 拦截所有的 Controller,@RestController 继承于 Controller, 所以统一拦截了.
- public class RequestParamPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
- implements InitializingBean {
- private Class validatedAnnotationType = Controller.class;
- @Override
- public void afterPropertiesSet() throws Exception {
- Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
- this.advisor = new DefaultPointcutAdvisor(pointcut, new MethodParamInterceptor());
- }
- }
3, 万事俱备只欠东风, Processor 也写好了, 只需要让 Processor 生效.
- @Configuration
- public class MethodInterceptorConfig {
- @Bean
- public RequestParamPostProcessor converter() {
- return new RequestParamPostProcessor();
- }
- }`
这里有个坑需要注意一下, 如果在配置类中注入业务 Bean.
- @Configuration
- public class MethodInterceptorConfig {
- @Autowired
- private UserService userService;
- @Bean
- public RequestParamPostProcessor converter() {
- return new RequestParamPostProcessor();
- }
- }
启动时, 会出现:
- 2019-11-08 14:55:50.954 INFO 51396 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'sqlSessionFactory' of type [org.apache.ibatis.session.defaults.DefaultSqlSessionFactory] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
- 2019-11-08 14:55:50.960 INFO 51396 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'sqlSessionTemplate' of type [org.mybatis.spring.SqlSessionTemplate] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
- 2019-11-08 14:55:51.109 INFO 51396 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'rememberMapper' of type [com.sun.proxy.$Proxy84] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
- 2019-11-08 14:55:53.406 INFO 51396 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
很多切面失效, 如事务切面. 这是因为注入了自定义的 Bean, 自定义的 Bean 优先级最低, 由最低优先级的 BeanPostProcessor 来加载并完成初始化的. 但为了加载其中的 RequestParamPostProcessor, 导致不得不优先装载低优先级 Bean, 此时事务处理器的 AOP 等都还没完成加载, 注解事务初始化都失败了. 但 Spring 就提示了一个 INFO 级别的提示, 然后剩下的 Bean 由最低优先级的 BeanPostProcessor 正常处理.
AspectJ 方式实现切面
- @Component@Aspect@Slf4jpublic class MethodParamInterceptor {
- @Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
- public void paramAspect() {
- }
- @Before("paramAspect()")
- public void beforeDataSource(JoinPoint joinPoint) {
- Arrays.stream(joinPoint.getArgs()).forEach(paramObject -> {
- if (paramObject instanceof Map) {
- Map parameter = (Map) paramObject;
- processPage(parameter);
- }
- });
- }
- private void processPage(Map paramMap) {
- if (null == paramMap) {
- return;
- }
- if (!paramMap.containsKey("page") && !paramMap.containsKey("limit")) {
- return;
- }
- int page = 1;
- int rows = 10;
- for (Map.Entry entry : paramMap.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue().toString();
- if ("page".equals(key)) {
- page = NumberUtils.toInt(value, page);
- } else if ("limit".equals(key)) {
- rows = NumberUtils.toInt(value, rows);
- }
- }
- int offset = (page - 1) * rows;
- paramMap.put("offset", offset);
- paramMap.put("rows", rows);
- }
- @After("paramAspect()")
- public void afterDataSource(JoinPoint joinPoint) {
- }
- }
从上面两个例子可以对比出 SpringAOP 和 AspectJ 的两种不同用法, 但达到的能力是一样的.
Sping AOP 在组织, 抽象代码场景中更加适合, AspectJ 用于单纯的切面来实现某项功能更加简洁.
[本文是 51CTO 专栏机构 "舟谱数据" 的原创文章, 微信公众号 "舟谱数据 ( id: zhoupudata)"]
戳这里, 看该作者更多好文
来源: http://zhuanlan.51cto.com/art/201911/606983.htm