博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面向切面编程-日志切面应用
阅读量:5992 次
发布时间:2019-06-20

本文共 12164 字,大约阅读时间需要 40 分钟。

简介:

  AOP:面向切面编程,即拓展功能不通过修改源代码实现,采用横向抽取机制,取代了传统的纵向继承体系重复性代码。在运行期通过代理方式向目标类织入增强代码。

  Aspecj:Aspecj 是一个基于java语言的AOP框架,spring2.0开始,spring AOP引入对Aspect的支持,Aspect扩展了Java语言,提供了一个专门的编译器,在编译时提供横向代码的织入。

  使用aspectj实现aop有两种方式

    (1)基于aspectj的xml配置
    (2)基于aspectj的注解方式

Spring AOP底层代理机制:

1、JDK动态代理:针对实现了接口的类产生代理

2、CGLib动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强技术来生成当前类的子类对象。

 

面向切面相关术语:

  JoinPoint(连接点):能被拦截的点,即类里面可以被增强的方法,这些方法称为连接点,Spring只支持方法类型的连接点。

  PointCut(切入点):实际对JoinPoint中拦截的点,即实际增强的方法成为切入点。

  Advice(通知/增强):所谓增强是指拦截到JoinPoint之后所要做的事情。(比如扩展的日志功能就是增强),通知分为:

    前置增强@Before:在方法(切入点)之前执行的增强

    后置增强@After:在方法(切入点)之后执行的增强

    异常增强@AfterThrowing:在方法执行出现异常的时候执行的增强

    //最终增强:在后置通知之后执行,无论目标方法是否出现异常,都会执行的增强,好像没有这个

    环绕增强@Around:在方法之前和之后都执行的增强

    返回增强@AfterReturning:在方法正常返回之后执行

  Aspect(切面):切入点和增强的结合,即将通知应用到具体方法上的过程

  Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field.(一般不用)

  Target(目标对象):代理的目标对象,即要增强的类

  Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程

  Proxy( 代理) :一个类被 AOP 织入增强后, 就产生一个结果代理类

   

实践:

  导入aop相关的包

org.springframework
spring-aop
4.2.5.RELEASE
aopalliance
aopalliance
1.0
org.aspectj
aspectjweaver
1.8.0
org.springframework
spring-aspects
4.2.5.RELEASE

Spring核心配置文件导入AOP约束

通过execution函数,可以定义切点的方法切入

语法:
  execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
例如:
1、匹配所有类public方法 execution(public * * (..))
2、匹配指定包下所有类中的方法 execution(* cn.com.yang.dao.*(..)) 不包含子包
execution(* cn.com.yang.dao..*(..)) 包含本包,子孙包下的所有类
3、匹配指定类所有方法 execution(* cn.com.yang.dao.UserDao*(..) )
4、匹配实现特定接口所有类中的方法 execution(* cn.com.yang.dao.GenericDao+.*(..))
5、匹配所有save开头的方法 execution(* save*(..))
6、所有方法 execution(* * . *(..))

 

基于XML的AOP:

aop增强类:

