在SpringBoot使用AOP

一、AOP是什么

AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需 要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种 散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为 “Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为; 那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手 将这些剖开的切面复原,不留痕迹。

使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横 切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”

实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的 方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。然而殊途同归,实现AOP的技术特性却是相同的,分别为:

1、join point(连接点):是程序执行中的一个精确执行点,例如类中的一个方法。它是一个抽象的概念,在实现AOP时,并不需要去定义一个join point。
2、point cut(切入点):本质上是一个捕获连接点的结构。在AOP中,可以定义一个point cut,来捕获相关方法的调用。
3、advice(通知):是point cut的执行代码,是执行“方面”的具体逻辑。
4、aspect(方面):point cut和advice结合起来就是aspect,它类似于OOP中定义的一个类,但它代表的更多是对象间横向的关系。
5、introduce(引入):为对象引入附加的方法或属性,从而达到修改对象结构的目的。有的AOP工具又将其称为mixin。

上述的技术特性组成了基本的AOP技术,大多数AOP工具均实现了这些技术。它们也可以是研究AOP技术的基本术语。

二、为什么要使用AOP

减少代码量。提高系统的一致性。使用场景例如:
1、日志系统
2、消息发布系统

三、如何使用AOP

1、在pom文件加入依赖

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

2、添加配置文件类

package com.xyp.swaggertoexcel.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
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;

/**
 * @author xuyuanpeng
 * @version 1.0
 * @date 2019-04-28 17:37
 */

@Aspect
@Component
public class HttpRequestAspect {

    private static final Logger log = LoggerFactory.getLogger(HttpRequestAspect.class);

    public static long startTime;
    public static long endTime;

    /*@PointCut注解表示表示横切点,哪些方法需要被横切*/
    /*切点表达式*/
    @Pointcut("execution(public * com.xyp.swaggertoexcel.api.*.*(..))")
    /*切点签名*/
    public void print() {

    }

    @Around("print()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        log.info("around proceed before……");
        pjp.proceed();
        log.info("around proceed after……");
    }

    /*@Before注解表示在具体的方法之前执行*/
    @Before("print()")
    public void before(JoinPoint joinPoint) {
        log.info("前置切面before……");
        startTime = System.currentTimeMillis();
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String requestURI = request.getRequestURI();
        String remoteAddr = request.getRemoteAddr();   //这个方法取客户端ip"不够好"
        String requestMethod = request.getMethod();
        String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("请求url=" + requestURI + ",客户端ip=" + remoteAddr + ",请求方式=" + requestMethod + ",请求的类名=" + declaringTypeName + ",方法名=" + methodName + ",入参=" + args);
    }

    /*@After注解表示在方法执行之后执行*/
    @After("print()")
    private void after() {
        endTime = System.currentTimeMillis() - startTime;
        log.info("后置切面after……");
    }

    /*@AfterReturning注解用于获取方法的返回值*/
    @AfterReturning(pointcut = "print()", returning = "object")
    public void getAfterReturn(Object object) {
        log.info("本次接口耗时={}ms", endTime);
        log.info("afterReturning={}", object.toString());
    }
}

3、输出

2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : around proceed before……
2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 前置切面before……
2019-04-28 17:42:21.751 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 请求url=/api/http/getDateByUrl,客户端ip=0:0:0:0:0:0:0:1,请求方式=POST,请求的类名=com.xyp.swaggertoexcel.api.IHttpProxyService,方法名=getDateByUrl,入参=[Ljava.lang.Object;@6890087
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : around proceed after……
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 后置切面after……
2019-04-28 17:42:21.753 INFO 22620 — [nio-8080-exec-9] c.x.s.aop.HttpRequestAspect : 本次接口耗时=2ms

输出顺序
1、around 方法中的 调用pjp.proceed();之前
2、before方法
3、正式的请求切面的方法
4、around 方法中的 调用pjp.proceed();之后
5、after方法

四、参考

https://www.cnblogs.com/jingzhishen/p/4980551.html
https://blog.csdn.net/fanrenxiang/article/details/80844077

猜你喜欢

转载自blog.csdn.net/m13797378901/article/details/89642974