dubbo2.5-spring4-mybastis3.2-springmvc4-mongodb3.4-redis3.2整合(四)Spring AOP中使用log4j实现http请求日志入mongodb

释放双眼,带上耳机,听听看~!

1、面向切面编程的一些术语

  • 切面(Aspect):切面用于组织多个Advice,Advice放在切面中的定义。

  • 连接点(Jionpoint):程序执行过程中的明确的点,如方法的调用,或异常的抛出。在SpringAOP中,连接点总是方法的调用。

  • 增强的处理(Advice):AOP框架在特定的切入点执行的增强处理。处理有around、brefore和after等类型。

  • 切入点(Pointcut):可以插入增强处理的连接点。简而言之,当某个连接点满足指定的要求时,该连接点将添加增强处理,该连接点也就变成了切入点。


1
2
1pointcut xxxPointcut():execution(void H*.say*())
2
  • 引入:将方法或字段添加到被代理的类中,Spring允许将新的接口引入到任何被调用的处理对象中。例如:你可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。
  • 目标对象:被AOP框架进行增强处理的对象,也被称为被增强对象,如果AOP框架采用的是动态的AOP实现 ,那该对象就是一个被代理对象。
  • AOP代理:AOP框架创建的对象,简单说,代理就是对目标对象的加强,Spring中的AOP代理可以是JDK的动态代理,也可以是cglib的动态代理,前者是实现接口 的目标对象的代理,后者为不实现接口的目标对象的代理。
  • 织入(Weaving):将增强处理添加到目标对象中,并创一个被增强对象(AOP代理)的过程就是织入。织入有l两种实现方式—–编译时增强(AspectJ)和运行时增强(如Spring AOP)。Spring和其他纯Java AOP框架一样,在运行时织入。

2、AOP的基本概念

AOP是从程序运行角度考虑程序的流程,提取业务处理的过程的切面,AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。

AOP框架并不会与特定的代码耦合,AOP框架能处理程序执行中特定的切入点(PointCut),而不是某个具体类的耦合。

3、AOP框架具有如下的两个特征。

  • 各个步骤之间良好的隔离。

  • 源代码无关。

注:Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理。从而提高了更多的复用。

4.如何在Spring中引入AOP功能


1
2
3
4
5
6
7
8
9
10
11
1<dependency>
2            <groupId>org.springframework</groupId>
3            <artifactId>spring-aop</artifactId>
4            <version>${srping.version}</version>
5</dependency>
6<dependency>
7            <groupId>org.aspectj</groupId>
8            <artifactId>aspectjweaver</artifactId>
9            <version>1.7.2</version>
10        </dependency>
11

5、实现Web层的日志切面

实现AOP的切面主要有以下几个要素:

  • 使用 @Aspect 注解将一个java类定义为切面类。

  • 使用 @Pointcut 定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。

  • 根据需要在切入点不同位置的切入内容

  • 使用 @Before 在切入点开始处切入内容

  • 使用 @After 在切入点结尾处切入内容

  • 使用 @AfterReturning 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)

  • 使用 @Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内容

  • 使用 @AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑


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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
1package com.lidong.util;
2
3import java.util.Arrays;
4import java.util.Enumeration;
5import java.util.HashMap;
6import java.util.Map;
7
8import javax.servlet.http.HttpServletRequest;
9
10import org.apache.log4j.Logger;
11import org.aspectj.lang.JoinPoint;
12import org.aspectj.lang.annotation.AfterReturning;
13import org.aspectj.lang.annotation.Aspect;
14import org.aspectj.lang.annotation.Before;
15import org.aspectj.lang.annotation.Pointcut;
16import org.springframework.core.annotation.Order;
17import org.springframework.stereotype.Component;
18import org.springframework.web.context.request.RequestContextHolder;
19import org.springframework.web.context.request.ServletRequestAttributes;
20
21import com.mongodb.BasicDBObject;
22/**
23 1. 保存请求数据到mongodb
24 2. @author lidong
25 3. @createTime 2016年12月24日
26 */
27@Aspect
28@Order(1)
29@Component
30public class WebLogAspect {
31
32    private Logger logger = Logger.getLogger("mongodb");
33    ThreadLocal<Long> startTime = new ThreadLocal<>();
34    HttpServletRequest request;
35    JoinPoint mJoinPoint;
36    @Pointcut("execution (* com.lidong.*.controller.*.*(..))")
37    public void webLog(){}
38
39    @Before("webLog()")
40    public void doBefore(JoinPoint joinPoint) throws Throwable {
41            startTime.set(System.currentTimeMillis());  
42        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
43        request = attributes.getRequest();
44        mJoinPoint = joinPoint;
45    }
46
47
48    private BasicDBObject getBasicDBObject(HttpServletRequest request, JoinPoint joinPoint) {
49        BasicDBObject r = new BasicDBObject();
50        r.append("requestURL", request.getRequestURL().toString());
51        r.append("requestURI", request.getRequestURI());
52        r.append("queryString", request.getQueryString());
53        r.append("remoteAddr", request.getRemoteAddr());
54        r.append("remoteHost", request.getRemoteHost());
55        r.append("remotePort", request.getRemotePort());
56        r.append("localAddr", request.getLocalAddr());
57        r.append("localName", request.getLocalName());
58        r.append("method", request.getMethod());
59        r.append("headers", getHeadersInfo(request));
60        r.append("parameters", request.getParameterMap());
61        r.append("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
62        r.append("args", Arrays.toString(joinPoint.getArgs()));
63        return r;
64    }
65  /**
66     * 获取请求头部的信息
67     * @param request
68     * @return
69     */
70    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
71        Map<String, String> map = new HashMap<>();
72        Enumeration headerNames = request.getHeaderNames();
73        while (headerNames.hasMoreElements()) {
74            String key = (String) headerNames.nextElement();
75            String value = request.getHeader(key);
76            map.put(key, value);
77        }
78        return map;
79    }
80
81    @AfterReturning(returning = "ret", pointcut = "webLog()")
82    public void doAfterReturning(Object ret) throws Throwable {
83             BasicDBObject logInfo = getBasicDBObject(request, mJoinPoint);    
84             // 处理完请求,返回内容
85             logInfo.append("response",ret.toString());
86             logInfo.append("spend_time", (System.currentTimeMillis() - startTime.get()));
87             logger.info(logInfo);
88     }
89}
90

