Java 核心技术Spring源码分析

SpringBoot—集成AOP详解(面向切面编程Aspect)

2020-04-12  本文已影响0人  Hughman

AOP介绍

AOP概述

  AOP是Aspect-Oriented Programming,即为面向(切面)方面编程。在维基百科中的解释:Aspect是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点。从关注点中分离出横切关注点是面向切面的程序设计核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑代码不需要再包含针对特定领域问题代码的调用,比如一些公用模块的日志、安全等代码。
  代码通过切面抽离,更加整齐和清晰,将重复的代码抽取出来单独的进行维护,在需要使用的时候,统一调用这些公共模块的代码,这样一个类就是一个基本的模块,方便统一维护和扩展更新。
  AOP就是为业务实现提供了切面注入的一种机制,将定义好的切面通过切入点(pointcut)在业务逻辑中进行绑定。比如SpringBoot微服务中的所有controller层需要对http请求进行一些常规日志的打印,如果每次在controller进行打印,代码就会冗余,如果说将这些公共代码进行封装,也需要每一个controller类进行调用,所以AOP出现的恰到好处,这时候引入AOP对http相关的日志逻辑进行统一管理编写代码,不需要controller层进行调用,只需要创建一个切面,并通过切入点绑定controller即可,下面的示例会讲到。

AOP相关术语

Spring AOP

AOP Advice相关术语

SpringBoot中集成AOP

1)pom.xml引入
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

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

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.1</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.14</version>
        </dependency>
    </dependencies>

其中,关于aop相关的主要引入了支持切面编程的依赖:org.aspectj.aspectjweaverorg.aspectj.aspectjrt的依赖。aspectjweaver是aspectj的织入包,aspectjrt是aspectj的运行时包。

2)配置文件
server:
  context-path: /demo/v1
  port: 9000
3)controller类
package com.example.andya.demo.controller;

import org.springframework.web.bind.annotation.*;

/**
 * @author Andya
 * @create 2020-04-12 10:36
 */
@RestController
@RequestMapping("/aopTest")
public class AopController {

    @RequestMapping(value = "/sayHi/{name}", method = RequestMethod.GET)
    public String sayHi(@PathVariable(value = "name") String name) {
        return "hi, " + name;
    }
}
4)AOP层类
package com.example.andya.demo.aop;

import com.alibaba.fastjson.JSON;
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.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Andya
 * @create 2020-04-12 10:39
 */
@Aspect
@Component
public class WebLogAspect {

    private Logger LOG = LoggerFactory.getLogger(WebLogAspect.class);

    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 定义切入点,以controller下所有包的请求为切入点
     */
    @Pointcut("execution(public * com.example.andya.demo.controller..*.*(..))*")
    public void webLog(){

    }

    /**
     *前置通知:在切入点之前执行的通知
     * @param joinPoint
     * @throws Throwable
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        startTime.set(System.currentTimeMillis());

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();


        //打印请求相关参数
        LOG.info("========================================== Start ==========================================");

        LOG.info("URL:" + request.getRequestURL().toString());

        LOG.info("HTTP_METHOD:" + request.getMethod());


        //header第一种格式展示
        Enumeration<String> enumeration = request.getHeaderNames();
        Map<String, String> headerMap = new HashMap<>();
        while (enumeration.hasMoreElements()) {
            String headerName = enumeration.nextElement();
            headerMap.put(headerName, request.getHeader(headerName));
        }
        String headerJsonStr = JSON.toJSONString(headerMap);
        if (headerJsonStr.length() > 0) {
            LOG.info("HTTP_HEADERS INFO IS: {}", headerJsonStr);
        }

        //header第二种格式展示
        LOG.info("HTTP_HEADERS: ");
        Enumeration<?> enumeration1 = request.getHeaderNames();
        while (enumeration1.hasMoreElements()) {
            String key = (String) enumeration1.nextElement();
            String value = request.getHeader(key);
            LOG.info("     {}: {}", key, value);
        }

        LOG.info("IP:" + request.getRemoteAddr());

        LOG.info("CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        try {
            LOG.info("REQUEST BODY : [{}]", JSON.toJSONString(joinPoint.getArgs()[0]));
//            LOG.info("ARGS:{}", Arrays.toString(joinPoint.getArgs()));
        } catch (Exception e) {
            LOG.error("REQUEST BODY PARSE ERROR!");
        }

        HttpSession session = (HttpSession) servletRequestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
        LOG.info("SESSION ID:" + session.getId());


    }


//    /**
//     * 后置通知
//     * @param ret
//     * @throws Throwable
//     */
//    @AfterReturning(returning = "ret", pointcut = "webLog()")
//    public void doAfterReturning(Object ret) throws Throwable {
//        // 处理完请求,返回内容
//        LOG.info("RESPONSE : " + ret);
//        LOG.info("SPEND TIME : " + (System.currentTimeMillis() - startTime.get()));
//
//    }


