首页 Java java教程 java开发实战之springMVC使用AOP实现访问日志的管理

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

Jul 20, 2018 am 10:51 AM

一、说明

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

以上是java开发实战之springMVC使用AOP实现访问日志的管理的详细内容。更多信息请关注PHP中文网其他相关文章!

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

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)