AOP、Http拦截器的使用

  • 切面的使用【自定义注解 AOP 拦截器】

我的开发过程中遇到大量的日志输出,统一处理方式,这种统一处理,或者日志输出,如果不使用切面等处理方式很容易造成代码冗余,看着很乱。
把一个个的横切关注点放到某个模块中去,称之为切面。那么每一个的切面都能影响业务的某一种功能,切面的目的就是功能增强,如日志切面就是一个横切关注点,应用中许多方法需要日志记录的只需要插入日志的切面即可。
AOP术语
Joinpoint:连接点,被拦截到需要被增强的方法。

Pointcut:切入点,哪些包中的哪些类中的哪些方法,可认为是连接点的集合。

Advice:增强,当拦截到Joinpoint之后,在方法执行的什么时机(when)做什么样(what)的增强。根据时机分为:前置增强、后置增强、异常增强、最终增强、环绕增强

Aspect:切面,Pointcut+Advice,去哪些地方+在什么时机+做什么增强

Target:目标对象,被代理的目标对象

weaving:织入,把Advice加到Target上之后,创建出Proxy对象的过程

Proxy:一个类被AOP织入增强后,产生的代理类

创建切面

@Aspect
@Component
public class AspectDoPost {

	//切面位置:所有controller下的请求【一般打印日志使用】
    @Pointcut("execution(* com.jingchuang.jcos.business.controller.*.*(..))")
    public void log() {
    }
    
    //切面 配置通知
    @Before("log()")         //AfterReturning
    public void before(JoinPoint joinPoint) {
			//从JoinPoint里可以获取到很多东西
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			//获取切入点所在的方法
        	Method method = signature.getMethod();
			HttpServletRequest request = 
			((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}
	
    @AfterReturning(returning = "object", pointcut = "log()")
    public void adAfterReturning(Object object) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        log.info("resopnse=" + JSON.toJSONString(object));
        log.info("==============================end==============================");
    }



    /**
     * 切面位置:自定义注解
     */
    @Pointcut(value = "@annotation(com.jingchuang.jcos.business.annotation.MyRetryableMethod)")
    public void doPostLog() {
    }

    @Around(value = "doPostLog()")
    public Object aroundMethod(ProceedingJoinPoint pjd){
		//ProceedingJoinPoint 也可以基于反射获取很多信息
    }
}

ProceedingJoinPoint 接口类源码中有两个抽象方法;

package org.aspectj.lang;

import org.aspectj.runtime.internal.AroundClosure;

public interface ProceedingJoinPoint extends JoinPoint {
    void set$AroundClosure(AroundClosure var1);

    Object proceed() throws Throwable;

    Object proceed(Object[] var1) throws Throwable;
}

环绕通知 ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,这也是环绕通知和前置、后置通知方法的一个最大区别。环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。

使用切面对Controller层拦截时需要注意

//如果程序有统一返回异常处理的话,并且也在com.**.**.**controller包下这里的父类也是会被切面执行的。
public class LoginController extends BaseController {}

拦截器

拦截器的实现可以通过实现接口HandlerInterceptor。
还是先看源码

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

从上述源码我们可以看到 三个default修饰的接口,三个接口为:
前置处理
preHandle
前置处理返回值为boolean类型,如果返回true,我们可以进入正常的应用,如果返回false,则拦截器目的达到了,拦截掉不应该进入到程序的请求。
后置处理
完成处理
案例:通过拦截器实现对请求参数校验数据。【字段有没有传入】
前提:对接外部数据时,对方是主系统(并未对抛出的异常做处理),我们更希望自己能获取到所有信息,所以请求参数并没有使用@RequestBody去匹配对应的json,或者实体类,因为如果对方传入数据不是json格式,将会抛出运行时异常,而数据没有进入到自己的程序中。
实现设计:使用拦截器,过滤非法数据,用IO读取body数据(当读取数据时如果不写会,正常程序无法再次读取到数据【下标】这里不说原因了,直说解决方式),使用自定义注解实现接口需要验证哪些数据。
一、创建自定义注解,并且定义成运行时生效,Element类型为Method

/**需要验证的参数,isEmptyJson使用String类型也可以*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JsonCheck {

    /**需要校验哪些参数是需要验证为空*/
    public String[] isEmptyJson()  ;

    /**需要校验那些参数是需要校验为空的trim后*/
    public String[] isBlankJson() default "";

    /**方法名称*/
    public String methodName() default "" ;

    /**错误异常抛出样式*/
    public String myException() default "";

}

二、配置拦截的资源

