前言
以前在学习spring的时候很少回去关注spring的实现原理,只是单纯的学习spring的运用,这次看到一篇博客学的不错,结合自己在项目中运用到的,总结一下springAOP的实现原理。博客地址1:http://www.cnblogs.com/hongwz/p/5764917.html
博客地址2:https://blog.csdn.net/shendl/article/details/526362
- 什么是AOP?
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
springAOP的概念
- 横切关注点
软件系统可看作由一组关注点组成。其中,直接的业务关注点,是直切关注点。而为直切关注点提供服务的,就是横切关注点,也就是对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。 - 切面(aspect)
横切关注点被模块化为特殊的类,这些类称为切面。其实就是定义一个特殊的切面类。 - 连接点(joinpoint)
连接点是一个应用执行过程中能够插入一个切面的点。因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器,所以运用的时刻应该是调用方法时、抛出异常时、甚至修改字段时。 - 切点(pointcut)
切点其实就是对连接点进行拦截。如果通知定义了“什么”和“何时”。那么切点就定义了“何处”。切点会匹配通知所要织入的一个或者多个连接点,通俗一点就是:通知被应用的位置。 - 通知(advice)
谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。
1、(前置)Before——在方法调用之前调用通知
2、(后置)After——在方法完成之后调用通知,无论方法执行成功与否
3、(最终)After-returning——在方法执行成功之后调用通知
4、(异常)After-throwing——在方法抛出异常后进行通知
5、(环绕)Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
- 目标对象
AOP使用JDK动态代理生成目标对象 - 织入(weave)
1、将切面应用到目标对象并导致代理对象创建的过程
2、切面在指定的连接点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入。
编译期——切面在目标类编译时期被织入,这种方式需要特殊编译器。AspectJ的织入编译器就是以这种方式织入切面。
类加载期——切面在类加载到
JVM ,这种方式需要特殊的类加载器,他可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5 的 LTW 就支持这种织入方式
运行期——切面在应用运行期间的某个时刻被织入。一般情况下,在织入切面时候,AOP 容器会为目标对象动态的创建代理对象。Spring AOP 就是以这种方式织入切面
将切面应用到目标对象并导致代理对象创建的过程
- 引入(introduction)
在不修改代码的前提下,引入允许我们向现有的类中添加方法或属性。
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用JDK动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB,CGLIB原理博客地址:https://blog.csdn.net/yakoo5/article/details/9099133/。
CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
综上所述:AOP编程只需要程序员参与以下三个部分即可:
1、定义普通业务组件
2、定义切入点,一个切入点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作
所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
基于springboot+SSM的统一日志管理场景使用springAOP
1、首先需要往pom.xml文件中添加如下依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
2、编写日志处理类
@Aspect
@Component
public class SysLogUtil {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(public * com.bosssoft.hr.controller.*.*(..))")
public void logging() {}
@Before("logging()")
public void doBefore(JoinPoint joinPoint)throws Throwable{
startTime.set(System.currentTimeMillis());
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
assert attributes != null;
HttpServletRequest request = attributes.getRequest();
// 记录下请求内容
logger.info("请求url : " + request.getRequestURL().toString());
logger.info("请求类型 : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
logger.info("接口包路径 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
logger.info("参数 : " + Arrays.toString(joinPoint.getArgs()));
}
@AfterReturning(returning="ret" ,pointcut="logging()")
public void doAfterReturning(Object ret) throws Throwable{
try{
// 处理完请求,返回内容
logger.info("RESPONSE : " + ret);
logger.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
}finally {
if (startTime!=null){
startTime.remove();
}
}
}
}
注解说明
@Aspect
作用是把当前类标识为一个切面供容器读取
@Before
标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@AfterReturning
后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@AfterThrowing
异常抛出增强,相当于ThrowsAdvice
@After
final增强,不管是抛出异常或者正常退出都会执行
@Around
环绕增强,相当于MethodInterceptor
@DeclareParents
引介增强,相当于IntroductionInterceptor
@Pointcut
定义切点:上述类表示切点定义在com.bosssoft.hr.controller包下的所有方法
execution函数:用于匹配方法执行的连接点
@component (把普通pojo实例化到spring容器中,相当于配置文件中的
<bean id = "" class=""/>
)
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类
日志信息打印如下:
注意:这里提一点,反射机制的作用性。在动态代理的时候就是基于java 的反射机制实现的。同时Spring 中的 IOC 的底层实现原理也是反射机制,Spring 的容器会帮我们创建实例,该容器中使用的方法就是反射,通过解析 xml 文件,获取到 id 属性和 class 属性里面的内容,利用反射原理创建配置文件里类的实例对象,存入到 Spring 的 bean 容器中。