记录用户的操作日志以及动态修改方法上的注解值

需求:记录用户的某些重要的具体方法动作

实现:<采用spring的AOP切面思想,对需要监控记录的方法动作设置切点(自定义注解的方式),同时利用java的反射原理实现动态修改方法上的注解值>

一 AOP的基本概念

(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知

(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用

(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式

(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类

概要:
框架:Spring + SpringMVC + Mybatis
pointcut切点:用的是Annotation自定义注解的方式,因为不需全部记录操作,只要某些关键的操作,并能够详细描述这个操作的内容,比如机构aa新增会员aaa,机构aa修改会员ccc的资料
需要的依赖(第三个):这里写图片描述

1、数据库设计一张专门记录操作日志的表

2、Annotation自定义注解类

package com.xxx.controller;
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类的方法上添加注解@AdminControllerLog表明哪些方法需要切面 
 * @author Uno 
 *@Documented:指明该注解可以用于生成doc 
 *@Inherited:该注解可以被自动继承 
 *@Retention:指明在什么级别显示该注解: 
 *  RetentionPolicy.SOURCE 注解存在于源代码中,编译时会被抛弃 
    RetentionPolicy.CLASS 注解会被编译到class文件中,但是JVM会忽略 
    RetentionPolicy.RUNTIME JVM会读取注解,同时会保存到class文件中 
  @Target:指明该注解可以注解的程序范围 
    ElementType.TYPE 用于类,接口,枚举但不能是注解 
    ElementType.FIELD 作用于字段,包含枚举值 
    ElementType.METHOD 作用于方法,不包含构造方法 
    ElementType.PARAMETER 作用于方法的参数 
    ElementType.CONSTRUCTOR 作用于构造方法 
    ElementType.LOCAL_VERIABLE 作用于本地变量或者catch语句 
    ElementType.ANNOTATION_TYPE 作用于注解 
    ElementType.PACKAGE 作用于包 
 */  

@Target(ElementType.METHOD)//此注解只能作用于方法上  
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AdminControllerLog {

    String description() default ""; //默认值为空

}

3、切面类

package com.xxx.controller;
import java.lang.reflect.Method;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.xxx.b2b.model.organ.Organ;
import com.xxx.b2b.service.operateLog.LogService;
import com.xxx.b2b.utils.JsonUtils;

/**
 * 切面类
 */

@Aspect // 标记为切面
@Component // spring扫描bean
public class OprateLogAspect {

    // 注入Service用于把操作日志保存数据库
    @Resource
    private LogService logService;

    // 常量
    private static String ORGAN_IN_SESSION = "OrganInfo";

    // Controller层切点
    // 平常这里多用的是表达式 类似@Pointcut("execution(** com.xxx.controller.Performance.perform(..))")
    @Pointcut("@annotation(com.qly.b2b.controller.AdminControllerLog)")
    public void controllerAspect() {

    }

    /**
     * 在目标方法正常完成后拦截记录Controller层用户的操作
     * 
     * @param joinPoint
     *            切点
     */
    @AfterReturning("controllerAspect()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        HttpSession session = request.getSession();
        // 读取session中的用户
        Organ organ = (Organ) session.getAttribute(ORGAN_IN_SESSION);
        // 请求的IP
        String ip = request.getRemoteAddr();
        // 获取用户请求方法的参数并序列化为JSON格式字符串  用来记录入参 这里暂时用不到
        String params = "";
        if (joinPoint.getArgs() != null && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                params += (joinPoint.getArgs()[i]) + ";";
            }
        }
        // 用户操作日志类
        AdminLog adminLog = new AdminLog();
        adminLog.setOrganId(organ.getOrganId());
        // 方法描述
        adminLog.setOperate(getControllerMethodDescription(joinPoint));
        // 持久化到数据库
        logService.insert(adminLog);
    }

    /**
     * 获取注解中对方法的description描述信息 类似@AdminControllerLog (description="查询用户")
     * 但这种description是写死的,后面我们需要利用反射实现动态修改
     */
    public static 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);
        // ps:getsDeclaredMethod会返回类所有声明的字段,包括private、protected、public,但是不包括                    父类的  
        // getMethods():则会返回包括父类的所有的public方法,和getFields一样  
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    // 获取description
                    description = method.getAnnotation(AdminControllerLog.class).description();
                    break;
                }
            }
        }
        return description;
    }

}

4、Controller类

@Controller
@RequestMapping("/organ")
public class OrganController extends BaseController {

    @Resource
    private UserService userService;

    @RequestMapping(value = "/updateUserInfo")
    @AdminControllerLog(description="修改会员资料"// 切点标记  不作反射修改,就默认这个description值
    public String updateUserInfo(@ModelAttribute("beanForm") User user, HttpServletRequest request, ModelMap modelMap)
            throws Exception {

        User u = new User();
        u.setUserId(user.getUserId());
        u.setUserName(user.getUserName());
        u.setUserPwd(user.getUserPwd());
        u.setWater(user.getWater());
        u.setRemark(user.getRemark());
        userService.updateUser(u);

        try {
            // 获取当前线程的方法名
            String name = Thread.currentThread().getStackTrace()[1].getMethodName();
            // getMethod里面的参数对应updateUserInfo方法的参数,固定形式的,不可少
            Method method = OrganController.class.getMethod(name, User.class, HttpServletRequest.class,ModelMap.class);
            // 描述信息
            String altered = "修改会员" + user.getUserId() + "资料";
            // 调用修改方法
            alterAnnotationOn(method, altered);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 执行成功跳转
        modelMap.put("returnMeg", "update"); 
        return this.userManage("hy",new UserQuery(), modelMap, request);

    }

    /**
     * 修改注解description 
     * 
     * @param method
     * @param altered
     * @throws Exception
     */
    public static void alterAnnotationOn(Method method, String altered) throws Exception {
        method.setAccessible(true);
        System.out.println(method.getName());
        boolean methodHasAnno = method.isAnnotationPresent(AdminControllerLog.class); // 是否指定类型的注释存在于此元素上
        if (methodHasAnno) {
            // 得到注解
            AdminControllerLog methodAnno = method.getAnnotation(AdminControllerLog.class);
            // 修改
            InvocationHandler h = Proxy.getInvocationHandler(methodAnno);
            // annotation注解的membervalues
            Field hField = h.getClass().getDeclaredField("memberValues");
            // 因为这个字段是 private final 修饰,所以要打开权限
            hField.setAccessible(true);
            // 获取 memberValues
            Map<String, String> memberValues = (Map) hField.get(h);
            // 修改 description属性值
            memberValues.put("description", altered);
        }
    }

5、在springmvc配置文件中加入切面声明

<aop:aspectj-autoproxy proxy-target-class="true"/>  

若有更好的解决方案,欢迎讨论!谢谢

猜你喜欢

转载自blog.csdn.net/u013068184/article/details/81146351