项目要加一个日志记录功能,需要对一些敏感信息进行一个日志记录,以前做过登陆的日志记录,做法很简单,就是在登陆方法后头加上日志的数据库插入,这样做一处还好,现在需要跟多操作都加上日志记录,如果每个地方都加上日志的数据库日志插入,即繁琐,代码的重复度也很大。Spring AOP的面向切入编程很好的解决了这个问题,通过在切入点(方法前,方法后,方法异常)植入通知(日志记录,事务管理...),这样就可以很灵活方便的在一些方法上加上一些跟业务逻辑无关的操作,可见AOP是对OOP的一个很好的补充。
下面就直接说说日记功能的实现步骤:
1.配置applicationContext-mvc.xml
加上这样的配置
<mvc:annotation-driven />
<!-- 激活组件扫描功能,在包cn.edu.pzxx.scampus及其子包下面自动扫描通过注解配置的组件-->
<context:component-scan base-package="cn.edu.pzxx.scampus" />
<!-- 启动对@AspectJ注解的支持 -->
<!-- proxy-target-class等于true是强制使用cglib代理,proxy-target-class默认是false,如果你的类实现了接口 就走JDK代理,如果没有,走cglib代理 -->
<!-- 注:对于单利模式建议使用cglib代理,虽然JDK动态代理比cglib代理速度快,但性能不如cglib -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
2.创建日志实体
package cn.edu.pzxx.scampus.sys.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import cn.edu.pzxx.scampus.core.base.BaseEntity;
@Entity
@Table(name="sys_log")
public class Log extends BaseEntity{
/**
* @Fields serialVersionUID : TODO
*/
private static final long serialVersionUID = 1L;
/**
* 描述
*/
@Column(length = 255)
private String description;
/**
* 方法名
*/
@Column(length = 255)
private String method;
/**
* 日志类型
*/
@Column
private String logType;
/**
* 请求ip
*/
@Column(length = 64)
private String requestIp;
/**
* 异常代码
*/
@Column(length = 64)
private String exceptioncode;
/**
* 异常详细
*/
@Column(length = 255)
private String exceptionDetail;
/**
* 参数
*/
@Column(length = 64)
private String params;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description == null ? null : description.trim();
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method == null ? null : method.trim();
}
public String getLogType() {
return logType;
}
public void setLogType(String logType) {
this.logType = logType;
}
public String getRequestIp() {
return requestIp;
}
public void setRequestIp(String requestIp) {
this.requestIp = requestIp == null ? null : requestIp.trim();
}
public String getExceptioncode() {
return exceptioncode;
}
public void setExceptioncode(String exceptioncode) {
this.exceptioncode = exceptioncode == null ? null : exceptioncode.trim();
}
public String getExceptionDetail() {
return exceptionDetail;
}
public void setExceptionDetail(String exceptionDetail) {
this.exceptionDetail = exceptionDetail == null ? null : exceptionDetail.trim();
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params == null ? null : params.trim();
}
}
3.日志的dao,service具体代码就不放出来,大致就是对日志实体的增删查
@Repository("logDao")
public class LogDao extends BaseDAO{
/**
这里省略日志实体增删代码
*/
}
public interface LogService extends BaseService{
public PageModel<Log> listLog(PageModel<Log> pageModel);
}
@Service("logService")
public class LogServiceImpl extends BaseServiceImpl implements LogService{
/**
这里省略日志实体增删代码
*/
}
4.创建日志注解
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;
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ILog {
/** 要执行的操作类型比如:add操作 **/
public String operationType() default "";
/** 要执行的具体操作比如:添加用户 **/
public String operationName() default "";
}
5.编写切面
package cn.edu.pzxx.scampus.sys.aspect;
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.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import cn.edu.pzxx.scampus.hr.entity.Teacher;
import cn.edu.pzxx.scampus.jiaowu.entity.JwStudentInfo;
import cn.edu.pzxx.scampus.sys.entity.Log;
import cn.edu.pzxx.scampus.sys.entity.User;
import cn.edu.pzxx.scampus.sys.service.ILog;
import cn.edu.pzxx.scampus.sys.service.LogService;
import cn.edu.pzxx.scampus.utils.Constants;
@Aspect
@Component
public class LogAspect {
@Resource
private LogService logService;
private static final Logger logger = LoggerFactory.getLogger(LogAspect. class);
//Controller层切点,这里如果需要配置多个切入点用“||”
@Pointcut("execution(* cn.edu.pzxx.scampus.*.action.*.login(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.save*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.add*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.insert*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.del*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.remove*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.new*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.edit*(..))"
+ "||execution(* cn.edu.pzxx.scampus.*.action.*.update*(..))")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) {
System.out.println("==========执行controller前置通知===============");
if(logger.isInfoEnabled()){
logger.info("before " + joinPoint);
}
}
//配置controller环绕通知,使用在方法aspect()上注册的切入点
// @Around("controllerAspect()")
public void around(JoinPoint joinPoint){
System.out.println("==========开始执行controller环绕通知===============");
long start = System.currentTimeMillis();
try {
((ProceedingJoinPoint) joinPoint).proceed();
long end = System.currentTimeMillis();
if(logger.isInfoEnabled()){
logger.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");
}
System.out.println("==========结束执行controller环绕通知===============");
} catch (Throwable e) {
long end = System.currentTimeMillis();
if(logger.isInfoEnabled()){
logger.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());
}
}
}
/**
* 后置通知 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@After("controllerAspect()")
public void after(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
//读取session中的用户
String userType=(String)session.getAttribute(Constants.USERTYPE);
Teacher teacher=null;
JwStudentInfo student=null;
if("1".equals(userType)) {
teacher = (Teacher)session.getAttribute(Constants.LOGINUSER);
}else if("2".equals(userType)) {
student = (JwStudentInfo)session.getAttribute(Constants.LOGINUSER);
}
//请求的IP
String ip = request.getRemoteAddr();
try {
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 operationType = "";
String operationName = "";
String operator=teacher!=null?teacher.getTeacherName():(student!=null?student.getStudentName():(String)session.getAttribute(Constants.SUPERADMIN));
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operationType = method.getAnnotation(ILog.class).operationType();
operationName = method.getAnnotation(ILog.class).operationName();
break;
}
}
}
//*========控制台输出=========*//
System.out.println("=====controller后置通知开始=====");
System.out.println("请求方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
System.out.println("方法描述:" + operationName);
System.out.println("请求人:" + operator);
System.out.println("请求IP:" + ip);
//*========数据库日志=========*//
Log log = new Log();
log.setDescription(operationName);
log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
log.setLogType("INFO");
log.setRequestIp(ip);
log.setExceptioncode( null);
log.setExceptionDetail( null);
log.setParams( null);
//保存数据库
logService.save(log);
System.out.println("=====controller后置通知结束=====");
} catch (Exception e) {
//记录本地异常日志
logger.error("==后置通知异常==");
logger.error("异常信息:{}", e.getMessage());
}
}
//配置后置返回通知,使用在方法aspect()上注册的切入点
@AfterReturning("controllerAspect()")
public void afterReturn(JoinPoint joinPoint){
System.out.println("=====执行controller后置返回通知=====");
if(logger.isInfoEnabled()){
logger.info("afterReturn " + joinPoint);
}
}
/**
* 异常通知 用于拦截记录异常日志
*
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "controllerAspect()", throwing="e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
//读取session中的用户
String userType=(String)session.getAttribute(Constants.USERTYPE);
Teacher teacher=null;
JwStudentInfo student=null;
if("1".equals(userType)) {
teacher = (Teacher)session.getAttribute(Constants.LOGINUSER);
}else if("2".equals(userType)) {
student = (JwStudentInfo)session.getAttribute(Constants.LOGINUSER);
}
//请求的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 += JsonUtil.getJsonStr(joinPoint.getArgs()[i]) + ";";
}
}
try {
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 operationType = "";
String operationName = "";
String operator=teacher!=null?teacher.getTeacherName():(student!=null?student.getStudentName():(String)session.getAttribute(Constants.SUPERADMIN));
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
operationType = method.getAnnotation(ILog.class).operationType();
operationName = method.getAnnotation(ILog.class).operationName();
break;
}
}
}
/*========控制台输出=========*/
System.out.println("=====异常通知开始=====");
System.out.println("异常代码:" + e.getClass().getName());
System.out.println("异常信息:" + e.getMessage());
System.out.println("异常方法:" + (joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()")+"."+operationType);
System.out.println("方法描述:" + operationName);
System.out.println("请求人:" + operator);
System.out.println("请求IP:" + ip);
System.out.println("请求参数:" + params);
/*==========数据库日志=========*/
Log log = new Log();
log.setDescription(operationName);
log.setExceptioncode(e.getClass().getName());
log.setLogType("ERROR");
log.setExceptionDetail(e.getMessage());
log.setMethod((joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName() + "()"));
log.setParams(params);
log.setRequestIp(ip);
//保存数据库
logService.save(log);
System.out.println("=====异常通知结束=====");
} catch (Exception ex) {
//记录本地异常日志
logger.error("==异常通知异常==");
logger.error("异常信息:{}", ex.getMessage());
}
/*==========记录本地异常日志==========*/
logger.error("异常方法:{}异常代码:{}异常信息:{}参数:{}", new Object[]{joinPoint.getTarget().getClass().getName() + joinPoint.getSignature().getName(), e.getClass().getName(), e.getMessage(), params});
}
}
这里spring的ServletRequestAttributes创建request()得到空指针
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
查看RequestContextHolder源代码,发现该类中有提到RequestContextListener
再查看RequestContextListener的源代码,其中有个方法
public void requestInitialized(ServletRequestEvent requestEvent) {
if (!(requestEvent.getServletRequest() instanceof HttpServletRequest)) {
throw new IllegalArgumentException(
“Request is not an HttpServletRequest: ” +
requestEvent.getServletRequest());
}
HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest();
ServletRequestAttributes attributes = new ServletRequestAttributes(request);
request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes);
LocaleContextHolder.setLocale(request.getLocale());
RequestContextHolder.setRequestAttributes(attributes); //把requestAttributes的属性设置好
if (logger.isDebugEnabled()) {
logger.debug(“Bound request context to thread: ” + request);
} }
所以,要在web.xml下面配置好监听,让服务器启动时就初始化改类,可以得到request
org.springframework.web.context.request.RequestContextListener
这样就可以了正常执行
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
6.这里我是在action植入通知,当然在service层植入也行,看情况而定
@ILog(operationType=”login”,operationName=”登陆”)
public String login() {
写你登陆的代码……
}
这样就会在执行登陆操作时植入日志记录的通知,本来一切到这里好像结束了,但是我在做的过程还是出现几个问题,就是我在action注入service bean竟然会为空,如@Autowired private UserService userService 是为null,后面发现去除了的开启就可以了,这里边到底有什么不同,一时间也没搞明白,但是却在网上找到了个类似的解决方法,如下,详细可以看看这个原由
@Aspect作用于action,致使action中的@Autowired注入为null的解决方案,以下三种任选一种:
1、去掉@Autowired,改用set,get注入
2、将action纳入spring的ioc管理
3、修改Struts.xml文件的属性,
<constant name="struts.objectFactory.spring.autoWire.alwaysRespect" value="true" />
使自动注入总是有效
这里我选择了第三种方法,果然service的注入就奏效了。