    /**
     * 后置最终通知
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        LOG.info("=========================================== End ===========================================");
        // 每个请求之间空一行
        LOG.info("");
    }


    /**
     * 环绕通知
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        String resultStr = JSON.toJSONString(result);
        // 打印出参
        LOG.info("RESPONSE ARGS  : {}", resultStr);
        // 执行耗时
        LOG.info("TIME-CONSUMING : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }

}

其中:

5)运行类
package com.example.andya.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
6)访问url

http://127.0.0.1:9000/demo/v1/aopTest/sayHi/andya

在这里插入图片描述
7)运行结果
在这里插入图片描述
... ...
... ...
... ...
2020-04-12 15:21:31.737  INFO 18548 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2020-04-12 15:21:31.782  INFO 18548 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9000 (http)
2020-04-12 15:21:31.786  INFO 18548 --- [           main] com.example.andya.demo.DemoApplication   : Started DemoApplication in 2.928 seconds (JVM running for 4.26)
2020-04-12 15:21:41.084  INFO 18548 --- [nio-9000-exec-2] o.a.c.c.C.[.[localhost].[/demo/v1]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2020-04-12 15:21:41.085  INFO 18548 --- [nio-9000-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2020-04-12 15:21:41.102  INFO 18548 --- [nio-9000-exec-2] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 17 ms
2020-04-12 15:21:41.126  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : ========================================== Start ==========================================
2020-04-12 15:21:41.126  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : URL:http://127.0.0.1:9000/demo/v1/aopTest/sayHi/andya
2020-04-12 15:21:41.126  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : HTTP_METHOD:GET
2020-04-12 15:21:41.155  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : HTTP_HEADERS INFO IS: {"accept-language":"zh-CN","cookie":"JSESSIONID=1014BD34FFE9D2660CB47B282C63FA7D","host":"127.0.0.1:9000","connection":"Keep-Alive","accept-encoding":"gzip, deflate","accept":"text/html, application/xhtml+xml, image/jxr, */*","user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"}
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : HTTP_HEADERS: 
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      accept: text/html, application/xhtml+xml, image/jxr, */*
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      accept-language: zh-CN
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      accept-encoding: gzip, deflate
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      host: 127.0.0.1:9000
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      connection: Keep-Alive
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  :      cookie: JSESSIONID=1014BD34FFE9D2660CB47B282C63FA7D
2020-04-12 15:21:41.156  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : IP:127.0.0.1
2020-04-12 15:21:41.157  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : CLASS_METHOD:com.example.andya.demo.controller.AopController.sayHi
2020-04-12 15:21:41.157  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : REQUEST BODY : ["andya"]
2020-04-12 15:21:41.160  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : SESSION ID:0357B2624C73C5F79BC977AD628DB45F
2020-04-12 15:21:41.162  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : RESPONSE ARGS  : "hi, andya"
2020-04-12 15:21:41.163  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : TIME-CONSUMING : 37 ms
2020-04-12 15:21:41.163  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : =========================================== End ===========================================
2020-04-12 15:21:41.163  INFO 18548 --- [nio-9000-exec-2] com.example.andya.demo.aop.WebLogAspect  : 

参考书籍
《SPING技术内幕 深入解析SPRING架构与设计原理》
参考官网
Spring AOP官网

上一篇下一篇

猜你喜欢

热点阅读