概述:
????????在開發(fā)中我們需要將用戶的訪問(wèn)記錄當(dāng)作日志寫入數(shù)據(jù)庫(kù)中,如果給每個(gè)Controller層方法都加上相同的記錄日志的代碼,這樣無(wú)疑會(huì)造成代碼的冗余,也不利于程序的擴(kuò)展,所以我們可以通過(guò)aop編程,利用橫切技術(shù),動(dòng)態(tài)給每個(gè)方法添加記錄日志功能;
步驟:
1)陋葡、開啟aop注解支持;
? ??<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2)欧聘、編寫切面類;
? ??在切面類上添加@Component(將切面類添加到IOC容器)渺蒿、@Aspect(聲明該類為切面類)
3)仅醇、編寫環(huán)繞通知;
? ??在環(huán)繞通知的方法上加入@Around注解;
代碼展示:
@Around("execution(* com.itheima.controller.*.*(..))")
public Object around(ProceedingJoinPoint pjp){
? ? Object obj = null;
? ? try {
? ? ? ? //得到訪問(wèn)時(shí)間
? ? ? ? Date visitTime = new Date();
? ? ? ? //獲取到目標(biāo)對(duì)象的字節(jié)碼對(duì)象
? ? ? ? Class clazz = pjp.getTarget().getClass();
? ? ? ? //得到當(dāng)前目標(biāo)對(duì)象中方法的參數(shù)
? ? ? ? Object[] args = pjp.getArgs();
? ? ? ? //放行方法
? ? ? ? obj = pjp.proceed(args);
? ? ? ? if(!pjp.getSignature().getName().equals("ininBinder")){
? ? ? ? ? ? //得到一個(gè)日志對(duì)象
? ? ? ? ? ? SysLog log = new SysLog();
? ? ? ? ? ? //通過(guò)目標(biāo)字節(jié)碼對(duì)象獲取該對(duì)象的類名以及被代理方法的方法名;
? ? ? ? ? ? log.setMethod("類名為:"+clazz.getName()+"方法名為:"+pjp.getSignature().getName());//"類名為:"+XXX+"方法名為:"+XXX
? ? ? ? ? ? log.setExecutionTime(new Date().getTime()-visitTime.getTime());
? ? ? ? ? ? log.setIp(request.getRemoteAddr());//通過(guò)request域?qū)ο螳@取IP;
? ? ? ? ? ? log.setUrl(request.getRequestURI());//通過(guò)request域?qū)ο螳@取URI
? ? ? ? ? ? log.setUsername(request.getRemoteUser());//通過(guò)request域?qū)ο螳@取登錄的用戶名
? ? ? ? ? ? log.setVisitTime(visitTime);
? ? ? ? ? ? //日志入庫(kù)
? ? ? ? ? ? logService.save(log);
? ? ? ? }
? ? }catch (Throwable t){
? ? ? ? //還原異常(使用AOP編程時(shí)一定要注意還原異常,否則權(quán)限不足時(shí)會(huì)出現(xiàn)404)
? ? ? ? if(t.getMessage().equals("Access is denied")){
? ? ? ? ? ? throw new AccessDeniedException("權(quán)限不足沿后!");
? ? ? ? }else {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? throw new Exception("權(quán)限不足期升!");
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? t.printStackTrace();
? ? }
? ? return obj;//返回被代理方法的返回值;
}
4)、在配置文件中添加掃描切面類所在的包;
????<context:component-scan base-package="切面類所在的包名"/>
說(shuō)明:
? ??在步驟三中需要用到request域?qū)ο?所以在切面類中注入request域?qū)ο?注入request域?qū)ο笄疤崾荌OC容器中擁有request域?qū)ο?我們可以通過(guò)在web.xml文件中添加一個(gè)監(jiān)聽器,用來(lái)監(jiān)聽request域?qū)ο蟮膭?chuàng)建,request域?qū)ο髣?chuàng)建完成后自動(dòng)放入IOC容器;
????代碼實(shí)現(xiàn):
????? <listener>
? ? ? ? <!--監(jiān)聽request對(duì)象的創(chuàng)建,request創(chuàng)建完成后將其放入ioc容器-->
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
????</listener>
關(guān)注點(diǎn):
????1)烙样、代碼冗余時(shí)使用AOP編程;(例:事務(wù)、日志......)
????2)蕊肥、在封裝日志對(duì)象時(shí),有幾個(gè)參數(shù)需要通過(guò)request域?qū)ο螳@取比較方便;(例:ip谒获、url、username)
????????????????獲取ip:request.getRemoteAddr()壁却;
????????????????獲取url:request.getRequestURL()批狱;
????????????????獲取username:request.getRemoteUser();
? ? ? ? ? ? 說(shuō)明:如果不使用request域?qū)ο螳@取,也可以使用反射方法式(比較麻煩);
????3)展东、獲取請(qǐng)求所訪問(wèn)的類名+方法名:
????????????????如果我們想要在通知中獲取目標(biāo)對(duì)象的類名以及當(dāng)前被增強(qiáng)方法的方法名,我們通過(guò)反射即可實(shí)現(xiàn)赔硫;
????????????1)、通過(guò)ProceedingJoinPoint對(duì)象即可獲取目標(biāo)對(duì)象的字節(jié)碼;
? ? ????????????????代碼:ProceedingJoinPoint.getTarget().getClass();
????????????2)盐肃、通過(guò)ProceedingJoinPoint對(duì)象也可獲取當(dāng)前被增強(qiáng)方法的方法名爪膊;
? ????????????????? 代碼:ProceedingJoinPoint.getSignature().getName();
? ? 4)、由于我們需要用到request域?qū)ο笏晕覀冃枰O(jiān)聽request域?qū)ο蟮膭?chuàng)建;
????????????<listener>
????????????????????<!--監(jiān)聽request對(duì)象的創(chuàng)建,request創(chuàng)建完成后將其放入ioc容器-->
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
????????????</listener>