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"; }
前端访问之后生成日志记录
上一篇: .net core 与ELK(5)安装logstash
下一篇: Python字符遍历的艺术