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数据库日志
最后一列为异常原因
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的是不需要这些的