天天看點

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

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/u010046908/article/details/53978250

1、面向切面程式設計的一些術語

  • 切面(Aspect):切面用于組織多個Advice,Advice放在切面中的定義。
  • 連接配接點(Jionpoint):程式執行過程中的明确的點,如方法的調用,或異常的抛出。在SpringAOP中,連接配接點總是方法的調用。
  • 增強的處理(Advice):AOP架構在特定的切入點執行的增強處理。處理有around、brefore和after等類型。
  • 切入點(Pointcut):可以插入增強處理的連接配接點。簡而言之,當某個連接配接點滿足指定的要求時,該連接配接點将添加增強處理,該連接配接點也就變成了切入點。
pointcut xxxPointcut():execution(void H*.say*())           
  • 引入:将方法或字段添加到被代理的類中,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功能

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${srping.version}</version>
</dependency>
<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.2</version>
        </dependency>           

5、實作Web層的日志切面

實作AOP的切面主要有以下幾個要素:

  • 使用 @Aspect 注解将一個java類定義為切面類。
  • 使用 @Pointcut 定義一個切入點,可以是一個規則表達式,比如下例中某個package下的所有函數,也可以是一個注解等。
  • 根據需要在切入點不同位置的切入内容
  • 使用 @Before 在切入點開始處切入内容
  • 使用 @After 在切入點結尾處切入内容
  • 使用 @AfterReturning 在切入點return内容之後切入内容(可以用來對處理傳回值做一些加工處理)
  • 使用 @Around 在切入點前後切入内容,并自己控制何時執行切入點自身的内容
  • 使用 @AfterThrowing 用來處理當切入内容部分抛出異常之後的處理邏輯
package com.lidong.util;

import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.mongodb.BasicDBObject;
/**
 1. 儲存請求資料到mongodb
 2. @author lidong
 3. @createTime 2016年12月24日
 */
@Aspect
@Order(1)
@Component
public class WebLogAspect {

    private Logger logger = Logger.getLogger("mongodb");
    ThreadLocal<Long> startTime = new ThreadLocal<>();
    HttpServletRequest request;
    JoinPoint mJoinPoint;
    @Pointcut("execution (* com.lidong.*.controller.*.*(..))") 
    public void webLog(){}

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
            startTime.set(System.currentTimeMillis());   
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        request = attributes.getRequest();
        mJoinPoint = joinPoint;
    }


    private BasicDBObject getBasicDBObject(HttpServletRequest request, JoinPoint joinPoint) {
        BasicDBObject r = new BasicDBObject();
        r.append("requestURL", request.getRequestURL().toString());
        r.append("requestURI", request.getRequestURI());
        r.append("queryString", request.getQueryString());
        r.append("remoteAddr", request.getRemoteAddr());
        r.append("remoteHost", request.getRemoteHost());
        r.append("remotePort", request.getRemotePort());
        r.append("localAddr", request.getLocalAddr());
        r.append("localName", request.getLocalName());
        r.append("method", request.getMethod());
        r.append("headers", getHeadersInfo(request));
        r.append("parameters", request.getParameterMap());
        r.append("classMethod", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        r.append("args", Arrays.toString(joinPoint.getArgs()));
        return r;
    }
  /**
     * 擷取請求頭部的資訊
     * @param request
     * @return
     */
    private Map<String, String> getHeadersInfo(HttpServletRequest request) {
        Map<String, String> map = new HashMap<>();
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
             BasicDBObject logInfo = getBasicDBObject(request, mJoinPoint);    
             // 處理完請求,傳回内容
             logInfo.append("response",ret.toString());
             logInfo.append("spend_time", (System.currentTimeMillis() - startTime.get()));
             logger.info(logInfo);
     }
}           

注意:在WebLogAspect切面中,分别通過doBefore和doAfterReturning兩個獨立函數實作了切點頭部和切點傳回後執行的内容,若我們想統計請求的處理時間,就需要在doBefore處記錄時間,并在doAfterReturning處通過目前時間與開始處記錄的時間計算得到請求處理的消耗時間。

6、實作Web層的日志的日志儲存到mongodb資料庫中

6.1.通過自定義appender實作

思路:log4j提供的輸出器實作自Appender接口,要自定義appender輸出到MongoDB,隻需要繼承AppenderSkeleton類,并實作幾個方法即可完成。

  • 引入mongodb的驅動,在pom.xml中引入下面依賴
<dependency>  
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver</artifactId>
    <version>3.2.2</version>
</dependency>            
  • 實作MongoAppender

編寫MongoAppender類繼承AppenderSkeleton,實作如下:

package com.lidong.util;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.LoggingEvent;

import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientURI;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
/**
 * 自定義appender實作輸出日志MongoDB
 * @author lidong
 *
 */
public class MongoAppender  extends AppenderSkeleton {

    private MongoClient mongoClient;
    private MongoDatabase mongoDatabase;
    private MongoCollection<BasicDBObject> logsCollection;
    /**
     * mongodb連接配接的url
     */
    private String connectionUrl;
    /**
     * 資料庫的名稱
     */
    private String databaseName;
    /**
     * 集合的名稱
     */
    private String collectionName;

    @Override
    protected void append(LoggingEvent loggingEvent) {

        if(mongoDatabase == null) {
            MongoClientURI connectionString = new MongoClientURI(connectionUrl);
            mongoClient = new MongoClient(connectionString);
            mongoDatabase = mongoClient.getDatabase(databaseName);
            logsCollection = mongoDatabase.getCollection(collectionName, BasicDBObject.class);
        }
        //将日志插入到集合
        logsCollection.insertOne((BasicDBObject) loggingEvent.getMessage());

    }

    @Override
    public void close() {
        if(mongoClient != null) {
            mongoClient.close();
        }
    }

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

    public MongoClient getMongoClient() {
        return mongoClient;
    }

    public void setMongoClient(MongoClient mongoClient) {
        this.mongoClient = mongoClient;
    }

    public MongoDatabase getMongoDatabase() {
        return mongoDatabase;
    }

    public void setMongoDatabase(MongoDatabase mongoDatabase) {
        this.mongoDatabase = mongoDatabase;
    }

    public MongoCollection<BasicDBObject> getLogsCollection() {
        return logsCollection;
    }

    public void setLogsCollection(MongoCollection<BasicDBObject> logsCollection) {
        this.logsCollection = logsCollection;
    }

    public String getConnectionUrl() {
        return connectionUrl;
    }

    public void setConnectionUrl(String connectionUrl) {
        this.connectionUrl = connectionUrl;
    }

    public String getDatabaseName() {
        return databaseName;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public String getCollectionName() {
        return collectionName;
    }

    public void setCollectionName(String collectionName) {
        this.collectionName = collectionName;
    }



}           

6.3 在log4j.prperties中配置

# mongodb輸出
log4j.logger.mongodb=INFO, mongodb
log4j.appender.mongodb=com.lidong.util.MongoAppender
log4j.appender.mongodb.connectionUrl=mongodb://127.0.0.1:27017
log4j.appender.mongodb.databaseName=logs
log4j.appender.mongodb.collectionName=logs_request           

6.4在spring-mvc.xml中開啟統一處理請求日志

<!-- web統一管理  -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <bean class="com.lidong.util.WebLogAspect" />
           

6.5在資料庫中檢視儲存的日志

代碼位址