目录
1.AOP介绍
AOP(Aspect Oriented Programming,面向切面编程),是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是OOP(Object Oriented Programming,面向对象编程)的补充和完善。Spring AOP只实现了方法级别的连接点,在J2EE应用中,AOP拦截到方法级别的操作就已经足够了。在Spring中需要利用Spring AOP实现为IOC和企业服务之间建立联系。
OOP引入封装、继承、多态等概念来建立一种对象层次结构。
AOP则利用一种称为“横切”的技术,剖开对象内部,并将公共行为封装到可重用模块,从而减少重复代码,降低耦合。
底层:实际上,AOP的底层是通过Spring提供的动态代理技术实现的。运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强。
2.常用的动态代理技术
2.1JDK代理:基于接口的动态代理技术
JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能,其原理分为以下几个步骤:
1、创建实现InvocationHandler接口的代理类工厂:在调用Proxy类的静态方法newProxyInstance时,会动态生成一个代理类。该代理类实现了目标接口,并且持有一个InvocationHandler类型的引用。
2、InvocationHandler接口:InvocationHandler是一个接口,它只有一个方法invoke。在代理对象的方法被调用时,JVM会自动调用代理类的invoke方法,并将被调用的方法名、参数等信息传递给该方法。
3、调用代理对象的方法:当代理对象的方法被调用时,JVM会自动调用代理类的invoke方法。在invoke方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。
4、invoke方法调用:在invoke方法中,通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。
2.2cglib代理:基于父类的动态代理技术
cglib(Code Generation Library)是一个基于ASM(Java字节码操作框架)实现的代码生成库,它可以在运行时动态生成目标类的子类作为代理类,并覆盖其中的方法来实现代理功能。与Java自带的JDK动态代理不同,CGlib动态代理可以代理没有实现接口的类。其原理分为以下几个步骤:
1、创建Enhancer对象:Enhancer是CGLIB库中用于动态生成子类的主要类。通过创建Enhancer对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类。
2、设置回调拦截器:在生成代理类时,需要指定拦截器。拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑。在CGLIB中,拦截器需要实现MethodInterceptor接口。
3、创建代理对象:通过调用Enhancer对象的create方法,可以生成一个代理对象。代理对象会继承目标类的方法,并且在调用代理对象的方法时会先调用拦截器的intercept方法,再执行目标方法。
4、调用代理对象:通过调用代理对象的方法,会触发拦截器的intercept方法。在intercept方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事务管理等。
3.Spring AOP术语
Target(目标对象) | 代理的目标对象 |
Proxy(代理) | 一个类被AOP织入增强后,就产生一个结果代 理类 |
Joinpoint(连接点) | 所谓连接点是指那些被拦截到的点。在 spring,这些点指的是方法,因为spring只支持方法类型的连接点 |
Pointcut(切入点) | 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 |
Advice(通知/增强) | 所谓通知是指拦截Joinpoint之后所要做的事情就是通知 |
Aspect(切面) | 是切入点和通知(引介)的结合 |
Weaving (织入) | 是指把增强应用到目标对象来创建新的代 理对象的过程。spring采用动态代理织入,而AspectJ采用编 译期织入和类装载期织入 |
用通俗的语言描述AOP术语:
切面是指封装横切到系统功能类,包含通知和切点
切点定义切面插入到哪些方法上,确定切面使用范围
通知定义了切点处所要执行的程序代码以及执行时机
连接点实在应用执行过程中满足切点范围的具体点
织入是把切面插入到目标对象中
4.切点表达式
语法:
execution([修饰符]返回值类型包名.类名.方法名(参数))
1、访问修饰符可以省略
2、返回值类型、包名、类名、方法名可以使用星号*代表任意
3、包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
4、参数列表可以使用两个点..表示任意个数,任意类型的参数列表
例如:
execution(public void com.iflytek.aop.Target.method())
execution(void com.iflytek.aop.Target.* ( ..))
execution(* com.iflytek.aop.*.*( ..))
execution(* com.iflytek.aop..*.* (..))
execution(* *..*.*(..))
5.通知类型
配置文件方式:
<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
注解方式:
@通知注解("切点表达式")
名称 | 标签 | 注解 | 说明 |
前置通知 | <aop:before> | @Before | 在方法之前执行 |
后置通知 | <aop:after> | @AfterReturning | 在方法后执行,无论方法内部是否抛出异常 |
后置返回通知 | <aop:after-returning> | @Around | 在方法后执行并且方法内部不能抛出异常 |
异常抛出通知 | <aop:after-throwing> | @AfterThrowing | 在方法内部抛出异常时执行 |
环绕通知 | <aop:around> | @After | 在方法之前和之后都通知 |
6.使用AOP
6.1ApplaicationContext.xml方式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启AOP注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<context:component-scan base-package="service"></context:component-scan>
<context:component-scan base-package="controller"></context:component-scan>
</beans>
LogSefvice类:
package service;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@Aspect
public class LogService {
@Pointcut("execution(* service.*.*(..))")
public void pointcut(){}
@Before(value="pointcut()")
public void writeLog(JoinPoint point){
System.out.println("开始执行service,事件:"+ LocalDateTime.now());
String method = point.getSignature().getName();
Object[] args = point.getArgs();
System.out.println("service的方法名:"+method);
System.out.println("用户传递的参数:");
for(Object object : args){
System.out.println(object.toString()+" ");
}
System.out.println("");
}
@AfterReturning(value="pointcut()",returning="val")
public void writeEndLog(Object val){
System.out.println("结束执行service,时间:"+LocalDateTime.now());
System.out.println("返回值为:"+val.toString());
}
@AfterThrowing(value ="pointcut()",throwing = "ex")
public void writeExLog(Exception ex){
System.out.println("方法报了异常,异常为:"+ex.getMessage());
}
}
6.2注解方式
和xml方式不一样的是注解方式是在config配置类上加上注解@EnableAspectJAutoProxy
LogManager类:
package com.cqgcxy.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
@Component
@Aspect
public class LogManager {
@Around("execution(* com.xxxxx.service..*.*(..))")
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
String name = signature.getName();
String declaringTypeName = signature.getDeclaringTypeName();
Object target = pjp.getTarget();
System.out.println(target);
System.out.println(declaringTypeName);
System.out.println(name);
System.out.println(LocalDateTime.now() + "========>" + signature.getName() + "========>" + "被执行了");
Object proceed = pjp.proceed();
System.out.println(LocalDateTime.now() + "========>" + signature.getName() + "========>" + "执行完毕");
return proceed;
}
}
Test结果:
com.cqgcxy.service.impl.PhoneServiceImpl@1e13529a
com.cqgcxy.service.PhoneService
select
2093-11-15T16:00:22.420========>select========>被执行了
全世界都是手机!
2093-11-15T16:00:22.420========>select========>执行完毕
Process finished with exit code 0