今天繼續實現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>
接下來開始編寫代碼。
創建日志類實體
1 public class SystemLog { 2 private String id; 3 4 private String description; 5 6 private String method; 7 8 private Long logType; 9 10 private String requestIp; 11 12 private String exceptioncode; 13 14 private String exceptionDetail; 15 16 private String params; 17 18 private String createBy; 19 20 private Date createDate; 21 22 public String getId() { 23 return id; 24 } 25 26 public void setId(String id) { 27 this.id = id == null ? null : id.trim(); 28 } 29 30 public String getDescription() { 31 return description; 32 } 33 34 public void setDescription(String description) { 35 this.description = description == null ? null : description.trim(); 36 } 37 38 public String getMethod() { 39 return method; 40 } 41 42 public void setMethod(String method) { 43 this.method = method == null ? null : method.trim(); 44 } 45 46 public Long getLogType() { 47 return logType; 48 } 49 50 public void setLogType(Long logType) { 51 this.logType = logType; 52 } 53 54 public String getRequestIp() { 55 return requestIp; 56 } 57 58 public void setRequestIp(String requestIp) { 59 this.requestIp = requestIp == null ? null : requestIp.trim(); 60 } 61 62 public String getExceptioncode() { 63 return exceptioncode; 64 } 65 66 public void setExceptioncode(String exceptioncode) { 67 this.exceptioncode = exceptioncode == null ? null : exceptioncode.trim(); 68 } 69 70 public String getExceptionDetail() { 71 return exceptionDetail; 72 } 73 74 public void setExceptionDetail(String exceptionDetail) { 75 this.exceptionDetail = exceptionDetail == null ? null : exceptionDetail.trim(); 76 } 77 78 public String getParams() { 79 return params; 80 } 81 82 public void setParams(String params) { 83 this.params = params == null ? null : params.trim(); 84 } 85 86 public String getCreateBy() { 87 return createBy; 88 } 89 90 public void setCreateBy(String createBy) { 91 this.createBy = createBy == null ? null : createBy.trim(); 92 } 93 94 public Date getCreateDate() { 95 return createDate; 96 } 97 98 public void setCreateDate(Date createDate) { 99 this.createDate = createDate; 100 } 101 } View Code編寫dao接口
1 package com.gcx.dao; 2 3 import com.gcx.entity.SystemLog; 4 5 public interface SystemLogMapper { 6 int deleteByPrimaryKey(String id); 7 8 int insert(SystemLog record); 9 10 int insertSelective(SystemLog record); 11 12 SystemLog selectByPrimaryKey(String id); 13 14 int updateByPrimaryKeySelective(SystemLog record); 15 16 int updateByPrimaryKey(SystemLog record); 17 } View Code編寫service層
1 package com.gcx.service; 2 3 import com.gcx.entity.SystemLog; 4 5 public interface SystemLogService { 6 7 int deleteSystemLog(String id); 8 9 int insert(SystemLog record); 10 11 int insertTest(SystemLog record); 12 13 SystemLog selectSystemLog(String id); 14 15 int updateSystemLog(SystemLog record); 16 } View Code編寫service實現類serviceImpl
1 package com.gcx.service.impl; 2 3 import javax.annotation.Resource; 4 5 import org.springframework.stereotype.Service; 6 7 import com.gcx.annotation.Log; 8 import com.gcx.dao.SystemLogMapper; 9 import com.gcx.entity.SystemLog; 10 import com.gcx.service.SystemLogService; 11 12 @Service("systemLogService") 13 public class SystemLogServiceImpl implements SystemLogService { 14 15 @Resource 16 private SystemLogMapper systemLogMapper; 17 18 @Override 19 public int deleteSystemLog(String id) { 20 21 return systemLogMapper.deleteByPrimaryKey(id); 22 } 23 24 @Override 25 26 public int insert(SystemLog record) { 27 28 return systemLogMapper.insertSelective(record); 29 } 30 31 @Override 32 public SystemLog selectSystemLog(String id) { 33 34 return systemLogMapper.selectByPrimaryKey(id); 35 } 36 37 @Override 38 public int updateSystemLog(SystemLog record) { 39 40 return systemLogMapper.updateByPrimaryKeySelective(record); 41 } 42 43 @Override 44 public int insertTest(SystemLog record) { 45 46 return systemLogMapper.insert(record); 47 } 48 49 } View Code到這裡基本程序編寫完畢
下面開始自定義注解
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層的是用來記錄功能的日志,運行結果如下。
這樣做的話不知道在實際的項目中運行效率好不好,在這裡請看到博客的大牛給點建議!!