@Configuration
public class ApplicationConfig implements WebMvcConfigurer {

	/**实现拦截*/
    @Autowired
    private JsonCheckInterceptor jsonCheckInterceptor;

    /**
     * 配置拦截器
     *
     * @param registry
     * @author lance
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //json参数拦截
        registry.addInterceptor(jsonCheckInterceptor)
                .addPathPatterns("/lmsApi/LMSCancelReport")
                .addPathPatterns("/lmsApi/MoveStoreEndFromLMS")
                .addPathPatterns("/lmsApi/GetTruckStoreEndStatusFromLMS")
                .excludePathPatterns("/static/**", "/login.html");
    }
}

三、创建BodyReaderHttpServletRequestWrapper
如果不需要从Body里面读取数据,请忽略这一块

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @Author 陈子烨
 * @Date 2019/12/30 12:52
 * @Version 1.0
 **/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    public final String body;

    /**
     *
     * @param request
     */
    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException{
        super(request);
        StringBuilder sb = new StringBuilder();
        InputStream ins = request.getInputStream();
        BufferedReader isr = null;
        try{
            if(ins != null){
                isr = new BufferedReader(new InputStreamReader(ins));
                char[] charBuffer = new char[128];
                int readCount = 0;
                while((readCount = isr.read(charBuffer)) != -1){
                    sb.append(charBuffer,0,readCount);
                }
            }else{
                sb.append("");
            }
        }catch (IOException e){
            throw e;
        }finally {
            if(isr != null) {
                isr.close();
            }
        }

        sb.toString();
        body = sb.toString();
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayIns = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletIns = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return byteArrayIns.read();
            }
        };
        return  servletIns;
    }

}

四、拦截器实现

@Component
public class JsonCheckInterceptor implements HandlerInterceptor {

	@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        try{
            if (handler instanceof HandlerMethod) {
                // 强转
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                // 获取方法
                Method method = handlerMethod.getMethod();
                // 是否有JsonCheck注解
                if (!method.isAnnotationPresent(JsonCheck.class)) {
                    return true;
                }
                // 获取注解内容信息
                JsonCheck jsonCheck = method.getAnnotation(JsonCheck.class);
                if (jsonCheck == null) {
                    return true;
                }

                //开始校验参数 并且封装返回
                String[] isEmptyJson = jsonCheck.isEmptyJson();
                BodyReaderHttpServletRequestWrapper bodyReader = new BodyReaderHttpServletRequestWrapper(request);
                String param = bodyReader.body;
                JSONObject jsonParam = JSON.parseObject(param);
                StringBuffer sb = new StringBuffer();
                boolean bool = true;
                lmsResult result = new lmsResult();
                for(String s:isEmptyJson){
                    if(MyStringUtils.isEmpty(jsonParam.getString(s))){
                        sb.append(jsonCheck.myException()+s+"异常;");
                        bool = false;
                    }
                }
                //可以通过注解来实现一些别的
                //如果是false 插入一条日志。记录返回值
                if(!bool){
                    responseOut(response,result);
                    return bool;
                }

            }
        }catch (Exception e){
        	//自定义处理异常,返回自己写的MyException
            logger.info("拦截验证");
            e.printStackTrace();
        }
        return true;

	}


    /**
     * @param response : 响应请求
     * @param object:  object
     * @return void
     * @Title: out
     * @Description: response输出JSON数据
     **/
    private static void responseOut(ServletResponse response, Object object) {

        try (PrintWriter out =  response.getWriter()){
            response.setContentType("application/json;charset=UTF-8");
            response.setCharacterEncoding("UTF-8");
            out.println(JSONObject.toJSONString(object));
        } catch (Exception e) {
            logger.error("响应出错:{}", e);
            e.printStackTrace();
        }
    }

    private String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = ip.indexOf(",");
            if (index != -1) {
                return ip.substring(0, index);
            } else {
                return ip;
            }
        }
        ip = request.getHeader("X-Real-IP");
        if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
            return ip;
        }
        return request.getRemoteAddr();
    }
}

五、post接口

    @PostMapping(value = "/GetTruckStoreEndStatusFromLMS")
    @ResponseBody
    @JsonCheck(isEmptyJson = {"companyId","storeName","storeCode","time","token","carMark","mainProdListNo","scheduleNo"},
            methodName = "GetTruckStoreEndStatusFromLMS"
    )
    public Result test(String param){
	}

其他:校验参数是否为空,是否特定格式,还是使用官方注解比较好~这种情况只是特殊业务需要我自己写。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值