乐趣区

关于java:AOP面向切面

AOP 是什么?

AOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,它是面向对象编程 (OOP) 的一种补充和欠缺。它以通过预编译形式和运行期动静代理形式,实现在不批改源代码的状况下给程序动静对立增加额定性能的一种技术。

AOP 和 OOP

AOP 与 OOP 字面意思相近,但其实两者齐全是面向不同畛域的设计思维。理论我的项目中咱们通常将面向对象了解为一个动态过程 (例如一个零碎有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理形式,了解为一个动静过程,能够在对象
运行时动静织入一些扩大性能或管制对象执行

AOP 利用场景剖析?

理论我的项目中通常会将零碎分为两大部分,一部分是外围业务,一部分是非核业务。在
编程实现时咱们首先要实现的是外围业务的实现,非核心业务个别是通过特定形式切入到零碎中,这种特定形式个别就是借助 AOP 进行实现。

AOP 就是要基于 OCP(开闭准则),在不扭转原有系统核心业务代码的根底上动静增加一些扩大性能并能够 ” 管制 ” 对象的执行。例如 AOP 利用于我的项目中的日志解决,事务处理,
权限解决,缓存解决等等。

Spring AOP 利用原理剖析(先理解)?

Spring AOP 底层基于代理机制 (动静形式) 实现性能扩大:
1) 如果指标对象 (被代理对象) 实现接口,则底层能够采纳 JDK 动静代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口)。
2) 如果指标对象 (被代理对象) 没有实现接口,则底层能够采纳 CGLIB 代理机制为指标对象创立代理对象(默认创立的代理类会继承指标对象类型)
Spring AOP 原理剖析,如图 -3 所示:

阐明:Spring boot2.x 中 AOP 当初默认应用的 CGLIB 代理, 如果须要应用 JDK 动静
代理能够在配置文件 (applicatiion.properties) 中进行如下配置:

spring.aop.proxy-target-class=false

Spring 中 AOP 相干术语剖析

▪ 切面 (aspect): 横切面对象, 个别为一个具体类对象(能够借助 @Aspect 申明)。
▪ 通 知 (Advice): 在 切 面 的 某 个 特 定 连 接 点 上 执 行 的 动 作 (扩 展 功 能),例 如 around,before,after 等。
▪ 连接点(joinpoint): 程序执行过程中某个特定的点,个别指向被拦挡到的指标办法。
▪ 切入点(pointcut): 对多个连接点(Joinpoint) 一种定义, 个别能够了解为多个连接点的集 合。

连接点与切入点定义如图 -4 所示:

阐明:咱们能够简略的将机场的一个安检口了解为连接点,多个安检口为切入点,安全检查过程看成是告诉。总之,概念很艰涩难懂,多做例子,做完就会清晰。先能够按文言去了解。

Spring AOP 疾速实际

业务形容

基于我的项目中的外围业务,增加简略的日志操作,借助 SLF4J 日志 API 输入指标办法的执行时长。(前提,不能批改指标办法代码 - 遵循 OCP 准则)

我的项目创立及配置

创立 maven 我的项目或在已有我的项目根底上增加 AOP 启动依赖:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

阐明:基于此依赖 spring 能够整合 AspectJ 框架疾速实现 AOP 的根本实现。AspectJ 是一个面向切面的框架,他定义了 AOP 的一些语法,有一个专门的字节码生成器来生成恪守 java 标准的 class 文件。

扩大业务剖析及实现

将此日志切面类作为外围业务加强(一个横切面对象)类,用于输入业务执行时长,
其要害代码如下:

package com.cy.pj.common.aspect;
@Aspect
@Slf4j
@Component
public class SysLogAspect {@Pointcut("bean(sysUserServiceImpl)")
public void doLogPointCut() {}
@Around("doLogPointCut()")
public Object around(ProceedingJoinPoint jp)
throws Throwable{
try {log.info("start:{}"+System.currentTimeMillis());
 Object result=jp.proceed();// 最终会调用指标办法
 log.info("after:{}"+System.currentTimeMillis());
 return result; }catch(Throwable e) {log.error("after:{}",e.getMessage());
 throw e; } } }

