当前位置:   article > 正文

从零开始SpringCloud Alibaba实战(59)——过滤器filter、拦截器interceptor、和AOP的区别与联系及应用_springcloud alibaba 拦截器

springcloud alibaba 拦截器

前言

在实现一些公共逻辑的时候,很多功能通过过滤器,拦截器,AOP都能实现,但是不同的方式有不同的效率。具体有什么区别,看下文描述。

过滤器

过滤器拦截的是URL

Spring中自定义过滤器(Filter)一般只有一个方法,返回值是void,当请求到达web容器时,会探测当前请求地址是否配置有过滤器,有则调用该过滤器的方法(可能会有多个过滤器),然后才调用真实的业务逻辑,至此过滤器任务完成。过滤器并没有定义业务逻辑执行前、后等,仅仅是请求到达就执行。

特别注意:过滤器方法的入参有request,response,FilterChain,其中FilterChain是过滤器链,使用比较简单,而request,response则关联到请求流程,因此可以对请求参数做过滤和修改,同时FilterChain过滤链执行完,并且完成业务流程后,会返回到过滤器,此时也可以对请求的返回数据做处理。

拦截器

拦截器拦截的是URL

拦截器有三个方法,相对于过滤器更加细致,有被拦截逻辑执行前、后等。Spring中拦截器有三个方法:preHandle,postHandle,afterCompletion。分别表示如下

public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)表示被拦截的URL对应的方法执行前的自定义处理

public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)表示此时还未将modelAndView进行渲染,被拦截的URL对应的方法执行后的自定义处理,。

public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)表示此时modelAndView已被渲染,执行拦截器的自定义处理。

过滤器与拦截器的区别

过滤器是基于函数回调的(职责链),而拦截器则是基于 Java 反射的;
过滤器依赖于 Servlet 容器,而拦截器不依赖于 Servlet 容器;
过滤器对几乎所有的请求起作用,而拦截器只能对 Action 请求起作用;
拦截器可以访问 Action 的上下文,值栈里的对象,而过滤器不能;
在 Action 的生命周期里,拦截器可以被多次调用,而过滤器只能在容器初始化时调用一次。
执行顺序
过滤前 -> 拦截前 -> 业务 -> 拦截后 -> 请求与响应完成 -> 过滤后

AOP(面向切面)

面向切面拦截的是类的元数据(包、类、方法名、参数等)

相对于拦截器更加细致,而且非常灵活,拦截器只能针对URL做拦截,而AOP针对具体的代码,能够实现更加复杂的业务逻辑。

三者使用场景

Filter过滤器

1.过滤器拦截web访问url地址。 严格意义上讲,filter只是适用于web中,依赖于Servlet容器,利用Java的回调机制进行实现。
2.Filter过滤器:和框架无关,可以控制最初的http请求,但是更细一点的类和方法控制不了。
3.过滤器可以拦截到方法的请求和响应(ServletRequest request, ServletResponse response),并对请求响应做出像响应的过滤操作,
比如设置字符编码,鉴权操作等
  • 1
  • 2
  • 3
  • 4

Interceptor拦截器


拦截器拦截以 .action结尾的url,拦截Action的访问。 Interfactor是基于Java的反射机制(APO思想)进行实现,不依赖Servlet容器。
拦截器可以在方法执行之前(preHandle)和方法执行之后(afterCompletion)进行操作,回调操作(postHandle),可以获取执行的方法的名称,请求(HttpServletRequest)
Interceptor:可以控制请求的控制器和方法,但控制不了请求方法里的参数(只能获取参数的名称,不能获取到参数的值)
(用于处理页面提交的请求响应并进行处理,例如做国际化,做主题更换,过滤等)。
  • 1
  • 2
  • 3
  • 4
  • 5

Spring AOP拦截器


