一、AOP的基本概念:
AOP,面向切面編程,常用於日志,事務,權限等業務處理。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容(Spring核心之一),是函數式編程的一種衍生范型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
二、AOP的幾個特征:
(1)Aspect(切面):通常是一個類,里面可以定義切入點和通知
(2)JointPoint(連接點):程序執行過程中明確的點,一般是方法的調用
(3)Advice(通知):AOP在特定的切入點上執行的增強處理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入點):就是帶有通知的連接點,在程序中主要體現為書寫切入點表達式
(5)AOP代理:AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以使JDK動態代理,也可以是CGLIB代理,前者基於接口,后者基於子類
三、具體功能實例:
(1)首先引入spring AOP所需的jar包依賴:
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.3</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.3</version> </dependency>
(2)自定義注解類:
/** * 自動日志監聽注解類 * @author AoXiang * */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysAutoLog { String module() default ""; String methods() default ""; String description() default ""; }
(3)新建切面類:
/** * 日志切面類 * @author AoXiang * */ @Aspect @Component public class SysLogAopControl{ private Logger logger = LoggerFactory.getLogger(this.getClass()); private LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); @Autowired private SysHandLogStub SysHandLogStub; @Pointcut("@annotation(cn.tisson.tc.annotation.SysAutoLog)") public void LogAspect() {} @AfterThrowing(pointcut = "LogAspect()", throwing = "e") public void doAfterThrowing(JoinPoint point, Throwable e) throws Exception { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes)ra; HttpServletRequest request = sra.getRequest(); SysHandLogEntity sysLog = new SysHandLogEntity(); Map<String, Object> map = this.getMethodDescription(point); sysLog.setModel(map.get("module").toString()); sysLog.setMethod("執行方法異常:-->" + map.get("methods").toString()); sysLog.setStatusDesc("執行方法異常:-->" + e); sysLog.setArgs(map.get("args").toString()); sysLog.setIp(GetRemoteIpUtil.getRemoteIp(request)); sysLog.setCreateDate(new Date()); SysHandLogStub.update(sysLog); } @Around("LogAspect()") public Object doAround(ProceedingJoinPoint point) { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes)ra; HttpServletRequest request = sra.getRequest(); Object result = null; try { result = point.proceed(); SysHandLogEntity sysLog = new SysHandLogEntity(); Map<String, Object> map = this.getMethodDescription(point); sysLog.setModel(map.get("module").toString()); sysLog.setMethod(map.get("methods").toString()); sysLog.setStatusDesc(map.get("description").toString()); sysLog.setArgs(map.get("args").toString()); sysLog.setIp(GetRemoteIpUtil.getRemoteIp(request)); sysLog.setCreateDate(new Date()); //用戶信息 Subject subject = SecurityUtils.getSubject(); UserVo userVo = (UserVo)subject.getPrincipal(); if (userVo == null) { userVo = (UserVo)ShiroSubject.getSessionVo(); } if(userVo != null) { sysLog.setUserRealName(userVo.getRealName()); } SysHandLogStub.update(sysLog); } catch (Throwable e) { logger.error("異常信息:{}", e.getMessage()); throw new RuntimeException(e); } return result; } @SuppressWarnings("rawtypes") public Map<String, Object> getMethodDescription(JoinPoint joinPoint) throws Exception { Map<String, Object> map = new HashMap<String, Object>(); String targetName = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); Class<?> targetClass = Class.forName(targetName); Method[] methods = targetClass.getMethods(); for (Method method : methods) { if (method.getName().equals(methodName)) { Class[] clazzs = method.getParameterTypes(); if (clazzs.length == arguments.length) { map.put("module", targetName.substring(targetName.lastIndexOf(".")+1,targetName.length())); String methodStr = method.toString().substring(0,method.toString().lastIndexOf("(") ); methodStr = methodStr.substring(methodStr.lastIndexOf(".")+1,methodStr.length() ); map.put("methods", methodStr); map.put("args", this.getArgs(method, arguments)); String desc = method.getAnnotation(SysAutoLog.class).description(); if (StringUtils.isEmpty(desc)) desc = "執行成功!"; map.put("description", desc); break; } } } return map; } private String getArgs(Method method, Object[] arguments) { StringBuilder builder = new StringBuilder("{"); String params[] = parameterNameDiscoverer.getParameterNames(method); if(params.length==0) { return "無參數"; } for (int i = 0; i < params.length; i++) { if(!"password".equals(params[i])) { builder.append(params[i]).append(" : ").append(arguments[i]).append(";"); } } return builder.append("}").toString(); } }
(4)配置springMVC.xml文件,啟動AOP支持
<!-- 該文件只注入Controller 類 --> <aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 設置使用注解的類所在的jar包 --> <context:component-scan base-package="cn.test" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>
注意,關於springMVC.xml配置文件的寫法,有幾點需要注意:
引用crawl+的博客http://www.cnblogs.com/crawl/p/7940755.html中的描述如下:
use-default-filters 屬性的默認值為 true,即使用默認的 Filter 進行包掃描,而默認的 Filter 對標有 @Service,@Controller和@Repository 的注解的類進行掃描,因為前面說過,我們希望 SpringMVC 只來控制網站的跳轉邏輯,所以我們只希望 SpringMVC 的配置掃描 @Controllerce 注解標注的類,不希望它掃描其余注解標注的類,所以設置了 use-default-filters 為 false,並使用 context:include-filter 子標簽設置其只掃描帶有 @Controller 注解標注的類。
在使用 use-default-filters 屬性時要分清楚需要掃描哪些包,是不是需要使用默認的 Filter 進行掃描。樓主稍微總結一下,即 use-default-filters="false" 需要和 context:include-filter 一起使用,而不能和 context:exclude-filter 屬性一起使用。
(5)使用方式
@ResponseBody @SysAutoLog(description="測試方法") @RequestMapping("testr") public String test(HttpServletRequest request) throws Exception {}
只需要在方法上添加@SysAutoLog(description="")即我們自定義的注解即可,description是方法描述,這樣在記錄日志的時候可以一並記錄下日志描述。
四、總結:
關於Spring AOP處理日志的實現比較簡單,當然對於日志的統一處理也不止於這一種方式,還可以使用攔截器的方式,可以根據項目具體的應用環境選擇合適的方式,有什么不當之處歡迎大家批評指正。