1. 概述
在一般系統(tǒng)中,當(dāng)我們做了一些重要的操作時,如登陸系統(tǒng),添加用戶,刪除用戶等操作時,我們需要將這些行為持久化。本文我們通過spring aop和java的自定義注解來實(shí)現(xiàn)日志的插入。此方案對原有業(yè)務(wù)入侵較低,實(shí)現(xiàn)較靈活
2. 日志的相關(guān)類定義
我們將日志抽象為以下兩個類:功能模塊和操作類型
使用枚舉類定義功能模塊類型moduletype,如學(xué)生、用戶模塊
1
2
3
4
5
6
7
8
9
10
11
12
|
public enum moduletype { default ( "1" ), // 默認(rèn)值 student( "2" ), // 學(xué)生模塊 teacher( "3" ); // 用戶模塊 private moduletype(string index){ this .module = index; } private string module; public string getmodule(){ return this .module; } } |
使用枚舉類定義操作的類型:eventtype。如登陸、添加、刪除、更新、刪除等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public enum eventtype { default ( "1" , "default" ), add( "2" , "add" ), update( "3" , "update" ), delete_single( "4" , "delete-single" ), login( "10" , "login" ),login_out( "11" , "login_out" ); private eventtype(string index, string name){ this .name = name; this .event = index; } private string event; private string name; public string getevent(){ return this .event; } public string getname() { return name; } } |
3. 定義日志相關(guān)的注解
3.1. @logenable
這里我們定義日志的開關(guān)量,類上只有這個值為true,這個類中日志功能才開啟
1
2
3
4
5
6
7
8
9
10
|
@documented @retention (retentionpolicy.runtime) @target ({elementtype.type}) public @interface logenable { /** * 如果為true,則類下面的logevent啟作用,否則忽略 * @return */ boolean logenable() default true ; } |
3.2. @logevent
這里定義日志的詳細(xì)內(nèi)容。如果此注解注解在類上,則這個參數(shù)做為類全部方法的默認(rèn)值。如果注解在方法上,則只對這個方法啟作用
1
2
3
4
5
6
7
8
|
@documented @retention (retentionpolicy.runtime) @target ({java.lang.annotation.elementtype.method, elementtype.type}) public @interface logevent { moduletype module() default moduletype. default ; // 日志所屬的模塊 eventtype event() default eventtype. default ; // 日志事件類型 string desc() default "" ; // 描述信息 } |
3.3. @logkey
此注解如果注解在方法上,則整個方法的參數(shù)以json的格式保存到日志中。如果此注解同時注解在方法和類上,則方法上的注解會覆蓋類上的值。
1
2
3
4
5
6
7
8
|
@target ({elementtype.field,elementtype.parameter}) @retention (retentionpolicy.runtime) @documented public @interface logkey { string keyname() default "" ; // key的名稱 boolean isuserid() default false ; // 此字段是否是本次操作的userid,這里略 boolean islog() default true ; // 是否加入到日志中 } |
4. 定義日志處理類
4.1. logadmmodel
定義保存日志信息的類
1
2
3
4
5
6
7
8
9
10
11
|
public class logadmmodel { private long id; private string userid; // 操作用戶 private string username; private string admmodel; // 模塊 private string admevent; // 操作 private date createdate; // 操作內(nèi)容 private string admoptcontent; // 操作內(nèi)容 private string desc; // 備注 set/get略 } |
4.2. ilogmanager
定義日志處理的接口類ilogmanager
我們可以將日志存入數(shù)據(jù)庫,也可以將日志發(fā)送到開中間件,如果redis, mq等等。每一種日志處理類都是此接口的實(shí)現(xiàn)類
1
2
3
4
5
6
7
|
public interface ilogmanager { /** * 日志處理模塊 * @param paramlogadmbean */ void deallog(logadmmodel paramlogadmbean); } |
4.3. dblogmanager
ilogmanager實(shí)現(xiàn)類,將日志入庫。這里只模擬入庫
1
2
3
4
5
6
7
|
@service public class dblogmanager implements ilogmanager { @override public void deallog(logadmmodel paramlogadmbean) { system.out.println( "將日志存入數(shù)據(jù)庫,日志內(nèi)容如下: " + json.tojsonstring(paramlogadmbean)); } } |
5. aop的配置
5.1. logaspect定義aop類
使用@aspect注解此類
使用@pointcut定義要攔截的包及類方法
我們使用@around定義方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
@component @aspect public class logaspect { @autowired private loginfogeneration loginfogeneration; @autowired private ilogmanager logmanager; @pointcut ( "execution(* com.hry.spring.mvc.aop.log.service..*.*(..))" ) public void managerlogpoint() { } @around ( "managerlogpoint()" ) public object aroundmanagerlogpoint(proceedingjoinpoint jp) throws throwable { …. } } |
aroundmanagerlogpoint:主方法的主要業(yè)務(wù)流程
1. 檢查攔截方法的類是否被@logenable注解,如果是,則走日志邏輯,否則執(zhí)行正常的邏輯
2. 檢查攔截方法是否被@logevent,如果是,則走日志邏輯,否則執(zhí)行正常的邏輯
3. 根據(jù)獲取方法上獲取@logevent 中值,生成日志的部分參數(shù)。其中定義在類上@logevent 的值做為默認(rèn)值
4. 調(diào)用loginfogeneration的processingmanagerlogmessage填充日志中其它的參數(shù),做個方法我們后面再講
5. 執(zhí)行正常的業(yè)務(wù)調(diào)用
6. 如果執(zhí)行成功,則logmanager執(zhí)行日志的處理(我們這里只記錄執(zhí)行成功的日志,你也可以定義記錄失敗的日志)
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
|
@around ( "managerlogpoint()" ) public object aroundmanagerlogpoint(proceedingjoinpoint jp) throws throwable { class target = jp.gettarget().getclass(); // 獲取logenable logenable logenable = (logenable) target.getannotation(logenable. class ); if (logenable == null || !logenable.logenable()){ return jp.proceed(); } // 獲取類上的logevent做為默認(rèn)值 logevent logeventclass = (logevent) target.getannotation(logevent. class ); method method = getinvokedmethod(jp); if (method == null ){ return jp.proceed(); } // 獲取方法上的logevent logevent logeventmethod = method.getannotation(logevent. class ); if (logeventmethod == null ){ return jp.proceed(); } string optevent = logeventmethod.event().getevent(); string optmodel = logeventmethod.module().getmodule(); string desc = logeventmethod.desc(); if (logeventclass != null ){ // 如果方法上的值為默認(rèn)值,則使用全局的值進(jìn)行替換 optevent = optevent.equals(eventtype. default ) ? logeventclass.event().getevent() : optevent; optmodel = optmodel.equals(moduletype. default ) ? logeventclass.module().getmodule() : optmodel; } logadmmodel logbean = new logadmmodel(); logbean.setadmmodel(optmodel); logbean.setadmevent(optevent); logbean.setdesc(desc); logbean.setcreatedate( new date()); loginfogeneration.processingmanagerlogmessage(jp, logbean, method); object returnobj = jp.proceed(); if (optevent.equals(eventtype.login)){ //todo 如果是登錄,還需要根據(jù)返回值進(jìn)行判斷是不是成功了,如果成功了,則執(zhí)行添加日志。這里判斷比較簡單 if (returnobj != null ) { this .logmanager.deallog(logbean); } } else { this .logmanager.deallog(logbean); } return returnobj; } /** * 獲取請求方法 * * @param jp * @return */ public method getinvokedmethod(joinpoint jp) { // 調(diào)用方法的參數(shù) list classlist = new arraylist(); for (object obj : jp.getargs()) { classlist.add(obj.getclass()); } class [] argscls = ( class []) classlist.toarray( new class [ 0 ]); // 被調(diào)用方法名稱 string methodname = jp.getsignature().getname(); method method = null ; try { method = jp.gettarget().getclass().getmethod(methodname, argscls); } catch (nosuchmethodexception e) { e.printstacktrace(); } return method; } } |
6. 將以上的方案在實(shí)際中應(yīng)用的方案
這里我們模擬學(xué)生操作的業(yè)務(wù),并使用上文注解應(yīng)用到上面并攔截日志
6.1. istudentservice
業(yè)務(wù)接口類,執(zhí)行一般的crud
1
2
3
4
5
6
|
public interface istudentservice { void deletebyid(string id, string a); int save(studentmodel studentmodel); void update(studentmodel studentmodel); void querybyid(string id); } |
6.2. studentserviceimpl:
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
|
@logenable : 啟動日志攔截 類上 @logevent 定義所有的模塊 方法上 @logeven 定義日志的其它的信息 @service @logenable // 啟動日志攔截 @logevent (module = moduletype.student) public class studentserviceimpl implements istudentservice { @override @logevent (event = eventtype.delete_single, desc = "刪除記錄" ) // 添加日志標(biāo)識 public void deletebyid( @logkey (keyname = "id" ) string id, string a) { system.out.printf( this .getclass() + "deletebyid id = " + id); } @override @logevent (event = eventtype.add, desc = "保存記錄" ) // 添加日志標(biāo)識 public int save(studentmodel studentmodel) { system.out.printf( this .getclass() + "save save = " + json.tojsonstring(studentmodel)); return 1 ; } @override @logevent (event = eventtype.update, desc = "更新記錄" ) // 添加日志標(biāo)識 public void update(studentmodel studentmodel) { system.out.printf( this .getclass() + "save update = " + json.tojsonstring(studentmodel)); } // 沒有日志標(biāo)識 @override public void querybyid(string id) { system.out.printf( this .getclass() + "querybyid id = " + id); } } |
執(zhí)行測試類,打印如下信息,說明我們?nèi)罩咀⒔馀渲脝⒆饔昧耍?/p>
將日志存入數(shù)據(jù)庫,日志內(nèi)容如下:
1
|
{ "admevent" : "4" , "admmodel" : "1" , "admoptcontent" : "{\"id\":\"1\"}" , "createdate" : 1525779738111 , "desc" : "刪除記錄" } |
7. 代碼
以上的詳細(xì)的代碼見下面
原文鏈接:https://blog.csdn.net/hry2015/article/details/80244765