目的:实现日志记录用户的操作
可行方法: 1、springAOP(可使用自定义注解进行灵活操作) 2、spring拦截器(a、实现HandlerInterceptor接口 b、实现WebRequestInterceptor接口)
下面简单介绍一下我用两种方法的实现过程:
1、通过实现HandlerInterceptor接口
首先要介绍一下HandlerInterceptor的主要方法:
(1)perHandle(HttpServletRequest requestm,HttpServletResponse response,Object handle)方法:
该方法在请求处理之前进行调用。SpringMVC中的Interceptor是链式调用的,在一个请求中可以同时存在多个interceptor,每个interceptor的调用会依据它声明顺序依次执行,而且最先执行的都是其中的preHandle方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,或者添加一些判断是否继续请求下去。该方法的返回值是boolean类型,当返回false时,请求结束。后续的都不会再执行,当返回true的时候会继续调用下一个interceptor的preHandler方法。如果已经是最后一个interceptor则调用controller方法。
(2)postHandel(HttpServletRequest requestm,HttpServletResponse response,Object handle,ModelAndView modelAndView)方法
preHandle方法和后续的afterCompletion方法都只能是在当前所属的Interceptor的preHandler方法的返回值为true时才能被调用。postHandler方法在Controller方法处理之后在DispatcherServlet进行视图返回渲染之前被调用,所以可以对ModelAndView对象进行操作。
(3)afterCompletion(HttpServletRequest requestm,HttpServletResponse response,Object handle,Exception ex)方法
该方法也是需要当前对应的interceptor的preHandle方法返回值为true时才会执行。改方法是在整个请求结束之后,在dispatcherServlet渲染了对饮个视图之后执行,主要作用是用于进行资源清理工作。
public class OperationInterceptor extends HandlerInterceptorAdapter {
Logger log = Logger.getLogger(OperationInterceptor.class);
public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handle){
HttpSession session = request.getSession();
String requestUri = request.getRequestURI();//完整请求路径
String contextPath = request.getContextPath();//获取上下文路径
String url = requestUri.substring(contextPath.length());
if(StringUtils.contains(url, "add") || StringUtils.contains(url, "update")
|| StringUtils.contains(url, "del")){//包含敏感操作
String user_name = (String)session.getAttribute("curr_user");
if(user_name != null){
Enumeration<String> parameterNames = request.getParameterNames();
StringBuffer sb = new StringBuffer();
while(parameterNames.hasMoreElements()){
String key = parameterNames.nextElement();
String value = request.getParameter(key);
sb.append(" "+key+":"+(value == null?"":value)+" ");
}
log.info(String.format("before user:%s request:%s params:%s",user_name,url,sb));
}
}
return true;
}
}
pom导包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
配置信息如下:
<mvc:interceptors>
<!-- 全局配置 -->
<bean id="log4UpdateOrDeleteInterceptor" class="com.netease.videolive.Interceptor.OperationInterceptor"/>
<mvc:interceptor><!-- 单独配置 -->
<mvc:mapping path="/admin/user/**"/>
<bean class="com.netease.videolive.interceptor.OperationInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
引入:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
使用拦截器控制就可以实现了,接下来我们看一下
2、通过自定义注解实现这个功能
i、开启切面编程功能
要明确我们使的是SpringMVC的AOP监测,所以配置信息一定要放在与DispatcherServle对应的xml文件下,我的项目中命名为dispatcher-servlet.xml,配错了地方是没有用的哦~
<aop:aspectj-autoproxy proxy-target-class="true"/>
ii、导包、引入与上一个方法相同的头信息、并配置开启切面编程功能
iii、编写自定义注解@LogPrint,我把他写在LogPrint.java类里
@Documented//文档生成
@Target(ElementType.METHOD)//目标是方法
@Retention(RetentionPolicy.RUNTIME)//注解会在class中存在,运行时可通过反射获取
@Inherited//允许继承父类注解
public @interface LogPrint{
String value() default "";
}
简单介绍一下以上@Retention注解:
@Retention用来修饰注解,是元注解
Retention注解有一个属性value是RetentionPolicy类型的,Enum RetentionPolicy是一个枚举类型,这个枚举类型决定了Retention应该如何去保持,两者搭配使用,RetentionPolicy有3个值:CLASS, RUNTIME,SOURCE
用@Retention(RetentionPolicy.CLASS)修饰的注解,表示注解的信息被保存在字节码文件中当程序编译时,但不会被虚拟机读取在运行的时候。
用@Retention(RetentionPolicy.SOURCE)修饰的注解,表示注解的信息会被编译器抛弃,不会留在class文件中,注解的信息只会留在源文件中;
用@Retention(RetentionPolicy.RUNTIME)修饰的注解,表示注解的信息被保存在class文件中当程序编译时,会被虚拟机保留在运行时。该方式可以让你从JVM中读取Annotation注解的信息,以便在分析程序的时候使用。
v、定义切面(AOP相关方法的编写)
@Component//表示该类是spring管理的一个bean
@Aspect//表示该类是一个切面
public class LogAspect {
@Resource(name = "videoOperationServiceImpl")
private VideoOperationServiceImpl operationService;
@Resource(name = "userServiceImpl")
private UserServiceImpl userService;
public Logger logger = Logger.getLogger(LogAspect.class);
@Pointcut(value="@annotation(com.netease.videolive.annotation.LogPrint)")
public void myPointCut(){
System.out.println("myPointCut");
}
@Before("myPointCut()")
public void before(JoinPoint jp){
try {
int id;
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Operation oper = new Operation();
HttpSession session = request.getSession();
StringBuffer sb = new StringBuffer();
String methodName = jp.getSignature().getName();//方法名
String user_name = (String)session.getAttribute("curr_user");
sb.append("操作:");
sb.append(methodName);
Object[] args = jp.getArgs();//获得参数列表
id = Integer.valueOf(args[2]+"");
sb.append(" id:"+id);
User userById = userService.getUserById(id);
if(userById != null){
if(StringUtils.contains(methodName, "update")){
oper.setType(1);//更新操作
sb.append(" 操作前:"+userById.getName()+","+userById.getNickname()+","+userById.getPassword()+" 操作后:");
for(int i=3;i<args.length;i++){
String encode = java.net.URLDecoder.decode(args[i]+"","utf-8");
if(i != (args.length-1)){
sb.append(encode+",");
}else{
sb.append(encode);
}
}
}else if(StringUtils.contains(methodName, "del")){
oper.setType(2);//删除操作
}
}
oper.setCreate_time(new Date());
oper.setCreator(user_name);
oper.setOperation(sb.toString());
operationService.insertOperat(oper);
logger.info(sb.toString());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
切面编程涉及5个方法,下面简单介绍一下:
@Before:前置通知
@After:后置【finally】通知
@AfterRunturning:后置【try】使用returning来引用方法返回值
@AfterThrowing:后置【catch】使用thrown来引用抛出的异常
@Around:环绕通知,这个决定真实的方法是否执行,而且必须有返回值
JoinPoint 涉及的几个方法介绍:
Obejct[] getArgs:返回目标方法的参数
Signature getSignature:返回目标方法的签名
Object getTarget:返回被织入增强处理的目标对象
Object getThis:返回AOP框架为目标对象生成的代理对象
vi.使用:
@LogPrint
@RequestMapping("deltestusers.do")
public void deltestusers(HttpServletResponse response, HttpServletRequest request,String id) {
logger.info("--------deltestusers.do -------------");
String msg = "0";
try {
Map<String,Object> map = new HashMap<String,Object>();
map.put("id", id);
map.put("status", -1);
userService.deluser(map);
msg = "1";
} catch (Exception e) {
logger.info("updatetestusers.do exception " + e);
}
ReturnUtil.writeBack(response, JsonUtil.getJsonString(msg));
}
遇到的坑:
getArgs()方法返回的参数列表就是使用该注解的方法签名参数哦,如果是进行更改操作,参数没有写在签名里,是不能获取到的,使用request.getParamter(name)方法也是可以的。