阐明:
▪ @Aspect 注解用于标识或者形容 AOP 中的切面类型,基于切面类型构建的对象用于为指标对象进行性能扩大或控制目标对象的执行。
▪ @Pointcut 注解用于形容切面中的办法,并定义切面中的切入点(基于特定表达式的形式进行形容),在本案例中切入点表达式用的是 bean 表达式,这个表达式以 bean 结尾,bean 括号中的内容为一个 spring 治理的某个 bean 对象的名字。
▪ @Around 注解用于形容切面中办法,这样的办法会被认为是一个盘绕告诉(外围业务办法执行之前和之后要执行的一个动作),@Aournd 注解外部 value 属性的值为一个切入点表达式或者是切入点表达式的一个援用 (这个援用为一个 @PointCut 注解
形容的办法的办法名)。
▪ ProceedingJoinPoint 类为一个连接点类型,此类型的对象用于封装要执行的指标方
法相干的一些信息。只能用于 @Around 注解形容的办法参数。

当咱们切入点引入不正确时,会呈现如图所示谬误:

业务切面测试实现

启动我的项目测试或者进行单元测试,其中 Spring Boot 我的项目中的单元测试代码如下:

@SpringBootTest
public class AopTests {
@Autowired
private SysUserService userService;
@Test
public void testSysUserService() {
PageObject<SysUserDeptVo> po=
userService.findPageObjects("admin",1);
System.out.println("rowCount:"+po.getRowCount());
} }

对于测试类中的 userService 对象而言, 它有可能指向 JDK 代理, 也有可能指向 CGLIB 代理, 具体是什么类型的代理对象, 要看 application.yml 配置文件中的配置

利用总结剖析

在业务利用,AOP 相干对象剖析, 如图 -5 所示:

扩大业务织入加强剖析

基于 JDK 代理形式实现

如果指标对象有实现接口, 则能够基于 JDK 为指标对象创立代理对象, 而后为指标对象
进行性能扩大, 如图 -6 所示:

基于 CGLIB 代理形式实现

如果指标对象没有实现接口 (当然实现了接口也是能够的),能够基于 CGLIB 代理形式
为指标对象织入性能扩大,如图 -7 所示:

Spring AOP 编程加强

告诉类型

在基于 Spring AOP 编程的过程中,基于 AspectJ 框架规范,spring 中定义了五种类型的告诉(告诉 -Advice 形容的是一种扩大业务),它们别离是:
▪ @Before。(指标办法执行之前执行)
▪ @AfterReturning。(指标办法胜利完结时执行)
▪ @AfterThrowing。(指标办法异样完结时执行)
▪ @After。(指标办法完结时执行)
▪ @Around.(重点把握, 指标办法执行前后都能够做业务拓展)(优先级最高)

阐明:在切面类中应用什么告诉,由业务决定,并不是说,在切面中要把所有告诉都写上。代码实际剖析如下:

package com.cy.pj.common.aspect;
@Component
@Aspect
public class SysTimeAspect {@Pointcut("bean(sysUserServiceImpl)")
public void doTime(){}
@Before("doTime()")
public void doBefore(){System.out.println("time doBefore()");
}
@After("doTime()")
public void doAfter(){System.out.println("time doAfter()");
}
/** 外围业务失常完结时执行 * 阐明:如果有 after,先执行 after, 再执行
returning*/
@AfterReturning("doTime()")
public void doAfterReturning(){System.out.println("time doAfterReturning");
}
/** 外围业务出现异常时执行阐明:如果有 after,先执行 after, 再执行
Throwing*/
@AfterThrowing("doTime()")
public void doAfterThrowing(){System.out.println("time doAfterThrowing");
}
@Around("doTime()")
public Object doAround(ProceedingJoinPoint jp)
throws Throwable{System.out.println("doAround.before");
 try{Object obj=jp.proceed();
 System.out.println("doAround.after");
 return obj;
}catch(Throwable e){System.out.println(e.getMessage());
 throw e;
 } } }
切入点表达式加强

Spring 中通过切入点表达式定义具体切入点,其罕用 AOP 切入点表达式定义及阐明:

bean 表达式(重点)

bean 表达式个别利用于类级别,实现粗粒度的切入点定义,案例剖析:
▪ bean(“userServiceImpl”)指定一个 userServiceImpl 类中所有办法。
▪ bean(“*ServiceImpl”)指定所有后缀为 ServiceImpl 的类中所有办法。
阐明:bean 表达式外部的对象是由 spring 容器治理的一个 bean 对象, 表达式外部的
名字应该是 spring 容器中某个 bean 的 name。
缺点: 不能准确到具体方法, 也不能针对于具体模块包中的办法做切入点设计

within 表达式(理解)

