PHP8.1.21版本已发布
vue8.1.21版本已发布
jquery8.1.21版本已发布

java开发实战之springMVC使用AOP实现访问日志的管理

无忌哥哥
无忌哥哥 原创
2018-07-20 10:51:10 2721浏览

一、说明

近期,心中萌发了做一个个人网站的想法,来一场说走就走的编程之旅。说做就做,在项目框架搭建(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;
	}

当用户刷新页面时,控制台会打印相关的访问信息并将这些信息入库。当然,日志的使用需要根据项目来决定,每个方法前都加入注解,一是影响系统的性能,且使得访问日志的效果大打折扣;二是这种方式记录日志,对于代码还是有一定的侵入性的。最后看一下数据库记录的信息:

1.png

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。