今天繼續實現AOP,到這裡我個人認為是最靈活,可擴展的方式了,就拿日志管理來說,用Spring AOP 自定義注解形式實現日志管理。廢話不多說,直接開始!!!
關於配置我還是的再說一遍。
在applicationContext-mvc.xml中要添加的
<mvc:annotation-driven />
<!-- 激活組件掃描功能,在包com.gcx及其子包下面自動掃描通過注解配置的組件 -->
<context:component-scan base-package="com.gcx" />
<!-- 啟動對@AspectJ注解的支持 -->
<!-- proxy-target-class等於true是強制使用cglib代理,proxy-target-class默認是false,如果你的類實現了接口 就走JDK代理,如果沒有,走cglib代理 -->
<!-- 注:對於單利模式建議使用cglib代理,雖然JDK動態代理比cglib代理速度快,但性能不如cglib -->
<!--如果不寫proxy-target-class="true"這句話也沒問題-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
<!--切面-->
<bean id="systemLogAspect" class="com.gcx.annotation.SystemLogAspect"></bean>
接下來開始編寫代碼。
創建日志類實體

編寫dao接口

編寫service層

編寫service實現類serviceImpl

到這裡基本程序編寫完畢
下面開始自定義注解
1 package com.gcx.annotation;
2
3 import java.lang.annotation.*;
4
5 @Target({ElementType.PARAMETER, ElementType.METHOD})
6 @Retention(RetentionPolicy.RUNTIME)
7 @Documented
8 public @interface Log {
9
10 /** 要執行的操作類型比如:add操作 **/
11 public String operationType() default "";
12
13 /** 要執行的具體操作比如:添加用戶 **/
14 public String operationName() default "";
15 }
下面編寫切面
1 package com.gcx.annotation;
2
3 import java.lang.reflect.Method;
4 import java.util.Date;
5 import java.util.UUID;
6
7 import javax.annotation.Resource;
8 import javax.servlet.http.HttpServletRequest;
9 import javax.servlet.http.HttpSession;
10
11 import org.aspectj.lang.JoinPoint;
12 import org.aspectj.lang.ProceedingJoinPoint;
13 import org.aspectj.lang.annotation.After;
14 import org.aspectj.lang.annotation.AfterReturning;
15 import org.aspectj.lang.annotation.AfterThrowing;
16 import org.aspectj.lang.annotation.Around;
17 import org.aspectj.lang.annotation.Aspect;
18 import org.aspectj.lang.annotation.Before;
19 import org.aspectj.lang.annotation.Pointcut;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.springframework.stereotype.Component;
23
24 import com.gcx.entity.SystemLog;
25 import com.gcx.entity.User;
26 import com.gcx.service.SystemLogService;
27 import com.gcx.util.JsonUtil;
28
29 /**
30 * @author 楊建
31 * @E-mail: email
32 * @version 創建時間:2015-10-19 下午4:29:05
33 * @desc 切點類
34 */
35
36 @Aspect
37 @Component
38 public class SystemLogAspect {
39
40 //注入Service用於把日志保存數據庫
41 @Resource //這裡我用resource注解,一般用的是@Autowired,他們的區別如有時間我會在後面的博客中來寫
42 private SystemLogService systemLogService;
43
44 private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
45
46 //Controller層切點
47 @Pointcut("execution (* com.gcx.controller..*.*(..))")
48 public void controllerAspect() {
49 }
50
51 /**
52 * 前置通知 用於攔截Controller層記錄用戶的操作
53 *
54 * @param joinPoint 切點
55 */
56 @Before("controllerAspect()")
57 public void doBefore(JoinPoint joinPoint) {
58 System.out.println("==========執行controller前置通知===============");
59 if(logger.isInfoEnabled()){
60 logger.info("before " + joinPoint);
61 }
62 }
63
64 //配置controller環繞通知,使用在方法aspect()上注冊的切入點
65 @Around("controllerAspect()")
66 public void around(JoinPoint joinPoint){
67 System.out.println("==========開始執行controller環繞通知===============");
68 long start = System.currentTimeMillis();
69 try {
70 ((ProceedingJoinPoint) joinPoint).proceed();
71 long end = System.currentTimeMillis();
72 if(logger.isInfoEnabled()){
73 logger.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
74 }
75 System.out.println("==========結束執行controller環繞通知===============");
76 } catch (Throwable e) {
77 long end = System.currentTimeMillis();
78 if(logger.isInfoEnabled()){
79 logger.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
80 }
81 }
82 }
83
84 /**
85 * 後置通知 用於攔截Controller層記錄用戶的操作
86 *
87 * @param joinPoint 切點
88 */
89 @After("controllerAspect()")
90 public void after(JoinPoint joinPoint) {
91
92 /* HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
93 HttpSession session = request.getSession(); */
94 //讀取session中的用戶
95 // User user = (User) session.getAttribute("user");
96 //請求的IP
97 //String ip = request.getRemoteAddr();
98 User user = new User();
99 user.setId(1);
100 user.setName("張三");
101 String ip = "127.0.0.1";
102 try {
103
104 String targetName = joinPoint.getTarget().getClass().getName();
105 String methodName = joinPoint.getSignature().getName();
106 Object[] arguments = joinPoint.getArgs();
107 Class targetClass = Class.forName(targetName);
108 Method[] methods = targetClass.getMethods();
109 String operationType = "";
110 String operationName = "";
111 for (Method method : methods) {
112 if (method.getName().equals(methodName)) {
113 Class[] clazzs = method.getParameterTypes();
114 if (clazzs.length == arguments.length) {
115 operationType = method.getAnnotation(Log.class).operationType();
116 operationName = method.getAnnotation(Log.class).operationName();
117 break;
118 }
119 }
120 }
121 //*========控制台輸出=========*//
122 System.out.println("=====controller後置通知開始=====");
123 System.out.println("請求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
124 System.out.println("方法描述:" + operationName);
125 System.out.println("請求人:" + user.getName());
126 System.out.println("請求IP:" + ip);
127 //*========數據庫日志=========*//
128 SystemLog log = new SystemLog();
129 log.setId(UUID.randomUUID().toString());
130 log.setDescription(operationName);
131 log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
132 log.setLogType((long)0);
133 log.setRequestIp(ip);
134 log.setExceptioncode( null);
135 log.setExceptionDetail( null);
136 log.setParams( null);
137 log.setCreateBy(user.getName());
138 log.setCreateDate(new Date());
139 //保存數據庫
140 systemLogService.insert(log);
141 System.out.println("=====controller後置通知結束=====");
142 } catch (Exception e) {
143 //記錄本地異常日志
144 logger.error("==後置通知異常==");
145 logger.error("異常信息:{}", e.getMessage());
146 }
147 }
148
149 //配置後置返回通知,使用在方法aspect()上注冊的切入點
150 @AfterReturning("controllerAspect()")
151 public void afterReturn(JoinPoint joinPoint){
152 System.out.println("=====執行controller後置返回通知=====");
153 if(logger.isInfoEnabled()){
154 logger.info("afterReturn " + joinPoint);
155 }
156 }
157
158 /**
159 * 異常通知 用於攔截記錄異常日志
160 *
161 * @param joinPoint
162 * @param e
163 */
164 @AfterThrowing(pointcut = "controllerAspect()", throwing="e")
165 public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
166 /*HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
167 HttpSession session = request.getSession();
168 //讀取session中的用戶
169 User user = (User) session.getAttribute(WebConstants.CURRENT_USER);
170 //獲取請求ip
171 String ip = request.getRemoteAddr(); */
172 //獲取用戶請求方法的參數並序列化為JSON格式字符串
173
174 User user = new User();
175 user.setId(1);
176 user.setName("張三");
177 String ip = "127.0.0.1";
178
179 String params = "";
180 if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
181 for ( int i = 0; i < joinPoint.getArgs().length; i++) {
182 params += JsonUtil.getJsonStr(joinPoint.getArgs()[i]) + ";";
183 }
184 }
185 try {
186
187 String targetName = joinPoint.getTarget().getClass().getName();
188 String methodName = joinPoint.getSignature().getName();
189 Object[] arguments = joinPoint.getArgs();
190 Class targetClass = Class.forName(targetName);
191 Method[] methods = targetClass.getMethods();
192 String operationType = "";
193 String operationName = "";
194 for (Method method : methods) {
195 if (method.getName().equals(methodName)) {
196 Class[] clazzs = method.getParameterTypes();
197 if (clazzs.length == arguments.length) {
198 operationType = method.getAnnotation(Log.class).operationType();
199 operationName = method.getAnnotation(Log.class).operationName();
200 break;
201 }
202 }
203 }
204 /*========控制台輸出=========*/
205 System.out.println("=====異常通知開始=====");
206 System.out.println("異常代碼:" + e.getClass().getName());
207 System.out.println("異常信息:" + e.getMessage());
208 System.out.println("異常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
209 System.out.println("方法描述:" + operationName);
210 System.out.println("請求人:" + user.getName());
211 System.out.println("請求IP:" + ip);
212 System.out.println("請求參數:" + params);
213 /*==========數據庫日志=========*/
214 SystemLog log = new SystemLog();
215 log.setId(UUID.randomUUID().toString());
216 log.setDescription(operationName);
217 log.setExceptioncode(e.getClass().getName());
218 log.setLogType((long)1);
219 log.setExceptionDetail(e.getMessage());
220 log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
221 log.setParams(params);
222 log.setCreateBy(user.getName());
223 log.setCreateDate(new Date());
224 log.setRequestIp(ip);
225 //保存數據庫
226 systemLogService.insert(log);
227 System.out.println("=====異常通知結束=====");
228 } catch (Exception ex) {
229 //記錄本地異常日志
230 logger.error("==異常通知異常==");
231 logger.error("異常信息:{}", ex.getMessage());
232 }
233 /*==========記錄本地異常日志==========*/
234 logger.error("異常方法:{}異常代碼:{}異常信息:{}參數:{}", joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params);
235
236 }
237
238 }
我這裡寫的比較全,前置通知,環繞通知,後置通知,異常通知,後置飯後通知,都寫上了,在我們實際編寫中不寫全也沒事,我習慣上把記錄日志的邏輯寫在後置通知裡面,我看網上也有些在前置通知裡面的,但我感覺寫在後置通知裡比較好。
下面開始在controller中加入自定義的注解!!
1 package com.gcx.controller;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.stereotype.Controller;
5 import org.springframework.web.bind.annotation.RequestMapping;
6
7 import com.gcx.annotation.Log;
8 import com.gcx.service.UserService;
9
10 @Controller
11 @RequestMapping("userController")
12 public class UserController {
13
14 @Autowired
15 private UserService userService;
16
17 @RequestMapping("testAOP")
18 @Log(operationType="add操作:",operationName="添加用戶")
19 public void testAOP(String userName,String password){
20 userService.addUser(userName, password);
21 }
22 }
下面編寫測試類
1 @Test
2 public void testAOP1(){
3 //啟動Spring容器
4 ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"classpath:applicationContext-mvc.xml","classpath:applicationContext-dataSource.xml"});
5 //獲取service或controller組件
6 UserController userController = (UserController) ctx.getBean("userController");
7 userController.testAOP("zhangsan", "123456");
8 }
9

數據庫數據:

我原本想寫兩個切點,一個是service層,一個是controller層,service層是用來記錄異常信息的日志,而controller層的是用來記錄功能的日志,運行結果如下。 
這樣做的話不知道在實際的項目中運行效率好不好,在這裡請看到博客的大牛給點建議!!