/** * aop增强类 */public class MyUserAop {    //前置通知    public void before1() {        System.out.println("前置增强......");    }    //后置通知    public void after1() {        System.out.println("后置增强......");    }    //环绕通知    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {        //方法之前        System.out.println("方法之前.....");        //执行被增强的方法        proceedingJoinPoint.proceed();        //方法之后        System.out.println("方法之后.....");    }}

切入点,即实际要增强的方法:

// User实体的add方法public void add(){    System.out.println("user的add方法");}

Spring配置文件中的配置

输出结果:

  前置增强......
  user的add方法
  后置增强......

 

基于AspectJ注解的AOP:

1、在Spring核心配置文件中配置,开启AOP扫描

2、在增强类上面使用注解@Aspect
3、在增强类的方法上使用注解配置切入点表达式
配置之后的增强类:
/** * aop增强类 */@Aspectpublic class MyUserAop {    //前置通知    @Before(value = "execution(* cn.com.yang.modules.base.model.User.*(..))")    public void before1() {        System.out.println("前置增强......");    }    //后置通知    @After(value = "execution(* cn.com.yang.modules.base.model.User.*(..))")    public void after1() {        System.out.println("后置增强......");    }    //环绕通知    @Around(value = "execution(* cn.com.yang.modules.base.model.User.*(..))")    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {        //方法之前        System.out.println("方法之前.....");        //执行被增强的方法        proceedingJoinPoint.proceed();        //方法之后        System.out.println("方法之后.....");    }}
执行结果:
  方法之前.....
  前置增强......
  user的add方法
  方法之后.....
  后置增强......

 

AOP应用:

  在实际的应用中,每个方法内逻辑处理前可能都要打印入参信息,方法执行结束再打印返回结果。那么就可以使用AOP的横向抽取机制,为所有的方法增强前置日志输出和后置日志输出。

下面是一个实际项目中的使用自定义注解和AOP实现的环绕增强打印入参和响应结果日志和在返回结果为sucess的情况下将日志信息入库的例子:

自定义日志注解:

/** * 日志注解,不需要入库的则不加本注解(返回值中responseCode为"0000"才会入库) * value:需要记录到数据库中的操作信息,如:@Log("删除渠道:{channelId}"),其中channelId为请求参数中的值, *      没有占位符则直接记录value值,目前只支持入库传入参数中的值 * ElementType.METHOD 此注解只可应用在方法上,因为Spring只能对方法增强 * RetentionPolicy.RUNTIME 此注解在运行期仍保留,所以可在运行期使用代理获取有此注解的方法 * Documented 此注解包含在JavaDoc中 */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Log {    String value() default "";}

 

日志增强类:

package com.suning.epp.maarms.release.monitor;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import com.alibaba.fastjson.serializer.PropertyFilter;import com.alibaba.fastjson.serializer.SerializerFeature;import com.suning.epp.maarms.common.constants.Constants;import com.suning.epp.maarms.common.utils.StringUtil;import com.suning.epp.maarms.release.service.intf.OperateLogService;import com.suning.epp.pu.common.aop.lang.CommonResult;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.slf4j.MDC;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import java.lang.reflect.Method;import java.util.Arrays;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;import java.util.regex.Matcher;import java.util.regex.Pattern;/** * 日志切面,切controller方法,记录方法调用、退出与调用时间,如果有responseCode也会记录调用结果日志,
* 如果加了@Log注解,会将操作记录加到数据库 * * @author 17111829 */@Aspect@Componentpublic class LogAdvice { private static final Logger LOGGER = LoggerFactory.getLogger(LogAdvice.class); /** * 日志文件中配置的线程号占位符 */ private static final String STR_INVOKE_NO = "invokeNo"; private static final Pattern PATTERN = Pattern.compile("\\{[^{]*}"); private static final String LEFT_BRACE = "{"; /** * @Resource 默认按照bean id名称进行注入依赖 * 日志入库服务 */ @Resource private OperateLogService operateLogService; /** * 抽取工作的切入点表达式,切点,所有含有@RequestMapping的方法 */ @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") private void webPointcut() { // Do nothing } /** * 在方法执行前后增强 * * @param joinPoint joinPoint * @return 方法返回值 */ @Around("webPointcut()") public Object around(ProceedingJoinPoint joinPoint) { MDC.put(STR_INVOKE_NO, StringUtil.getUuid()); // 记录整个方法的执行用时 long start = System.currentTimeMillis(); // 获取当前方法执行的上下文的request HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); String requestURI = request.getRequestURI(); // 获取请求发起操作人 String userId = request.getRemoteUser(); // 获取请求参数 Map
paramMap = getRequestParams(request); // 不输出map中字段值为null的字段 String requestParams = JSONObject.toJSONString(paramMap, SerializerFeature.WriteMapNullValue); // 打印入参日志 LOGGER.info("URI:{},用户:{}, 入参:{}", requestURI, userId, requestParams); // 获取被增强的方法的相关信息 MethodSignature ms = (MethodSignature) joinPoint.getSignature(); // 获取被增强的方法 Method pointcutMethod = ms.getMethod(); // @Log注解的value属性值 String logContent = null; // 判断被增强的方法上是否有@Log注解 if (pointcutMethod.isAnnotationPresent(Log.class)) { Log logAnon = pointcutMethod.getAnnotation(Log.class); logContent = logAnon.value(); } Object result = null; try { // 执行被增强的方法并获取方法的返回值 result = joinPoint.proceed(); String responseCode = null; String resultStr; // 如果返回的结果是个字符串 if (result instanceof String) { resultStr = (String) result; // 如果有@Log注解并且@Log注解的value不为空 if (StringUtils.isNotBlank(logContent)) { JSONObject jso = JSON.parseObject(resultStr); responseCode = jso.getString(Constants.RESPONSE_CODE); } // 方法执行返回的结果不是字符串 } else { // 如果方法的返回值一个分页查询的结果 if (result instanceof PageResult) { // 分页数据过滤掉 data,不打印日志,SerializerFeature.WriteMapNullValue不输出Map中为null的字段 resultStr = JSON.toJSONString(result, dataFilter, SerializerFeature.WriteMapNullValue); } else { resultStr = JSON.toJSONString(result, SerializerFeature.WriteMapNullValue); } // 有@Log注解时,如果返回0000则记录操作日志 if (StringUtils.isNotBlank(logContent)) { if (result instanceof Map) { responseCode = (String) ((Map) result).get(Constants.RESPONSE_CODE); } else if (result instanceof CommonResult) { responseCode = ((CommonResult) result).getResponseCode(); } } } // 返回0000则记录操作日志 if ("0000".equals(responseCode)) { insertOperateLog(logContent, userId, paramMap); } long end = System.currentTimeMillis(); long useTime = end - start; LOGGER.info("用时:{}ms,返回结果:{}", useTime, resultStr);       // 被增强的方法的异常也再此捕获,因此在代码中将不需要捕获异常信息 } catch (Throwable throwable) { LOGGER.error("发生异常, 异常信息:{}", ExceptionUtil.getAllStackTrace(throwable)); } // 方法执行结束,移除上下文中的线程号 MDC.remove(STR_INVOKE_NO); return result; } /** * 获取请求参数 * * @param request request * @return json格式参数 */ private Map
getRequestParams(HttpServletRequest request) { Enumeration pNames = request.getParameterNames(); Map
paramMap = new HashMap<>(6); String paramName; String[] paramValues; while (pNames.hasMoreElements()) { paramName = String.valueOf(pNames.nextElement()); paramValues = request.getParameterValues(paramName); if (paramValues != null && paramValues.length == 1) { paramMap.put(paramName, paramValues[0]); } else { paramMap.put(paramName, paramValues); } } return paramMap; } /** * 日志入库 * * @param logContent 操作事件 */ private void insertOperateLog(String logContent, String userId, Map
paramMap) { if (StringUtils.isNotBlank(logContent)) { String newLogContent = logContent; // 如果有占位符则替换 if (logContent.contains(LEFT_BRACE) && paramMap != null) { newLogContent = replaceParam(logContent, paramMap); } // 入库,谁干了什么 operateLogService.insertOperateLog(userId, newLogContent); } } /** * 用入参中的值 替换@Log注解中的占位符 * * @param logContent @Log注解中的value值 * @return 替换后的操作内容 */ private String replaceParam(String logContent, Map
paramMap) { Matcher matcher = PATTERN.matcher(logContent); String matchedStr; String oldContent; Object newContent; String newLogContent = logContent; while (matcher.find()) { matchedStr = matcher.group(); oldContent = matchedStr.substring(1, matchedStr.length() - 1).trim(); newContent = paramMap.get(oldContent); if (newContent != null) { if (newContent instanceof String[]) { newLogContent = newLogContent.replace(matchedStr, Arrays.toString((String[]) newContent)); } else { newLogContent = newLogContent.replace(matchedStr, newContent.toString()); } } } return newLogContent; } /** * 字段过滤器,过滤掉data字段不打印 */ private PropertyFilter dataFilter = new PropertyFilter() { @Override public boolean apply(Object object, String name, Object value) { // false表示 data字段将被排除在外 return !"data".equals(name); } };}

 

转载于:https://www.cnblogs.com/yangyongjie/p/10940843.html

你可能感兴趣的文章
PXE实现Linux的自动安装
查看>>
exchange系列(六)实现exchange邮件服务器的高可用性
查看>>
Ubuntu配置静态IP
查看>>
2、DNS之源码安装
查看>>
ubuntu 中DNAT SNAT配置实验.
查看>>
WiFi联盟推出实施级会员 惠及智能家居和物联网企业
查看>>
springmvc中进入web-inf目录下后重定向到webroot下
查看>>
/var/spool/clientmqueue
查看>>
Mybatis结果集自动映射
查看>>
网络基础CCNP篇|双点双向重分布
查看>>
jquery 多种遍历数组方法
查看>>
Windows Animated Cursor Handling漏洞临时解决方法
查看>>
如何更新 OpenStack 组件?- 每天5分钟玩转 OpenStack(161)
查看>>
[Unity3d]第一人称和第三人称视角完美切换
查看>>
Apache Options指令详解
查看>>
ImageMagick、imagick和ghostscript三者的关联?
查看>>
域控NTP同步外部源NTP并自动同步客户端时间
查看>>
Microsoft TV/Video Connection
查看>>
Step By Step实现轻量接触部署Windows XP Sp3
查看>>
手动添加apache的mod_rewrite模块
查看>>