只能拦截Spring管理Bean的访问(业务层Service)。 具体AOP详情参照 Spring AOP:原理、 通知、连接点、切点、切面、表达式
实际开发中,AOP常和事务结合:Spring的事务管理:声明式事务管理(切面)
AOP操作可以对操作进行横向的拦截,最大的优势在于他可以获取执行方法的参数( ProceedingJoinPoint.getArgs() ),对方法进行统一的处理。
Aspect : 可以自定义切入的点,有方法的参数,但是拿不到http请求,可以通过其他方式如RequestContextHolder获得(
ServletRequestAttributes servletRequestAttributes= (ServletRequestAttributes) 
常见使用日志,事务,请求参数安全验证等
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Filter与Interceptor联系与区别

拦截器是基于java的反射机制,使用代理模式,而过滤器是基于函数回调。
拦截器不依赖servlet容器,过滤器依赖于servlet容器。
拦截器只能对action起作用,而过滤器可以对几乎所有的请求起作用(可以保护资源)。
拦截器可以访问action上下文,堆栈里面的对象,而过滤器不可以。

执行顺序:过滤前-拦截前-Action处理-拦截后-过滤后。
三者功能类似,但各有优势,从过滤器–》拦截器–》切面,拦截规则越来越细致,执行顺序依次是过滤器、拦截器、切面。一般情况下数据被过滤的时机越早对服务的性能影响越小,因此我们在编写相对比较公用的代码时,优先考虑过滤器,然后是拦截器,最后是aop。比如权限校验,一般情况下,所有的请求都需要做登陆校验,此时就应该使用过滤器在最顶层做校验;日志记录,一般日志只会针对部分逻辑做日志记录,而且牵扯到业务逻辑完成前后的日志记录,因此使用过滤器不能细致地划分模块,此时应该考虑拦截器,然而拦截器也是依据URL做规则匹配,因此相对来说不够细致,因此我们会考虑到使用AOP实现,AOP可以针对代码的方法级别做拦截,很适合日志功能。

从上面对拦截器与过滤器的描述来看,它俩是非常相似的,都能对客户端发来的请求进行处理,它们的区别如下:

作用域不同
过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用
拦截器依赖于spring容器,可以在spring容器中调用,不管此时Spring处于什么环境
细粒度的不同
过滤器的控制比较粗,只能在请求进来时进行处理,对请求和响应进行包装
拦截器提供更精细的控制,可以在controller对请求处理之前或之后被调用,也可以在渲染视图呈现给用户之后调用
中断链执行的难易程度不同
拦截器可以 preHandle方法内返回 false 进行中断
过滤器就比较复杂,需要处理请求和响应对象来引发中断,需要额外的动作,比如将用户重定向到错误页面
拦截器相比过滤器有更细粒度的控制,依赖于Spring容器,可以在请求之前或之后启动,过滤器主要依赖于servlet,过滤器能做的,拦截器基本上都能做。

springboot 对过滤器filter、拦截器interceptor、和AOP的支持

springboot 整合过滤器filter

springboot下过滤器有两种实现方式

1.注解方式
  • 1

使用该过滤器的时候需添加@WebFilter注解,另外还需要@Component注解,将该类作为组件,注入spring容器中。


import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
 

@Slf4j
@Component
@WebFilter(value = "/hello")
public class HelloFilter1 implements Filter {
 
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
            FilterChain filterChain) throws IOException, ServletException {
        log.info("进入到过滤器1啦");
        filterChain.doFilter(servletRequest,servletResponse);
    }
 
    @Override
    public void destroy() {
 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

2.bean注入方式
首先自定义filter,然后在springboot启动类下配置一个过滤的bean,若不在springboot的启动类下配置,在其他的类上使用时,需在类上写上@Configuration,标志这个类是一个配置类。springboot启动类注解@springbootApplication内部含有该注解,所以无需配置。

 
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import lombok.extern.slf4j.Slf4j;
 
 

@Slf4j
public class HelloFilter2 implements Filter {
 
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
 
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
            FilterChain filterChain) throws IOException, ServletException {
        log.info("进入到过滤器2啦");
        //放行至下一个过滤器
        filterChain.doFilter(servletRequest,servletResponse);
    }
 
    @Override
    public void destroy() {
 
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
 
import com.example.demo.filter.HelloFilter1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
 
@SpringBootApplication
public class DemoApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
 
    @Bean
    public FilterRegistrationBean registrationBean(){
        FilterRegistrationBean filter = new FilterRegistrationBean(new HelloFilter2());
        filter.addUrlPatterns("/hello");
        //多个过滤器时执行顺序
        //filter.setOrder(1);
        return filter;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

springboot 整合拦截器interceptor

  1. 拦截器的基本使用
    1.1 拦截器的实现类
    实现Spring的HandlerInterceptor接口;
    继承实现HandlerInterceptor接口的类,例如:HandlerInterceptorAdapter;
    1.2 HandlerInterceptor方法介绍
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception;

    void postHandle(
    HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
    throws Exception;

    void afterCompletion(
    HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
    throws Exception;
    preHandler:业务处理器处理请求之前被调用,对用户的request进行处理,若返回值为true,则继续调用后续的拦截器和目标方法;若返回值为false,则终止请求;这里可以加上登录校验,权限拦截等。
    postHandler:Controller执行后但未返回视图前调用该方法,这里可以对返回用户前的模型数据进行加工处理。
    afterCompletion:Controller执行后且返回视图后调用,可以得到Controller时的异常信息,这里可以记录操作日志,资源清理等。
    1.3 将拦截器加入到MVC中
    在SpringBoot 2.0以上, WebMvcConfigurerAdapter已过时,但是WebMvcConfigurerAdapter实际上是WebMvcConfigurer接口的空实现类。故有两种方法可以实现:推荐使用:implements WebMvcConfigurer

  2. @EnableWebMvc + implements WebMvcConfigurer
    在扩展类中重写父类的方法即可,这种方法会屏蔽springboot对SpringMVC的自动配置。

  3. implements WebMvcConfigurer
    在扩展类中重写父类的方法即可,这种方法会使用springboot对SpringMVC的自动配置。

  4. extends WebMvcConfigurationSupport
    在扩展类中重写父类的方法即可,这种方法会屏蔽springboot对SpringMVC的自动配置。

根据官方文档,尽量不要使用@EnableWebMvc注解,因为他会关闭自动配置。

@Component
public class LogInterceptor implements HandlerInterceptor {
private final static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
//业务处理器处理请求之前被调用,对用户的request进行处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
logger.info(“【拦截器】-【已进入拦截器!!】”);
return true;
}
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}
  • 1

}
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//指定拦截器,指定拦截路径
registry.addInterceptor(logInterceptor).addPathPatterns(“/**”);
}
}
2. 拦截器的自定义注解
创建注解类。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD}) 作用于类、接口等与方法上
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyLimit {
int intVal() default 5;
String stringVal() default “”;
}
创建拦截器类-AnnotationInterceptor

@Component
public class AnnotationInterceptor implements HandlerInterceptor {
private final static Logger logger = LoggerFactory.getLogger(AnnotationInterceptor.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //判断当前被拦截的方法是否含有注解
    boolean assignableFromMethod = handler.getClass().isAssignableFrom(HandlerMethod.class);//[额赛额包] 指定的
    logger.info("当前方法上是否有注解" + assignableFromMethod);
    if (assignableFromMethod) {
        //获取当前方法的自定义注解拦截器的注解
        MyLimit methodAnnotation = ((HandlerMethod) handler).getMethodAnnotation(MyLimit.class);
        logger.info("当前方法的注解:" + methodAnnotation);
        if (methodAnnotation == null) {
            return true;
        }
        //获取注解上的参数
        int intVal = methodAnnotation.intVal();
        String stringVal = methodAnnotation.stringVal();
        logger.info("打印注解上的参数:[intVal={}][stringVal={}]", intVal, stringVal);
    }
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

}
配置拦截器

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
@Resource
private AnnotationInterceptor annotationInterceptor;
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//指定拦截器,指定拦截路径
registry.addInterceptor(logInterceptor).addPathPatterns(“/**”);
registry.addInterceptor(annotationInterceptor);
}
}
使用注解

@RequestMapping(value = "/get", method = RequestMethod.GET)
@ResponseBody
@MyLimit(stringVal = "哈哈,哈哈哈")
public String getUesr(@RequestParam(value = "str", required = false) Integer integer) {
    try {
        if (integer == 2) {
            throw new RuntimeException("请求地址不合法");
        }
    }catch (Exception e){
        logger.error("请求参数不合法",e);
    }
    logger.info("请求参数:str={}",integer);
    SysQuartzJobConfig sysQuartzJobConfig = sysQuartzJobConfigMapper.selectByPrimaryKey(integer);
    return JSON.toJSONString(sysQuartzJobConfig);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

效果:

MyPrefix 16591 INFO com.Interceptor.impl.AnnotationInterceptor - 当前方法上是否有注解true
MyPrefix 16591 INFO com.Interceptor.impl.AnnotationInterceptor - 当前方法的注解:@com.Interceptor.annotation.MyLimit(intVal=5, stringVal=哈哈,哈哈哈)
MyPrefix 16592 INFO com.Interceptor.impl.AnnotationInterceptor - 打印注解上的参数:[intVal=5][stringVal=哈哈,哈哈哈]
3. 获取请求参数解决java.io.IOException: Stream closed
在拦截器中通过request.getInputStream();获取body中的信息后,此时在Controller中使用@RequestBody注解获取参数出现异常。

流关闭异常出现原因分析

IO(1)Java-BIO体系学习

输入流中含有一个“隐式指针”记录当前流读取的位置,故一旦我们在拦截器中将读取InputStream,那么后续无法使用@RequestBody注解。
可以在【拦截器】之前使用【过滤器】,包装request请求,重写getInputStream()方法;
将数据保存到内存Byte[]中,使用ByteArrayInputStream进行操作。
引入依赖

在springboot中,无需引入。

org.apache.tomcat.embed tomcat-embed-core 8.5.15 新建拦截器

@Component
public class LogInterceptor implements HandlerInterceptor {

private final static Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

//业务处理器处理请求之前被调用,对用户的request进行处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    logger.info("【拦截器】-【已进入拦截器!!】");
    logger.info("【拦截器】-request的类型{}", request.getClass());
    String bodyString = HttpHelper.getBodyString(request);
    logger.info("bodyString的值:" + bodyString);
    return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

}
新建过滤器

public class HttpServletRequestReplacedFilter implements Filter {

private final static Logger logger = LoggerFactory.getLogger(HttpServletRequestReplacedFilter.class);

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    logger.info("【过滤器】-【已进入过滤器!】");
    ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
    chain.doFilter(requestWrapper, response);
}

@Override
public void destroy() {

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

}
包装request

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final static Logger logger = LoggerFactory.getLogger(BodyReaderHttpServletRequestWrapper.class);

private final byte[] body;

public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {
    super(request);
    //获取到请求体
    logger.info("【过滤器】-【获取请求的body信息】");
    body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}

//重写方法
@Override
public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(getInputStream()));
}

@Override
public ServletInputStream getInputStream() throws IOException {
    //节点流
    final ByteArrayInputStream bais = new ByteArrayInputStream(body);
    return new ServletInputStream() {
        @Override
        public boolean isFinished() {
            return false;
        }

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

        @Override
        public void setReadListener(ReadListener listener) {
            //监听
            logger.info("【拦截器】-【BodyReaderHttpServletRequestWrapper ReadListener:{}】", this);
        }

        @Override
        public int read() throws IOException {
            return bais.read();
        }
    };
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

}
读取Body的工具类

public class HttpHelper {
private final static Logger logger = LoggerFactory.getLogger(HttpHelper.class);
public static String getBodyString(HttpServletRequest request) {

    StringBuffer sb = new StringBuffer();
    InputStream inputStream;
    BufferedReader bufferedReader;
    try {
        //将数据保存到数组中,每次读取的时候,都读取一遍
        inputStream = request.getInputStream();
        //将字节数组当做输出的目的地
        //字节流转换为字符流(处理流)
        bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String line = "";
        while ((line = bufferedReader.readLine()) != null) {
            sb.append(line);
        }
    } catch (Exception e) {
        logger.error("数据读取异常", e);
    }
    return sb.toString();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

}
拦截器过滤器加入到容器中

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LogInterceptor logInterceptor;
// @Resource
// private AnnotationInterceptor annotationInterceptor;
//配置拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
//指定拦截器,指定拦截路径
registry.addInterceptor(logInterceptor).addPathPatterns(“/**”);
// registry.addInterceptor(annotationInterceptor);
}

@Bean
public FilterRegistrationBean httpServletRequestReplacedRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new HttpServletRequestReplacedFilter());
    registration.addUrlPatterns("/*");
    registration.addInitParameter("paramName", "paramValue");
    registration.setName("httpServletRequestReplacedFilter");
    registration.setOrder(1);
    return registration;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

}

springboot 整合AOP

aop日志实现
AOP(Aspect Oriented Programming)是一个大话题,这里不做介绍,直接使用。
实现效果:用户在浏览器操作web页面,对应的操作会被记录到数据库中。
实现思路:自定义一个注解,将注解加到某个方法上,使用aop环绕通知代理带有注解的方法,在环绕前进行日志准备,执行完方法后进行日志入库。

2.1 log表
CREATE TABLE log (
id int(11) NOT NULL AUTO_INCREMENT,
operateor varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
operateType varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
operateDate datetime(0) NULL DEFAULT NULL,
operateResult varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
ip varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (id) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
2.2 pojo
log表对应的实体类,使用了lombok的@Data注解,也可以使用get、set代替,使用了mybatisplus,所以配置了@TableName等注解,如果使用mybatis或者其他的orm,可以把注解拿掉。

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;

import java.util.Date;

@Data
@TableName(“log”)
public class Log {
@TableId(value = “id”, type = IdType.AUTO)
private Integer id;

@TableField("operateor")
private String operateor;

@TableField("operateType")
private String operatetype;

@TableField("operateDate")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date operatedate;

@TableField("operatereSult")
private String operateresult;

@TableField("ip")
private String ip;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

}
2.3 controller
该Controller用于接收用户的请求,并对用户操作进行记录。

import cn.lastwhisper.springbootaop.core.annotation.LogAnno;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
/**
* @desc 这里为了方便直接在controller上进行aop日志记录,也可以放在service上。

 * @Param 
 * @return
 */
@LogAnno(operateType = "添加用户")
@RequestMapping(value = "/user/add")
public void add() {
    System.out.println("向数据库中添加用户!!");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

}
2.4 mapper
该mapper用于对log表进行操作

package cn.lastwhisper.springbootaop.mapper;

import cn.lastwhisper.springbootaop.pojo.Log;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface LogMapper extends BaseMapper {
}

2.5 core
2.5.1 日志注解
该注解用于标识需要被环绕通知进行日志操作的方法

import org.springframework.core.annotation.Order;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**

  • 日志注解

*/
@Target(ElementType.METHOD) // 方法注解
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface LogAnno {
String operateType();// 记录日志的操作类型
}
2.5.2 aop环绕通知类
对带有LogAnno注解的方法进行环绕通知日志记录。

@Order(3)注解一定要带上,标记支持AspectJ的切面排序。

import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.Date;

import cn.lastwhisper.springbootaop.core.annotation.LogAnno;
import cn.lastwhisper.springbootaop.core.common.HttpContextUtil;
import cn.lastwhisper.springbootaop.mapper.LogMapper;
import cn.lastwhisper.springbootaop.pojo.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**

  • AOP实现日志

*/
@Order(3)
@Component
@Aspect
public class LogAopAspect {
// 日志mapper,这里省事少写了service
@Autowired
private LogMapper logMapper;

/**
 * 环绕通知记录日志通过注解匹配到需要增加日志功能的方法
 * 
 * @param pjp
 * @return
 * @throws Throwable
 */
@Around("@annotation(cn.lastwhisper.springbootaop.core.annotation.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
    // 1.方法执行前的处理,相当于前置通知
    // 获取方法签名
    MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
    // 获取方法
    Method method = methodSignature.getMethod();
    // 获取方法上面的注解
    LogAnno logAnno = method.getAnnotation(LogAnno.class);
    // 获取操作描述的属性值
    String operateType = logAnno.operateType();
    // 创建一个日志对象(准备记录日志)
    Log log = new Log();
    log.setOperatetype(operateType);// 操作说明

    // 设置操作人,从session中获取,这里简化了一下,写死了。
    log.setOperateor("lastwhisper");
    String ip = HttpContextUtil.getIpAddress();
    log.setIp(ip);
    Object result = null;
    try {
        // 让代理方法执行
        result = pjp.proceed();
        // 2.相当于后置通知(方法成功执行之后走这里)
        log.setOperateresult("正常");// 设置操作结果
    } catch (SQLException e) {
        // 3.相当于异常通知部分
        log.setOperateresult("失败");// 设置操作结果
    } finally {
        // 4.相当于最终通知
        log.setOperatedate(new Date());// 设置操作日期
        logMapper.insert(log);// 添加日志记录
    }
    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

}
2.5.3 common
用于获取用户的ip

package cn.lastwhisper.springbootaop.core.common;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtil {
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
}

/**
 * 获取IP地址的方法
 *
 * @param request 传一个request对象下来
 * @return
 */
public static String getIpAddress() {
    HttpServletRequest request = getRequest();
    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.getHeader("HTTP_CLIENT_IP");
    }
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
        ip = request.getHeader("HTTP_X_FORWARDED_FOR");
    }
    if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
        ip = request.getRemoteAddr();
    }
    return ip;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

}
2.6 最终配置
配置application.properties文件

spring.application.name = lastwhisper-aoplog
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/wxlogin?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

配置springboot启动类SpringbootaopApplication

package cn.lastwhisper.springbootaop;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan(“cn.lastwhisper.springbootaop.mapper”) //设置mapper接口的扫描包
@SpringBootApplication
public class SpringbootaopApplication {

public static void main(String[] args) {
    SpringApplication.run(SpringbootaopApplication.class, args);
}
  • 1
  • 2
  • 3

}

至此一个完整的SpringBoot AOP日志系统基本成型。

  1. 测试
    启动SpringbootaopApplication的main方法。访问 http://localhost:8080/user/add
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/tf789/article/detail/61327
推荐阅读
相关标签
  

闽ICP备14008679号