within 表达式利用于类级别,实现粗粒度的切入点表达式定义,案例剖析:
▪ within(“aop.service.UserServiceImpl”)指定以后包中这个类外部的所有办法。
▪ within(“aop.service.*”) 指定当前目录下的所有类的所有办法。
▪ within(“aop.service..*”) 指定当前目录以及子目录中类的所有办法。
within 表达式利用场景剖析:
1)对所有业务 bean 都要进行性能加强,然而 bean 名字又没有规定。
2)按业务模块 (不同包下的业务) 对 bean 对象进行业务性能加强。

execution 表达式(理解)

execution 表达式利用于办法级别,实现细粒度的切入点表达式定义,案例剖析:
语法:execution(返回值类型 包名. 类名. 办法名 (参数列表))。▪ execution(void aop.service.UserServiceImpl.addUser()) 匹配 addUser 办法。
▪ execution(void aop.service.PersonServiceImpl.addUser(String)) 办法参数必须为
String 的 addUser 办法。
▪ execution( aop.service...*(..)) 万能配置

@annotation 表达式(重点)

@annotaion 表达式利用于办法级别,实现细粒度的切入点表达式定义,案例剖析
▪ @annotation(anno.RequiredLog) 匹配有此注解形容的办法。
▪ @annotation(anno.RequiredCache) 匹配有此注解形容的办法。
其中:RequiredLog 为咱们本人定义的注解, 当咱们应用 @RequiredLog 注解润饰业务
层办法时, 零碎底层会在执行此办法时进行日扩大操作。
定义一 Cache 相干切面, 应用注解表达式定义切入点, 并应用此注解对须要
应用 cache 的业务办法进行形容, 代码剖析如下:
第一步: 定义注解 RequiredCache

package com.cy.pj.common.annotation;
/**
* 自定义注解, 一个非凡的类, 所有注解都默认继承 Annotation 接口
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequiredCache {}

第二步: 定义 SysCacheAspect 切面对象。

package com.cy.pj.common.aspect;
@Aspect
@Component
public class SysCacheAspect {@Pointcut("@annotation(com.cy.pj.common.annotation.RequiredCache)")
 public void doCache() {}
 @Around("doCache()")
 public Object around(ProceedingJoinPoint jp)
 throws Throwable{System.out.println("Get data from cache");
 Object obj=jp.proceed();
 System.out.println("Put data to cache");
 return obj;
 }
 }

第三步: 应用 @RequiredCache 注解对特定业务指标对象中的查询方法进行形容(这里以部门模块的查询方法为例)。

 @RequiredCache
@Override
public List<Map<String, Object>> findObjects() {
….
return list; }

第四步: 进行部门模块的拜访测试, 剖析其后果.

切面优先级设置实现

切面的优先级须要借助 @Order 注解进行形容,数字越小优先级越高,默认优先级比拟低。例如:
定义日志切面并指定优先级。

@Order(1)
@Aspect
@Component
public class SysLogAspect {…}

定义缓存切面并指定优先级:

@Order(2)
@Aspect
@Component
public class SysCacheAspect {…}

阐明:当多个切面作用于同一个指标对象办法时,这些切面会构建成一个切面链,相似过滤器链、拦截器链,其执行剖析如图 -9 所示:

要害对象与术语总结

Spring 基于 AspectJ 框架实现 AOP 设计的要害对象概览,如图 -10 所示:

重难点剖析

▪ AOP 是什么,解决了什么问题,利用场景?
▪ AOP 编程根本步骤及实现过程(以基于 AspectJ 框架实现为例)。
▪ AOP 编程中的外围对象及利用关系。(代理对象,切面对象,告诉,切入点)
▪ AOP 思维在 Spring 中的实现原理剖析。(基于代理形式进行扩大业务的织入)
▪ AOP 编程中基于注解形式的配置实现。(@Aspect,@PointCut,@Around,…)

FAQ 剖析

▪ 什么是 OCP 准则(开闭准则)?
▪ 什么是 DIP 准则 (依赖倒置)?
▪ 什么是繁多职责准则(SRP)?
▪ Spring 中 AOP 的有哪些配置形式?(XML, 注解)
▪ Spring 中 AOP 的告诉有哪些根本类型?(5 种)
▪ Spring 中 AOP 是如何为 Bean 对象创立代理对象的?(JDK,CGLIB)
▪ Spring 中 AOP 切面的执行程序如何指定?(@Order)

Bug 剖析

退出移动版