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

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

实现:<采用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的资料
需要的依赖(第三个):

		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>1.8.9</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjtools</artifactId>
			<version>1.8.9</version>
		</dependency>
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjweaver</artifactId>
			<version>1.7.4</version>
		</dependency>

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 {
    // 注解格式 例子@AdminControllerLog(description="修改会员资料")
	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"/>  

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值