一、说明
近期,心中萌发了做一个个人网站的想法,来一场说走就走的编程之旅。说做就做,在项目框架搭建(SpringMVC+mybatis+mysql)好了之后,开始考虑项目中日志的设计。经过考虑并结合网上的资料,决定采用注解的方式来记录访问日志。当然,目前的日志设计还不够完美,后期会在开发的过程中逐渐完善。
二、实现
2.1 关于AOP及相关注解
相对于AOP,有很多人偏向于使用拦截器来管理日志,这点要看个人的想法了。那么如何实现AOP拦截controller呢?由于默认的情况下,controller是交给jdk去代理的,因此,要想AOP能够拦截到controller,必须将其指定给cglib代理。
下面介绍一下,使用AOP拦截controller用到的注解(标红字段代表将会使用),当然,我们也可以使用配置文件的方式去定义,但是个人更喜欢将模块集中在一起,找配置文件真的很累~
@Target:注解的作用目标,即注解会对哪些对象产生作用。包括:
ElementType.TYPE 接口、类、枚举、注解
ElementType.FIELD 字段、枚举的常量
ElementType.METHOD 方法
ElementType.PARAMETER 方法里的参数
ElementType.CONSTRUCTOR 构造函数
ElementType.LOCAL_VARIABLE 局部变量
ElementType.ANNOTATION_TYPE 注解
ElementType.PACKAGE 包
@Retention:注解的保留位置,用于描述注解的生命周期,通俗的讲,@Retention注解负责定义该注解在什么范围内或条件下才会去产生作用。
RetentionPolicy.SOURCE 注解仅存在于源码中,在class字节码文件中不包含
RetentionPolicy.CLASS 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得
RetentionPolicy.RUNTIME 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Document:说明该注解将被包含在javadoc中
以上注解,再加上@Inherited、@Repeatable 注解,被称为java中的元注解。什么是元注解?
元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。
关于注解及元注解的解释,请点击这里
@Aspect:当@Aspect声明与类上时,表明这个类将会作为一个切面,即切面类。此时容器就可以读取到这个类,但前提是开启了cglib代理。
@Component:将类声明为bean并注入到容器中,spring在启动时会扫描并装载。与在配置文件中定义bean的效果相同。
@Pointcut:方法级别的注解,使用此注解后,可被其他方法引用。
与@Aspect、@Pointcut一起使用的还有5种通知型注解,也叫增强型注解:
@Before 前置通知,在方法执行前执行
@After 后置通知
@AfterReturning 后置【try】通知,放在方法头上,使用returning来引用方法返回值
@AfterThrowing 后置【catch】通知,放在方法头上,使用throwing来引用抛出的异常
@Around 环绕通知,放在方法头上,这个方法要决定真实的方法是否执行,而且必须有返回值
2.2 配置cglib代理
在spring-mvc.xml文件中加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true" />
2.3 自定义注解,用于描述日志信息
一般我们在创建自定义注解时,使用@interface会使此类默认继承annotation。代码如下:
package com.t1heluosh1.system.log.annotion; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 日志controller层注解 * * @author xuyong * */ //作用于参数和方法上 @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysControllerLog { int logType() default 100; //日志类型,默认为100-系统 int module() default 100; //操作模块,默认为100-登录 String description() default ""; //操作描述 }
自定义注解创建后,我们需要将注解作为bean注入到容器中,在spring-mvc.xml文件中加入如下代码:
<context:component-scan base-package="com.t1heluosh1.system.log" > <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
2.4 定义切面类,实现记录日志的功能
这里没有什么可以多说的,直接看代码:
package com.t1heluosh1.system.log.aspect; import java.lang.reflect.Method; import java.util.Date; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.t1heluosh1.pullulate.biz.sysLog.service.SysLogService; import com.t1heluosh1.pullulate.biz.sysLog.vo.SysLogVo; import com.t1heluosh1.pullulate.biz.user.model.User; import com.t1heluosh1.system.constant.SysParams; import com.t1heluosh1.system.log.annotion.SysControllerLog; import com.t1heluosh1.util.IPUtil; /** * 日志切点类即实现类 * * @author xuyong * */ @Aspect @Component public class SysLogAspect { //本地异常日志记录对象 private static final Logger logger = Logger.getLogger(SysLogAspect.class); @Resource private SysLogService logService; //切入点定义:controller @Pointcut("@annotation(com.t1heluosh1.system.log.annotion.SysControllerLog)") public void controllerAspect() { System.out.println("---------------------controllerAspect for log start----------------------------"); } /** * controller切入点方法实现 * * @param joinPoint */ @Before("controllerAspect()") public void doBefore(JoinPoint joinPoint) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder .getRequestAttributes()).getRequest(); //获取登录用户的信息 User user = (User)request.getSession().getAttribute(SysParams.CURRENT_USER); //获取请求IP地址 String ip = IPUtil.getRemoteHost(request); try { String methodDesc = getControllerMethodDescription(joinPoint); System.out.println("request method : " + joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()"); System.out.println("method description : " + methodDesc); System.out.println("request username : " + (user==null?"no login info found":user.getUserName())); System.out.println("request ip address : "+ ip); System.out.println("insert log infos into db start ..."); //获取相关日志参数 Object[] orgs = joinPoint.getArgs(); SysLogVo sysLogVo = null; if (orgs != null && orgs.length > 0) { for (Object obj:orgs) { if (obj instanceof SysLogVo) sysLogVo = (SysLogVo)obj; } } if (sysLogVo == null) { sysLogVo = new SysLogVo(); } //执行日志入库操作 //获取注解的信息 MethodSignature ms = (MethodSignature)joinPoint.getSignature(); Method method = ms.getMethod(); SysControllerLog log = method.getAnnotation(SysControllerLog.class); sysLogVo.setLogType(log.logType()); sysLogVo.setModule(log.module()); sysLogVo.setIpAddr(ip); sysLogVo.setUrl(request.getRequestURI()); sysLogVo.setMethodName(joinPoint.getTarget().getClass().getName()+"."+joinPoint.getSignature().getName()+"()"); sysLogVo.setMethodDesc(methodDesc); //TODO:remark可根据业务来进行改变,暂时为方法描述 sysLogVo.setRemark(log.description()); Date date = new Date(); sysLogVo.setAddTime(date); sysLogVo.setAddUser(user==null?SysParams.ADMIN_ID:String.valueOf(user.getId())); sysLogVo.setUpdateTime(date); sysLogVo.setUpdateUser(user==null?SysParams.ADMIN_ID:String.valueOf(user.getId())); logService.save(sysLogVo); System.out.println("insert log infos into db successful."); } catch (Exception e) { logger.error("--------------controllerAspect for log fail-----------------------"); logger.error("exception info : ", e); } } /** * 获取方法的描述 * * @param joinPoint * @return * @throws Exception */ @SuppressWarnings("rawtypes") private String getControllerMethodDescription(JoinPoint joinPoint) throws Exception { //获取目标类名 String targetName = joinPoint.getTarget().getClass().getName(); //获取方法名 String methodName = joinPoint.getSignature().getName(); //获取相关参数 Object[] arguments = joinPoint.getArgs(); //生成类对象 Class targetClass = Class.forName(targetName); //获取该类中的方法 Method[] methods = targetClass.getMethods(); String description = ""; for(Method method : methods) { if(!method.getName().equals(methodName)) { continue; } Class[] clazzs = method.getParameterTypes(); if(clazzs.length != arguments.length) { continue; } description = method.getAnnotation(SysControllerLog.class).description(); } return description; } }
2.5 使用demo
具体使用的方法如下:
/** * 跳转到登陆页面 * * @param request * @return * @throws Exception */ @RequestMapping(value="login") @SysControllerLog(description="跳转到登录页面",logType=100,module=100) public ModelAndView gotoLogin(HttpServletRequest request) { ModelAndView modelAndView = new ModelAndView("/show/login"); return modelAndView; }
当用户刷新页面时,控制台会打印相关的访问信息并将这些信息入库。当然,日志的使用需要根据项目来决定,每个方法前都加入注解,一是影响系统的性能,且使得访问日志的效果大打折扣;二是这种方式记录日志,对于代码还是有一定的侵入性的。最后看一下数据库记录的信息:
以上是java开发实战之springMVC使用AOP实现访问日志的管理的详细内容。更多信息请关注PHP中文网其他相关文章!