Spring Boot分布式系统实践【基础模块构建3.3】注解式操作日志记录实现

日志注解

前言

spring切面的编程,spring中事物处理、日志记录常常与pointcut相结合


Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的.
Pointcut可以有下列方式来定义或者通过&& || 和!的方式进行组合.


Spring AOP支持的AspectJ切入点指示符如下:
  • execution:用于匹配方法执行的连接点;
  • within:用于匹配指定类型内的方法执行;
  • this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
  • target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
  • args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;
  • @within:用于匹配所以持有指定注解类型内的方法;
  • @target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
  • @args:用于匹配当前执行的方法传入的参数持有指定注解的执行;
  • @annotation:用于匹配当前执行方法持有指定注解的方法;
  • bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;
  • reference pointcut:表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持。

AspectJ切入点支持的切入点指示符还有: call、get、set、preinitialization、staticinitialization、initialization、handler、adviceexecution、withincode、cflow、cflowbelow、if、@this、@withincode;但Spring AOP目前不支持这些指示符,使用这些指示符将抛出IllegalArgumentException异常。这些指示符Spring AOP可能会在以后进行扩展。

AspectJ类型匹配的通配符:
     *: 匹配任何数量字符
     
     ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
     
     +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
示例:
  • pointcutexp包里的任意类.

within(com.test.spring.aop.pointcutexp.*)

  • pointcutexp包和所有子包里的任意类.

within(com.test.spring.aop.pointcutexp..*)

  • 实现了Intf接口的所有类,如果Intf不是接口,限定Intf单个类.

this(com.test.spring.aop.pointcutexp.Intf)

当一个实现了接口的类被AOP的时候,用getBean方法必须cast为接口类型,不能为该类的类型.

  • 带有@Transactional标注的所有类的任意方法.

@within(org.springframework.transaction.annotation.Transactional)

@target(org.springframework.transaction.annotation.Transactional)

  • 带有@Transactional标注的任意方法.

@annotation(org.springframework.transaction.annotation.Transactional)

***> @within和@target针对类的注解,@annotation是针对方法的注解

  • 参数带有@Transactional标注的方法.

@args(org.springframework.transaction.annotation.Transactional)

  • 参数为String类型(运行是决定)的方法.

args(String)


其中 execution 是用的最多的,其格式为:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)

`returning type pattern,name pattern, and parameters pattern是必须的.

ret-type-pattern:可以为*表示任何返回值,全路径的类名等.

name-pattern:指定方法名,代表所以,set,代表以set开头的所有方法.

parameters pattern:指定方法参数(声明的类型),(..)代表所有参数,()代表一个参数,(,String)代表第一个参数为任何值,第二个为String类型.
`

举例说明:

任意公共方法的执行:

execution(public * *(..))

任何一个以“set”开始的方法的执行:

execution(* set*(..))

AccountService 接口的任意方法的执行:

execution(* com.xyz.service.AccountService.*(..))

定义在service包里的任意方法的执行:

execution(* com.xyz.service..(..))

定义在service包和所有子包里的任意类的任意方法的执行:

execution(* com.xyz.service...(..))

定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行:

execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))")

*> 最靠近(..)的为方法名,靠近.(..))的为类名或者接口名,如上例的JoinPointObjP2.(..))


注意上面两中方法的不同点出了 将 || 改成了 or ,还有就是 每个execution都被 ()包含起来,建议为了区分不同的表达式 最好都是用()包装。


@Aspect 需要 引入该bean 否则 spring将不识别。如上@Component或者xml引入

表达式中拦截符合条件的的方法,当执行行该方法时 执行相应的拦截方法,pointcut只负责 切入方法,并未执行方法体。

Aspect 几个通知注解(advice)
@Pointcut 拦截的切入点方法,注解的在方法级别之上,但是不执行方法体,只表示切入点的入口。

@Before 顾名思义 是在 切入点 之前执行 方法。

@AfterReturning 返回拦截方法的返回值 

@AfterThrowing 拦截的方法 如果抛出异常 加执行此方法 throwing="ex" 将异常返回到参数列表

@After 在之上方法执行后执行结束操作

@Around 方法执行前后

自动日志记录实现

/**
 * @Description: (系统日志注解)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    /**
     * 日志简介
     */
    String value() default "";
    /**
     * 日志类型
     */
    String type() default "sys";
}

定义切面与切点


/**
 * @Description: TODO(系统日志,切面处理类)
 * @date 2019-4-3 15:07
 */
@Aspect
@Component
public class SysLogAspect {
    @Reference(version = "1.0.0")
    private ISysLogServiceFacade  sysLogService;
    private ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
            .setNameFormat("SysLog-pool-%d").build();
    private ExecutorService singleThreadPool = new ThreadPoolExecutor(5, 10,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

    @Pointcut("@annotation( com.halburt.site.sys.annotation.SysLog)")
    public void logPointCut() { 
        
    }

    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = null ;
        Throwable ex = null;
        try {
            result = point.proceed();
        } catch (Throwable throwable) {
            ex = throwable;
        }
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        //保存日志
        saveSysLog(point, time , ex);

        return result;
    }

    private void saveSysLog(ProceedingJoinPoint joinPoint, long time ,Throwable ex ) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();
        com.halburt.site.sys.annotation.SysLog log = method.getAnnotation(com.halburt.site.sys.annotation.SysLog.class);
        if(log != null){
            //注解上的描述
            sysLog.setOperation(log.value());
            sysLog.setType(log.type());
        }
        if(ex == null){
            sysLog.setFlag(SysLog.SUCCESS);
        }else{
            sysLog.setEx(ex.toString());
            sysLog.setFlag(SysLog.ERROR);
        }
        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");

        //获取request
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        //设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        sysLog.setUserAgent(request.getHeader("user-agent"));
        sysLog.setRequestUri(request.getRequestURI());
        //请求的参数
        try{
            JSONObject json = new JSONObject();
            request.getParameterMap().forEach((key, value) -> {
                json.put(key, value[0]);
            });
            sysLog.setParams(json.toJSONString());
        }catch (Exception e){

        }
        //用户名
        try {
            Principal p = ((Principal)SecurityUtils.getSubject().getPrincipal());
            if(p != null){
                sysLog.setUserName(p.getRealname());
                sysLog.setUserId(p.getId());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        sysLog.setTime(time);
        sysLog.setCreateDate(new Date());

        singleThreadPool.execute(()-> sysLogService.save(sysLog));

    }


}

使用

    @SysLog(value="我要记录日志",type = "sys")
    @ResponseBody
    public String add(String key , HttpServletRequest request ) {
        return "ok";
    }

前端访问之后生成日志记录

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,783评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,396评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,834评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,036评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,035评论 5 362
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,242评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,727评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,376评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,415评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,463评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,140评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,734评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,809评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,028评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,521评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,119评论 2 341

推荐阅读更多精彩内容