基於Spring AOP和Groovy日誌模板配置的日誌記錄框架的二次實現與使用案例
一、專案地址
說明:本框架是基於koala-project(專案地址:http://git.oschina.net/openkoala/koala)中的koala-businesslog二次開發,因為koala-project已經很久沒有維護,對於一些Maven倉庫已經無法使用,Koala在Eclipse的外掛也基本無法使用,最近專案開發的時候使用到了這個,決心將他改一下可以正常使用,本著來與開源回報開源的思想將這個專案分享出來,希望大家一起學習。
二、改進內容
1、抽離專案依賴,去掉對org.openkoala和org.dayatang.dddlib原有框架內容的依賴,直接編譯即可使用;
2、更改原有專案獲取Bean的方式,這也是去掉對openkoala和dddlib框架依賴之後問題解決;
3、調整專案結構,使之更加明瞭和簡潔,並新增相應的註釋;
4、讓使用者自己實現日誌匯出器介面,方便使用者選擇合適的方式對日誌資訊進行儲存;
5、新增類似後臺管理的系統admin,可以對日誌進行檢視和搜尋,對於修改、刪除也提供了相應的方法;
6、提供一個完整的使用案例,是對使用者註冊的時候日誌的記錄;
7、更改原來JPA的使用為MyBatis的方式;
8、還有一些其他細節問題;
三、日誌系統專案介紹
1、簡介
現實場景,我們對於 業務的記錄(也叫業務日誌)的操作,很多時候是這樣編碼的:
//建立一家公司
public Organization createCompany(CompanyDto dto){
// 執行業務方法
companyDAO.save(dto);
// 記錄日誌
LogDAO.save(new BusinessLog(userDAO.getSubjectName() + ",建立子公司:" + dto.getCompanyName()));
}
最後結果就是
1. 新公司的建立
1. 業務日誌:張三,建立子公司:廣州子公司
咋一看這樣寫沒有什麼問題,但是其中有一個最大的問題:業務邏輯和日誌邏輯的混在一起了。如果業務邏輯和日誌邏輯足夠複雜的時候,你可以想像得到你的程式碼就如同義大利麵一樣。以後維護的時候,就會變成人間地獄!
Koala業務日誌系統就是為解決此問題而設計:業務邏輯和日誌邏輯分離!
2、Koala業務日誌系統的目標
日誌的記錄對業務方法儘量無侵入
盡最大可能不影響業務方法的效能(非同步實現)
系統及日誌模板配置簡單(基於 groovy)
日誌持久化(也稱為匯出日誌)方式靈活(面向介面設計,可擴充套件檔案、NoSQL 儲存)
修改日誌模板而不需要重啟應用
事實上,要達到真正的無侵入是不可能的,Koala業務日誌系統對業務方法的侵入只不過是要在業務方法上加上一個註解。
3、現有模組劃分
- ufind-businesslog-api 業務日誌系統的核心api
- ufind-businesslog-admin 業務日誌後臺管理系統
ufind-businesslog-demo 業務日誌系統案例系統,實際使用時,可以參考此模組
4、目前的缺陷
- 依賴Spring 的AOP
- 只有受Spring IOC容器託管的bean才能被日誌
5、如何使用Koala預設實現的業務日誌系統
大綱
1. 在類路徑下加入`businesslog.properties`檔案
1. 為業務方法加上別名,具體做法:在業務方法上加入`@BusinessLogAlias`註解,並設定別名
1. 在類路徑下加入日誌模板配置檔案
5.1、詳細操作
(1) 在類路徑下加入businesslog.properties
檔案
#指定攔截的業務方法,使用Spring的切入點寫法
pointcut=execution(* business.*Application.*(..))
#日誌開關
kaola.businesslog.enable=true
#指定日誌匯出器BusinessLogExporter介面的實現。預設有:BusinessLogConsoleExporter和BusinessLogExporterImpl,這個實在使用者具體使用的時候進行自定義的
businessLogExporter=com.ufind.businesslog.demo.exportImpl.BusinessLogExporterImpl
#執行緒池配置。因為業務日誌的匯出藉助執行緒池實現非同步
#核心執行緒數
log.threadPool.corePoolSize=10
#最大執行緒數
log.threadPool.maxPoolSize=50
#佇列最大長度
log.threadPool.queueCapacity=1000
#執行緒池維護執行緒所允許的空閒時間
log.threadPool.keepAliveSeconds=300
#執行緒池對拒絕任務(無執行緒可用)的處理策略
log.threadPool.rejectedExecutionHandler=java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy
(2)如果使用Koala的預設日誌匯出器,需要配置資料庫引數,資料庫設定database.properties
db.jdbc.driver=com.mysql.jdbc.Driver
db.jdbc.connection.url=jdbc:mysql://127.0.0.1:3306/ufind_log?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
db.jdbc.username=root
db.jdbc.password=root
db.jdbc.dialect=org.hibernate.dialect.MySQL5Dialect
db.jdbc.testsql=select 1
hibernate.hbm2ddl.auto=update
db.jdbc.show_sql=true
db.jdbc.database.Type=MYSQL
db.jdbc.generateDdl=true
db.jdbc.maximumConnectionCount=200
db.jdbc.minimumConnectionCount=20
(3)為業務方法加上別名。這個別名必須符合Java方法名的命名規則。給業務方法加別名的目的是為了方便業務方法與日誌模板之間的對映。
@BusinessLogAlias("業務方法別名")
業務方法
例如:
@BusinessLogAlias("UserInfoApplicationImpl_addUser")
public boolean addUser(UserInfo userInfo) {
return this.userInfo.addUser(userInfo);
}
(4)在類路徑下加入日誌模板配置檔案
日誌模板實際上是groovy檔案。在這個groovy檔案中,你可以寫Java程式碼,也可以寫groovy程式碼。這樣,就可以達到最大的靈活。同時,配置起來又不復雜。
目前我們支援兩種配置方式:單檔案配置方式和多檔案配置方式。
例如:
package businessLogConfig
class UserInfoApplicationImpl {
def context
def UserInfoApplicationImpl_addUser() {
"${getPreTemplate()}:建立一個新使用者:${context._param0.userAccount}"
}
def getPreTemplate() {
"${context._user}-"
}
}
5.2、日誌模板配置
單檔案配置
- 在類路徑下加入
BusinessLogConfig.groovy
檔案模板為:
class BusinesslogConfig { //必須 def context //InvoiceApplicationImpl_addInvoice為業務方法別名 def InvoiceApplicationImpl_addInvoice() { "日誌內容" } def ProjectApplicationImpl_findSomeProjects() { [category:"專案操作", logs:"查詢專案"] } }
配置模板說明
配置模板實際上是一個Groovy類。你可以在類中定義任何方法。如果方法為某個業務方法的別名(使用@MethodAlias
註解)
那麼,我們就認為它是一個業務日誌方法。它的返回值(return或者放在方法最後一行的變數)將會被Set到org.openkoala.businesslog.BusinessLog
的例項中。日誌方法返回值有兩種情況:1. 只返回一個String型別的日誌文字;2. 返回一個Map,這個Map包括Key為
category
的日誌分類及日誌文字。在類中,還會使用Groovy定義變數的方法:
def context
定義一個變數。這個變數實際上是一個Map。
Map中儲存的是業務方法的返回值
、引數
。如果需要,你可以儲存任何你需要的資料。你可以從這個context中取
出你需要的內容,填充到你的日誌中。至於如何取context中的內容,請看附錄
- 在類路徑下加入
多檔案配置
當業務系統非常複雜的時候,一個日誌配置檔案是不足夠的。我們提供多檔案的配置方式- 在類路徑中加入
businessLogConfig
資料夾。 - 在該資料夾中加入日誌配置檔案,檔名任意,只要符合Groovy類檔案的命名規範即可。
- 在類路徑中加入
注: 多檔案配置方式與單檔案配置方式不相容。在此業務日誌系統中,單檔案配置方式優先。
businessLogConfig
資料夾中的所有以.groovy
結尾的檔案都將被作為日誌配置檔案。
6、實現自己的日誌持久化方式
新建一個實現
com.ufind.businesslog.api.BusinessLogExporter
介面的類,
比如:com.ufind.businesslog.demo.exportImpl.MyBusinesslogExporter在
businesslog.properties
中設定businessLogExporter=com.mycom.busineslog.MyBusinesslogExporter
附錄
在日誌模板中取context
的內容
key value
_methodReturn 業務方法返回值
_param 業務方法的引數, _param0代表第一個引數 _param1代表第二個引數,依此類推
_executeError 業務方法執行失敗的異常資訊
_businessMethod 業務方法
_user 業務方法操作人
_time 業務方法操作時間
_ip ip地址
四、ufind-businesslog專案介紹
1、ufind-businesslog-api專案的核心API
BusinessLogAlias是業務日誌的註解類;
BusinessLogExporter日誌匯出器,定義的介面讓使用者實現,實現的過程就是日誌資訊的儲存過程;
BusinessLogInterceptor業務日誌的攔截器,當業務方法執行之後獲得執行的結果,根據groovy中配置的日誌模板得到具體的日誌資訊,並呼叫非同步執行任務儲存日誌資訊;
BusinessLogThread日誌的處理執行緒類,run()方法主要得到日誌模板中的配置資訊並將資訊根據使用者實現的日誌匯出器將日誌資訊儲存到資料庫中;
BusinessLogServletFilter業務日誌的過濾器,在方法請求呼叫之前獲得容器中的上下文環境,以便構造日誌資訊;
2、ufind-businesslog-demo業務日誌專案使用案例
這裡使用到日誌框架API的地方就是自己去實現日誌匯出器,實現com.ufind.businesslog.api.BusinessLogExporter介面
exportImpl包下的是使用者自己實現的日誌匯出器,其中指定了如何儲存日誌資訊;
專案AOP配置
<bean id="logInterceptor" class="com.ufind.businesslog.api.BusinessLogInterceptor"/>
<bean id="businessLogExporter" class="com.ufind.businesslog.demo.exportImpl.BusinessLogExporterImpl"/>
<!-- 加了 proxy-target-class="true" 使spring集中制使用cglib的代理 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="businessBehavior" expression="execution(* com.ufind.businesslog.demo.application.impl.*.*(..))"/>
<aop:aspect id="logAspect" ref="logInterceptor">
<aop:after-returning returning="result" method="logAfter" pointcut-ref="businessBehavior"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="businessBehavior" throwing="error"/>
</aop:aspect>
</aop:config>
非同步執行緒池配置
<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心執行緒數 -->
<property name="corePoolSize" value="${log.threadPool.corePoolSize}"/>
<!-- 最大執行緒數 -->
<property name="maxPoolSize" value="${log.threadPool.maxPoolSize}"/>
<!-- 佇列最大長度 >=mainExecutor.maxSize -->
<property name="queueCapacity" value="${log.threadPool.queueCapacity}"/>
<!-- 執行緒池維護執行緒所允許的空閒時間 -->
<property name="keepAliveSeconds" value="${log.threadPool.keepAliveSeconds}"/>
<!-- 執行緒池對拒絕任務(無執行緒可用)的處理策略 -->
<property name="rejectedExecutionHandler">
<bean class="${log.threadPool.rejectedExecutionHandler}"/>
</property>
</bean>
自定義LogFilter過濾器繼承com.ufind.businesslog.api.BusinessLogServletFilter
package com.ufind.businesslog.demo.web.filters;
import com.ufind.businesslog.api.BusinessLogServletFilter;
import com.ufind.businesslog.demo.domain.UserInfo;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
public class LogFilter extends BusinessLogServletFilter {
/**
* 將需要用到的資訊放入日誌上下文
*/
@Override
public void beforeFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
addIpContext(getIp(req)); //新增IP
HttpServletRequest request = (HttpServletRequest) req;
UserInfo userInfo = (UserInfo) request.getSession().getAttribute("USER");
String userAccount = userInfo == null ? "未知" : userInfo.getUserAccount();
addUserContext(userAccount);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void destroy() {
//To change body of implemented methods use File | Settings | File Templates.
}
}
3、ufind-businesslog-admin業務日誌專案admin
使用自己Spring MVC 、MyBastis對MySQL資料庫的日誌資訊進行查詢、搜尋操作
這裡主要是提供一種思路,具體如何對日誌資訊進行視覺化的管理方式很多。
專案的主要思路就是
1、業務請求首先通過LogFilter, 將容器中的上下文環境加到ThreadLocalBusinessLogContext物件中,例如:使用者的資訊、IP地址等資訊;
2、執行到加了@BusinessLogAlias註解的業務方法,執行完畢之後被BusinessLogInterceptor攔截器進行攔截;
3、BusinessLogInterceptor攔截器根據切點資訊得到符合BusinessLogAlias註解的value值,就是得到grooy中要執行的方法名;
4、緊接著建立BusinessLogThread物件,為了非同步執行操作,該物件包含ThreadLocalBusinessLogContext物件資訊,要執行的日誌模板配置檔案中的哪一個方法,使用者自己實現的日誌匯出器;
5、然後執行非同步任務,再執行非同步任務的時候,根據註解的value值確定執行Groovy中的def的方法並得到返回值,該返回值就是日誌資訊通過contex新增值之後的字串資訊;
6、然後呼叫日誌匯出器的實現類中的export方法進行具體日誌資訊的儲存,將日誌資訊儲存到指定位置;