文档章节

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

o
 osc_gu9d45li
发布于 2019/04/04 14:14
字数 1549
阅读 30
收藏 0

精选30+云产品,助力企业轻松上云!>>>

日志注解

前言

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

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
十三的博文作品

Spring Boot 大型线上商城项目实战教程 SpringBoot + Mybatis + Thymeleaf 搭建美观实用的个人博客 23 个实验带你轻松玩转 Spring Boot Spring Boot 入门及前后端分离项目实践 从零开始搭建一...

我是13
2017/03/28
0
0
spring boot基础学习教程

Spring boot 标签(空格分隔): springboot HelloWorld 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使...

osc_ha0jymdv
2018/04/02
2
0
stylefeng-Roses/roses-kernel

roses-kernel 项目骨架,开发利器 介绍 本项目为Roses系列微服务框架的模块之一,Roses基于和,致力做更简洁的分布式和服务化解决方案,Roses拥有高效率的开发体验,提供可靠消息最终一致性分...

stylefeng-Roses
2018/09/16
0
0
Springboot+SpringCloud项目脚手架

系统介绍 spring-cloud-plus 是以spring-cloud-alibaba为基础并整合一些常用框架的分布式基础开发平台。项目以组件模块的方式构建,实现项目模块可插拔组装。工作中遇到的一些常用框架,我都...

葫芦胡
04/24
99
0
Spring Boot和Spring cloud

微服务框架SpringBoot简单验证 首先摘录部分IBM网站部分内容对框架做一个简单说明 http://www.ibm.com/developerworks/cn/java/j-lo-spring-boot/ Spring 框架对于很多 Java 开发人员来说都不...

osc_ph7q2o1c
2018/06/11
11
0

没有更多内容

加载失败,请刷新页面

加载更多

使用amoeba实现mysql读写分离

转载马士兵连老师笔记 使用amoeba实现mysql读写分离 1、什么是amoeba? Amoeba(变形虫)项目,专注 分布式数据库 proxy 开发。座落与Client、DB Server(s)之间。对客户端透明。具有负载均衡、...

兵荒马乱的青春
16分钟前
0
0
学Vue,就要学会vue JSX(二)

学习JSX,先了解一下createElement 提到JSX,不可避免的就要提到createElement,当你看完本节,你会发现,奇怪的知识又增多了。ok,我们接着上一部分继续讲。这一次的准备工作是了解createEleme...

osc_kurqu050
17分钟前
12
0
学Vue,就要学会vue JSX(三)

是时候使用JSX代替createElement了 接着上面的讲,当我们看到上面用createElement去实现组件,太麻烦了,别说工作效率提高了,就是那些嵌套可以嵌套正确就很赞了,所以我们需要用JSX去简化整...

osc_tq5hz9vv
18分钟前
13
0
protocol buffer使用

protocol buffer使用例子 protocol buffer是什么 https://developers.google.com/protocol-buffers 这是protocol buffer的官方网站,上边有详细的使用方式。 一般常见的序列和反序列方式就是...

RandomObject
19分钟前
7
0
小白的前端之路-HTMl

HTML——超文本标记语言 HTMl里面有标签,标签又分为单标签和双标签,也分为行级元素和块级元素 标签是用<>包裹起来的,而且必须要有<>,否则会直接显示在浏览器上面哦 现在介绍一下常用标签...

osc_e45irv7l
20分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部