• 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

  • 相关阅读:
    百度网盘提速方法
    2020年北京某企Java校招真题
    scrapy中选择器的用法
    scrapy框架基础篇
    selenium模拟浏览器爬取淘宝产品信息
    python连接MongoDB
    pyquery库
    BeautifulSoup4库
    Locust
    【Java】类赋值的使用方式
  • 原文地址:https://www.cnblogs.com/Halburt/p/10654591.html
Copyright © 2020-2023  润新知