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的是不需要这些的

posted @ 2019-04-10 20:06  石金磊  阅读(1011)  评论(0编辑  收藏  举报