AOP、ASPECT、Spring AOP、JDK动态代理、CGLib动态代理
1 AOP介绍
1.1 基本定义
AOP(Aspect Oriented Programming)称为面向切面编程,它是一种编程思想,是对OOP(Object Oriented Programming)的补充,可以进一步提高编程效率,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子
1.2 解释
要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面
举例说明,一般在编写web服务接口时,进入某个接口后会检验当前是否需要登录,有部分接口需要登录才能访问,而有部分接口不需要登录也可以访问
按照正常的逻辑而言,我们会这样做:

但是这种方法存在一个问题,即代码的重复度高,每个接口都要编写相当的检测代码,不利于维护和扩展。因此,为了提高代码的复用性,考虑将所有公共代码抽取成一个公共方法,每个接口调用该方法进行检测,这里有点切面的味道了

第二版的方法相比第一版的重复代码减少了,系统的扩展性和代码的复用性也提高了,但是还是存在问题,即每个接口都要调用该方法,也会存在相同代码。因此,便提出了“切面”的概念,将该方法注入到接口调用的某个地方(切点),因此无需编写调用方法的代码,每次在方法运行前会自动在切点处调用该方法,进一步提升了系统的扩展性,并降低了代码之间的耦合度

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。红框处,就是面向切面编程
1.3 AOP中的相关概念
-
切面(Aspect)
切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。
Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中@Component @Aspect //可以简单地认为, 使用 @Aspect 注解的类就是切面 public class LogAspect { } -
目标对象(Target)
**目标对象指将要被增强的对象,即包含主业务逻辑的类对象。**或者说是被一个或者多个切面所通知的对象,即就是被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码,所有的共有功能等待AOP容器的切入
-
连接点(JoinPoint)
程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:
(1)方法(表示程序执行点,即在哪个目标方法)
(2)相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法
@Before("pointcut()") public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点 } -
切入点(PointCut)
切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知
@Pointcut(value = "@annotation(authorityVerify)") public void pointcut(AuthorityVerify authorityVerify) { //该切入点的匹配规则是所有加了@AuthorityVerify注解的函数,并且通过authorityVerify还可以获得注解中的相关信息 } @Pointcut("execution(* com.remcarpediem.test.aop.service..*(..))") public void pointcut() {//该切入点的匹配规则是com.remcarpediem.test.aop.service包下的所有类的所有函数 } -
通知(Advice)
通知是AOP在特定切入点上执行的增强处理,是拦截到连接点之后要执行的代码,通知可以分为前置通知
Before、后置通知AfterReturning、异常通知AfterThrowing、最终通知After、环绕通知Around五类//@Around说明这是一个环绕通知,环绕通知是指在方法执行前后进行拦截 @Around("pointcut()") public void log(JoinPoint joinPoint) { try { return joinPoint.proceed();//执行方法,环绕通知中必须包含该行代码 } catch (BaseException e) {//即使方法执行报错,也会被环绕通知拦截 throw e; } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable); } } -
织入(Weaving)
**织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。**织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理
-
增强器(Adviser)
Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。 Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口
// AbstractPointcutAdvisor是默认接口 public class LogAdvisor extends AbstractPointcutAdvisor { private Advice advice; // Advice private Pointcut pointcut; // 切入点 @PostConstruct public void init() { // AnnotationMatchingPointcut是依据修饰类和方法的注解进行拦截的切入点。 this.pointcut = new AnnotationMatchingPointcut((Class) null, Log.class); // 通知 this.advice = new LogMethodInterceptor(); } }注:以上代码示例都是基于SpringAop完成

2 AOP实现
AOP前面说过是一种编程思想,因此需要具体的实现方式来实现
AOP的实现主要分为静态代理和动态代理,静态代理的实现方式是AspectJ,而动态代理则以Spring AOP为代表
- AspectJ(静态)
AspectJ是语言级的实现,它扩展了Java语言,定义了AOP语法AspectJ在编译期织入代码,它有一个专门的编译器,用来生成遵守Java字节码规范的class文件,需要在工程外处理,不易于移植- 因为是编译器期执行,在运行时没有开销,所以性能上
AspectJ肯定是强于AOP的 AspectJ是在jdk基础上实现了,不用额外添加jdk外的执行文件
- Spring AOP(动态)
Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器Spring AOP在运行时通过代理的方式织入代码,只支持方法类型的连接点Spring支持对AspectJ的集成

3 Spring AOP实现
Spring AOP实现主要是通过代理类的方式实现,有java动态代理和CGLIB代理两种方式
3.1 CGLIB和Java动态代理的区别
Java动态代理只能够对接口进行代理,不能对普通的类进行代理(因为所有生成的代理类的父类为Proxy,Java类继承机制不允许多重继承)。而CGLIB能够代理普通类Java动态代理使用Java原生的反射API进行操作,在生成类上比较高效;CGLIB使用ASM框架直接对字节码进行操作,在类的执行过程中比较高效
3.2 Spring AOP默认的使用方式
- 如果目标对象没有实现接口,则默认会采用
CGLIB代理 - 如果目标对象实现了接口,默认会采用
Java动态代理
3.3 SpringAOP的具体案例–利用注解避免sql语句重复提交
3.3.1 自定义注解类
@Documented
@Target(ElementType.METHOD) //表明该注解只能放在方法上
@Retention(RetentionPolicy.RUNTIME) //定义注解保留时间
public @interface AvoidRepeatCommit {
/**
* 避免重复提交的时间间隔,默认为1000ms
* */
int repeatTime() default 1000;
}
3.3.2 切面类
@Aspect
@Component
@Slf4j
public class AvoidRepeatCommitAspect {
@Autowired
private RedisCache redisCache;
@Pointcut(value = "@annotation(avoidRepeatCommit)")
public void pointcut(AvoidRepeatCommit avoidRepeatCommit){
}
@Around(value = "pointcut(avoidRepeatCommit)")
public Object doAround(ProceedingJoinPoint joinPoint, AvoidRepeatCommit avoidRepeatCommit) throws Throwable {
//将请求ip、方法名、方法参数封装成hash值,存到redis中,利用redis进行拦截
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String ipAddr = IpUtils.getIpAddr(request);
//获取方法签名
Object[] args = joinPoint.getArgs();
StringBuilder argsSb = new StringBuilder();
for(Object arg : args){
argsSb.append(arg.getClass().getName());
}
Signature signature = joinPoint.getSignature();
String className = signature.getClass().getName();
String methodName = signature.getName();
// 得到类名和方法
String ipKey = String.format("%s#%s#%s", className, methodName, argsSb.toString());
// 转换成HashCode
int ipKeyHashCode = Math.abs(ipKey.hashCode());
String redisIpKey = RedisConstant.AVOID_REPEAT_COMMIT + RedisConstant.REDIS_SEGMENTATION + ipAddr + RedisConstant.REDIS_SEGMENTATION + ipKeyHashCode;
log.info("ipKey={},hashCode={},key={}", ipKey, ipKeyHashCode, redisIpKey);
String repeatVal = redisCache.getCacheObject(redisIpKey);
int repeatTime = avoidRepeatCommit.repeatTime();
if(!StringUtils.isEmpty(repeatVal)){
log.info("请勿重复提交请求");
return R.error("请勿重复提交请求");
}
redisCache.setCacheObject(redisIpKey, StringUtils.getUUID(), repeatTime, TimeUnit.MILLISECONDS);
return joinPoint.proceed();
}
}
3.3.3 使用
在想要使用的方法上加上@AvoidRepeatCommit注解即可,然后Spring AOP会自动实现避免重复sql提交
AOP(面向切面编程)是一种编程思想,用于解决系统层面问题,如日志、事务、权限等。Spring AOP是其在Java中的实现,通过动态代理或CGLIB实现切面的织入。Spring AOP中的核心概念包括切面、连接点、切入点、通知和织入。当目标对象没有实现接口时,Spring AOP会使用CGLIB代理,反之则使用Java动态代理。
1558

被折叠的 条评论
为什么被折叠?



