第二十三节 SpringBoot使用AOP

一、引入依赖

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

二、编写扫描的注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Component
public @interface LogHandler {

    String packageName() default "";

    String methodType() default "";
}

三、编写切面

@Aspect
@Component
public class LogAspect {
}

四、加入切点

        切点就是规定【在哪里】加入通知。扫描带@LogHandler注解的方法

public class LogAspect  ...

    @Pointcut(value = "@annotation(com.tyzhou.aop.LogHandler)")
    public void pointCutName() {
        // do nothing
    }

五、加入通知

        @Before注解织入的是前置通知,还有后置通知与环绕通知。注解的value就是规定用扫描哪个切点

        注:如果要想让request对象在子线程中使用,要在进入线程池之前,调用一次getRequest方法,本质上是调用一次

            RequestContextHolder.setRequestAttributes(requestAttributes, true);

public class LogAspect ...

    @Before(value = "pointCutName()")
    public void operation(JoinPoint joinPoint) {
        HttpServletRequest request = getRequest();
        if (request == null) {
            return;
        }
        LOGGER.info("进入前置通知");
        //被代理的对象
        Object target = joinPoint.getTarget();
        //方法的签名
        final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //被代理的对象的方法反射
        final Method methodTarget = methodSignature.getMethod();
        //方法上的注解
        LogHandler logHandler = methodTarget.getAnnotation(LogHandler.class);

        LOGGER.error("被代理对象的包名:{}", target.getClass().getName());
        LOGGER.error("被代理对象的方法名:{}", methodTarget.getName());
        LOGGER.error("被代理对象的方法参数:{}", Arrays.toString(joinPoint.getArgs()));
        LOGGER.error("本次请求URL:{}", request.getRequestURL());
        LOGGER.error("本次请求者IP来自:{}", getIpAddress(request));
        LOGGER.error("本次操作:{}", logHandler.methodType());
    }

六、WebUtils与获取IP方法

public class WebUtils {

    public static HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) 
                                         RequestContextHolder.currentRequestAttributes();
            //子线程共享同一个Request(在进入线程池之前,要先调用一次本方法,让程序执行到这步)
            RequestContextHolder.setRequestAttributes(requestAttributes, true);
            return requestAttributes.getRequest();

        } catch (IllegalStateException e) {
            return null;
        }
    }

    public static HttpServletResponse getResponse() {
        HttpServletResponse response;
        try {
            response = ((ServletRequestAttributes) 
                          RequestContextHolder.currentRequestAttributes()).getResponse();
        } catch (Exception e) {
            return null;
        }
        return response;
    }
}


public class LogAspect ...
    
    /**
     * 获得客户端IP
     *
     * @param request request
     * @return 客户端IP
     */
    private String getIpAddress(HttpServletRequest request) {
        String unknown = "unknown";
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

七、在某个Controller上加入注解并请求

多线程情况下Request对象丢失解决方案

        RequestContextHodler里面有持有了一个 InheritalbeThreadLocal,InhertitableThreadLocal是能够获取到父线程的存放的线程局部变量。在WebUitls里面调用了setRequestAttributes(X,true)方法。


后记:

        不要在切面中【开线程池】做任何有关于【request】对象的业务,比如记录访问者IP地址 !

        如果主线程执行速度过快,那么在切面的子线程中的request对象可能会突变成null,因为主线程中的请求已经执行完毕,理所当然的释放了本次请求的request与response对象。所以,即使在切面中,我认为尽量不要开启异步线程。不使用线程池的方式,即同步得方式显得更加安全可靠。

源码下载

        本章节项目源码:点我下载源代码

        目录贴:跟着大宇学SpringBoot-------目录帖

posted @ 2022-07-17 12:13  小大宇  阅读(6)  评论(0编辑  收藏  举报