**注意:**在WebLogAspect切面中,分别通过doBefore和doAfterReturning两个独立函数实现了切点头部和切点返回后执行的内容,若我们想统计请求的处理时间,就需要在doBefore处记录时间,并在doAfterReturning处通过当前时间与开始处记录的时间计算得到请求处理的消耗时间。

6、实现Web层的日志的日志保存到mongodb数据库中

6.1.通过自定义appender实现

思路:log4j提供的输出器实现自Appender接口,要自定义appender输出到MongoDB,只需要继承AppenderSkeleton类,并实现几个方法即可完成。

  • 引入mongodb的驱动,在pom.xml中引入下面依赖


1
2
3
4
5
6
1<dependency>  
2    <groupId>org.mongodb</groupId>
3    <artifactId>mongodb-driver</artifactId>
4    <version>3.2.2</version>
5</dependency>
6
  • 实现MongoAppender

编写MongoAppender类继承AppenderSkeleton,实现如下:


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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
1package com.lidong.util;
2
3import org.apache.log4j.AppenderSkeleton;
4import org.apache.log4j.spi.LoggingEvent;
5
6import com.mongodb.BasicDBObject;
7import com.mongodb.MongoClient;
8import com.mongodb.MongoClientURI;
9import com.mongodb.client.MongoCollection;
10import com.mongodb.client.MongoDatabase;
11/**
12 * 自定义appender实现输出日志MongoDB
13 * @author lidong
14 *
15 */
16public class MongoAppender  extends AppenderSkeleton {
17
18    private MongoClient mongoClient;
19    private MongoDatabase mongoDatabase;
20    private MongoCollection<BasicDBObject> logsCollection;
21    /**
22     * mongodb连接的url
23     */
24    private String connectionUrl;
25    /**
26     * 数据库的名称
27     */
28    private String databaseName;
29    /**
30     * 集合的名称
31     */
32    private String collectionName;
33
34    @Override
35    protected void append(LoggingEvent loggingEvent) {
36
37        if(mongoDatabase == null) {
38            MongoClientURI connectionString = new MongoClientURI(connectionUrl);
39            mongoClient = new MongoClient(connectionString);
40            mongoDatabase = mongoClient.getDatabase(databaseName);
41            logsCollection = mongoDatabase.getCollection(collectionName, BasicDBObject.class);
42        }
43        //将日志插入到集合
44        logsCollection.insertOne((BasicDBObject) loggingEvent.getMessage());
45
46    }
47
48    @Override
49    public void close() {
50        if(mongoClient != null) {
51            mongoClient.close();
52        }
53    }
54
55    @Override
56    public boolean requiresLayout() {
57        return false;
58    }
59
60    public MongoClient getMongoClient() {
61        return mongoClient;
62    }
63
64    public void setMongoClient(MongoClient mongoClient) {
65        this.mongoClient = mongoClient;
66    }
67
68    public MongoDatabase getMongoDatabase() {
69        return mongoDatabase;
70    }
71
72    public void setMongoDatabase(MongoDatabase mongoDatabase) {
73        this.mongoDatabase = mongoDatabase;
74    }
75
76    public MongoCollection<BasicDBObject> getLogsCollection() {
77        return logsCollection;
78    }
79
80    public void setLogsCollection(MongoCollection<BasicDBObject> logsCollection) {
81        this.logsCollection = logsCollection;
82    }
83
84    public String getConnectionUrl() {
85        return connectionUrl;
86    }
87
88    public void setConnectionUrl(String connectionUrl) {
89        this.connectionUrl = connectionUrl;
90    }
91
92    public String getDatabaseName() {
93        return databaseName;
94    }
95
96    public void setDatabaseName(String databaseName) {
97        this.databaseName = databaseName;
98    }
99
100    public String getCollectionName() {
101        return collectionName;
102    }
103
104    public void setCollectionName(String collectionName) {
105        this.collectionName = collectionName;
106    }
107
108
109
110}
111

6.3 在log4j.prperties中配置


1
2
3
4
5
6
7
1# mongodb输出
2log4j.logger.mongodb=INFO, mongodb
3log4j.appender.mongodb=com.lidong.util.MongoAppender
4log4j.appender.mongodb.connectionUrl=mongodb://127.0.0.1:27017
5log4j.appender.mongodb.databaseName=logs
6log4j.appender.mongodb.collectionName=logs_request
7

6.4在spring-mvc.xml中开启统一处理请求日志


1
2
3
4
5
6
1
2    <!-- web统一管理  -->
3    <aop:aspectj-autoproxy proxy-target-class="true"/>
4    <bean class="com.lidong.util.WebLogAspect" />
5
6

6.5在数据库中查看保存的日志

代码地址

给TA打赏
共{{data.count}}人
人已打赏
安全运维

OpenSSH-8.7p1离线升级修复安全漏洞

2021-10-23 10:13:25

安全运维

设计模式的设计原则

2021-12-12 17:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索