1、AOP概念

面向切面编程,指扩展功能不修改源代码,将功能代码从业务逻辑代码中分离出来。

主要功能:日志记录,性能统计,安全控制,事务处理,异常处理等。

主要意图:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑代码。

AOP特点:采用横向抽取机制,取代了传统纵向继承体系重复性代码。

        aop全称Aspect Oriented Programming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足。

  通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将定义好的切面通过切入点绑定到业务中,以实现将一些特殊的逻辑绑定到此业务中。

  比如,若是需要一个记录日志的功能,首先想到的是在方法中通过log4j或其他框架来进行记录日志,但写下来发现一个问题,在整个业务中其实核心的业务代码并没有多少,都是一些记录日志或其他辅助性的一些代码。而且很多业务有需要相同的功能,比如都需要记录日志,这时候又需要将这些记录日志的功能复制一遍,即使是封装成框架,也是需要调用之类的。在此处使用复杂的设计模式又得不偿失。所以就需要面向切面出场了。

2、AOP名称

先介绍一些aop的名词,其实这些名词对使用aop没什么影响,但为了更好的理解最好知道一些

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式或者基于@Aspect注解的方式来实现。
  • 连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。
  • 通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
  • 切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。
  • 引入(Introduction):用来给一个类型声明额外的方法或属性(也被称为连接类型声明(inter-type declaration))。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用引入来使一个bean实现IsModified接口,以便简化缓存机制。
  • 目标对象(Target Object):被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。
  • AOP代理(AOP Proxy):AOP框架创建的对象,用来实现切面契约(例如通知方法执行等等)。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
  • 织入(Weaving):把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

       其中重要的名词有:切面,切入点

3、AOP底层实现

AOP底层使用动态代理实现,包括两种方式:

(1)使用JDK动态代理实现

(2)使用cglib来实现

JDK动态代理实现:

只能对实现了接口的类生成代理,而不是针对类,该目标类型实现的接口都将被代理。原理是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。步骤如下:

  1. 定义一个实现接口的InnvocationHandler的类
  2. 通过构造函数,注入被代理类
  3. 实现invoke(Object proxy,Method method,Object[] args)方法
  4. 在主函数中获得被代理类的类加载器
  5. 使用Proxy.newProxyInstance()产生一个代理对象
  6. 通过代理对象调用各种方法

cglib动态代理实现:

针对类实现代理,对是否实现接口无要求。原理是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以被代理的类或方法最好不要声明为final类型。

  1. 定义一个实现了MethodIntercepter接口的类
  2. 实现其intercepter()方法,在其中调用proxy.invokeSuper()

代理方式的选择

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,也可以强制使用cgLib实现AOP
  2. 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

静态代理和动态代理的区别

  1. 静态代理:自己编写创建代理类,然后再进行编译,在程序运行前,代理类的.class文件就已经存在了
  2. 动态代理:在实现阶段不用关心代理谁,而在运行阶段(通过反射机制)才指定代理哪一个对象

4、示例说明

1、pom引入Springboot使用AOP相关的依赖

 

 其中:
cglib包是用来动态代理用的,基于类的代理;
aspectjrt和aspectjweaver是与aspectj相关的包,用来支持切面编程的;
aspectjrt包是aspectj的runtime包;
aspectjweaver是aspectj的织入包;

2、实现一个简单的web请求入口(实现传入name参数,返回“hello xxx”的功能)

 

注意:在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。使用过Spring注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。

因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。

3、定义切面类,实现web层的日志切面

要想把一个类变成切面类,需要两步,
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类

package com.example.aop;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.Arrays;@Aspect@Componentpublic class WebLogAcpect {private Logger logger = LoggerFactory.getLogger(WebLogAcpect.class);/** * 定义切入点,切入点为com.example.aop下的所有函数 */@Pointcut("execution(public * com.example.aop..*.*(..))")public void webLog(){}/** * 前置通知:在连接点之前执行的通知 * @param joinPoint * @throws Throwable */@Before("webLog()")public void doBefore(JoinPoint joinPoint) throws Throwable {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容logger.info("URL : " + request.getRequestURL().toString());logger.info("HTTP_METHOD : " + request.getMethod());logger.info("IP : " + request.getRemoteAddr());logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));}@AfterReturning(returning = "ret",pointcut = "webLog()")public void doAfterReturning(Object ret) throws Throwable {// 处理完请求,返回内容logger.info("RESPONSE : " + ret);}}

以上的切面类通过 @Pointcut定义的切入点为com.example.aop包下的所有函数做切人,通过 @Before实现切入点的前置通知,通过 @AfterReturning记录请求返回的对象。

访问http://localhost:8004/hello” />

参考文章:

社区云-社区文章详情

【spring-boot】spring aop 面向切面编程初接触 – 大招无限 – 博客园