SpringAop注解实现日志的存储

一.介绍

1.AOP的作用

  在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加。AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

2.DI 和 IOC 概念

  依赖注入或控制反转的定义中,调用者不负责被调用者的实例创建工作,该工作由Spring框架中的容器来负责,它通过开发者的配置来判断实例类型,创建后再注入调用者。由于Spring容器负责被调用者实例,实例创建后又负责将该实例注入调用者,因此称为依赖注入。而被调用者的实例创建工作不再由调用者来创建而是由Spring来创建,控制权由应用代码转移到了外部容器,控制权发生了反转,因此称为控制反转。

3.BeanFactory与ApplicationContext

  ApplicationContext是BeanFactory的子接口,也被称为应用上下文。BeanFactory提供了Spring的配置框架和基本功能,ApplicationContext则添加了更多企业级功能(如国际化的支持),他另一重要优势在于当ApplicationContext容器初始化完成后,容器中所有的 singleton Bean 也都被实例化了,也就是说当你需要使用singleton Bean 是,在应用中无需等待就可以用,而其他BeanFactory接口的实现类,则会延迟到调用 getBean()方法时构造,ApplicationContext的初始化时间会稍长些,调用getBean()是由于Bean已经构造完毕,速度会更快。因此大部分系统都使用ApplicationContext,而只在资源较少的情况下,才考虑使用BeanFactory。

4.AOP的实现策略

(1)Java SE动态代理:
    使用动态代理可以为一个或多个接口在运行期动态生成实现对象,生成的对象中实现接口的方法时可以添加增强代码,从而实现AOP。缺点是只能针对接口进行代理,另外由于动态代理是通过反射实现的,有时可能要考虑反射调用的开销。
(2)字节码生成(CGLib 动态代理)
    动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定方法,覆盖方法时可以添加增强代码,从而实现AOP。其常用工具是cglib。
(3)定制的类加载器
    当需要对类的所有对象都添加增强,动态代理和字节码生成本质上都需要动态构造代理对象,即最终被增强的对象是由AOP框架生成,不是开发者new出来的。解决的办法就是实现自定义的类加载器,在一个类被加载时对其进行增强。JBoss就是采用这种方式实现AOP功能。
(4)代码生成
    利用工具在已有代码基础上生成新的代码,其中可以添加任何横切代码来实现AOP。
(5)语言扩展
    可以对构造方法和属性的赋值操作进行增强,AspectJ是采用这种方式实现AOP的一个常见Java语言扩展。

二.使用

1.mysql数据库日志

SpringAop注解实现日志的存储

SpringAop注解实现日志的存储

最后一列为异常原因

2.数据库表

DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` int(255) NOT NULL AUTO_INCREMENT,
`user_id` int(255) DEFAULT NULL COMMENT '操作人id',
`user_name` varchar(255) DEFAULT NULL COMMENT '操作人name',
`peration` varchar(255) DEFAULT NULL COMMENT '操作内容',
`method` varchar(255) DEFAULT NULL COMMENT '请求路径',
`params` varchar(255) DEFAULT NULL COMMENT '请求参数',
`ip` varchar(255) DEFAULT NULL COMMENT '请求人ip',
`create_date` datetime DEFAULT NULL COMMENT '请求时间',
`operate_result` varchar(255) DEFAULT NULL COMMENT '请求结果',
`abnormity` text COMMENT '异常原因',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;

3.Maven文件

<!-- Spring Aop 架包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.1</version>
</dependency> <dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency> <dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.1_3</version>
</dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/fr.opensagres/org.apache.struts2.views.xdocreport -->
<dependency>
<groupId>fr.opensagres</groupId>
<artifactId>org.apache.struts2.views.xdocreport</artifactId>
<version>0.9.1</version>
</dependency> <!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-core -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>

4.日志实现类

import java.util.Date;

public class SysLog {
private Integer id; private String registerName; private Integer userId; private String userName; private String peration; private String method; private String params; private String ip; private Date createDate; private String operateResult; private String abnormity;
get和set
}

日志Mapper和Service就不贴了,Mapper逆向工程生成的 Service就一个insert方法

5.自定义注解

package com.mz.monotoring.Util.Log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 自定义日志注解
*
* @author sjl
* @date 2019-04-09 11:03
*/
@Target(ElementType.METHOD) // 方法注解
@Retention(RetentionPolicy.RUNTIME) // 运行时可见
public @interface LogAnno {
String operateType();// 记录操作功能
}

6.切面工具类

package com.mz.monotoring.Util.Log;

import com.mz.monotoring.Domain.SysLog;
import com.mz.monotoring.Model.BaseReturnModel;
import com.mz.monotoring.Service.SysLogService;
import com.mz.monotoring.Util.Logs.Utils.IpAdrressUtil;
import com.mz.monotoring.Util.Logs.Utils.JacksonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map; /**
* 切面工具类
*
* @author sjl
* @date 2019-04-09 11:07
*/
@Component
@Aspect
public class LogAopAspect { /**
* 日志Service
*/
@Autowired
private SysLogService sysLogService; BaseReturnModel model = new BaseReturnModel(); /**
* 环绕通知记录日志通过注解匹配到需要增加日志功能的方法
*
* @param
* @return
* @throws Throwable
*/
@Around("@annotation(com.mz.monotoring.Util.Log.LogAnno)")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
//保存日志
SysLog sysLog = new SysLog();
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod(); //获取操作
LogAnno operation = method.getAnnotation(LogAnno.class);
if (operation != null) {
String value = operation.operateType();
//保存获取的操作
sysLog.setPeration(value);
} //获取请求的类名
String className = joinPoint.getTarget().getClass().getName(); //获取请求的方法名
String methodName = method.getName();
sysLog.setMethod(className + "." + methodName); //请求的参数
Object[] args = joinPoint.getArgs();
//获取请求参数中携带的用户id或username存入日志库中
String s = JacksonUtil.obj2json(args);
String[] split = s.split("\\[");
String[] split1 = split[1].split("\\]");
Map<String, Object> map = JacksonUtil.json2map(split1[0]);
if (map.get("id") != null) {
sysLog.setUserId(Integer.parseInt(map.get("id").toString()));
} else if (map.get("registerName") != null) {
sysLog.setUserName(map.get("registerName").toString());
}
//将参数所在的数组转换成json
String params = null;
try {
params = JacksonUtil.obj2json(args);
} catch (Exception e) {
e.printStackTrace();
}
sysLog.setParams(params); //请求的时间
sysLog.setCreateDate(new Date()); // 获取用户名
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (!(authentication instanceof AnonymousAuthenticationToken)) {
// sysLog.setUsername(authentication.getName());
// } Object proceed = null;
try {
proceed = joinPoint.proceed();
sysLog.setOperateResult("请求正常");
} catch (Throwable e) {
e.printStackTrace();
model.setCode(500);
model.setMess("请求失败");
sysLog.setOperateResult("请求失败");
sysLog.setAbnormity(e.toString());
} //获取用户ip地址
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
sysLog.setIp(IpAdrressUtil.getIpAdrress(request)); //调用service保存SysLog实体类到数据库
sysLogService.insertSelective(sysLog); //proceed不为空时被注解的方法运行正常 else抛异常
if (proceed == null) {
return model;
} else {
return proceed;
}
}
}

7.获取ip的工具类

package com.mz.monotoring.Util.Logs.Utils;

import javax.servlet.http.HttpServletRequest;

/**
* 获取用户真实的ip地址
*
* @author sjl
* @date 2019-04-09 16:09
*/
public class IpAdrressUtil {
public static String getIpAdrress(HttpServletRequest request) {
String ip = null; //X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
String unknown = "unknown";
if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
} if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
} if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
} if (ipAddresses == null || ipAddresses.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
} //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (ipAddresses != null && ipAddresses.length() != 0) {
ip = ipAddresses.split(",")[0];
} //还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip;
}
}

8.数组转Json   Json转JavaBean   Json转Map工具类

package com.mz.monotoring.Util.Logs.Utils;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import java.util.Map; /**
* @author sjl
* @date 2019-04-09 16:09
*/
public class JacksonUtil {
private final static ObjectMapper objectMapper = new ObjectMapper(); private JacksonUtil() { } public static ObjectMapper getInstance() {
return objectMapper;
} /**
* javaBean、列表数组转换为json字符串
*/
public static String obj2json(Object obj) throws Exception {
return objectMapper.writeValueAsString(obj);
} /**
* json 转JavaBean
*/ public static <T> T json2pojo(String jsonString, Class<T> clazz) throws Exception {
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
return objectMapper.readValue(jsonString, clazz);
} /**
* json字符串转换为map
*/
public static <T> Map<String, Object> json2map(String jsonString) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper.readValue(jsonString, Map.class);
}
}

9.方法上使用  只需在方法添加自定义好的注解就好了

package com.mz.monotoring.Service.Impl;

import com.mz.monotoring.Dao.UserBeanMapper;
import com.mz.monotoring.Domain.UserBean;
import com.mz.monotoring.Model.BaseReturnModel;
import com.mz.monotoring.Service.UserBeanService;
import com.mz.monotoring.Util.Log.LogAnno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; /**
* @author sjl
* @date 2019-04-09 11:45
*/
@Service
public class UserBenaServiceImpl implements UserBeanService { @Autowired
private UserBeanMapper userBeanMapper; BaseReturnModel model = new BaseReturnModel(); @LogAnno(operateType = "添加一条用户信息")
@Override
public BaseReturnModel insertSelective(UserBean record)throws Exception {
int i = userBeanMapper.insertSelective(record); if (i > 0) {
model.setMess("成功");
model.setCode(200);
} else {
model.setCode(500);
model.setMess("失败");
}
return model;
}
}

10 在spring中加入配置文件

<context:component-scan base-package="com.qd.util.Log"/>
<!-- 激活自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 用户服务对象 -->
<bean id="SysLogService" class="com.qd.service.SysLogService" abstract="true"/>

这个在spring2.0的时候是需要的, 不然进不去切面类    我用4.0的是不需要这些的

上一篇:windows平台下载android源代码


下一篇:Spring第九篇【Spring与Hibernate整合】