在通常专业中,大家免不了要打字与印刷非常多log。而多数索要输出的log又是重复的(比方传入参数,重回值)。由此,通过AOP格局来举办日志管理能够减去过多代码量,也愈Gavin雅。

[TOC]

Aspectj切入点语法定义

金沙娱樂城,主要行使本领:Aspect,Javassist(本文目的在于提供实现示例,因此不做过多的法规表明。)

基本概念

面向切面编制程序 —
AOP,能够让咱们把散落在选用随处的效果与利益分离出来,形成可复用的零件

主导的利用饱含:日志、安全、事务管理

 

正文由小编三汪头阵于简书。

Joinpoint-连接点

在系统运营此前,AOP的成效模块都亟需织入到OOP的成效模块中。

故此,要开展这种织入进度,大家要求知道在系统的那贰个施行点上实行织入操作。

那些即就要其上进展织入操作的系统施行点就称为Joinpoint。

(在Spring AOP 中仅支持艺术级其余Joinpoint)

在选拔spring框架配置AOP的时候,不管是经过XML配置文件只怕注明的主意都亟待定义pointcut”切入点”

在Sringboot情状下,将本文提供的代码及注重复制到你的档案的次序中,可径直动用。

Poincut-切点

表示的是Joinpoint的表明情势。将横切逻辑织入当前系统的经过中,

急需参考Pointcut规定的Joinpoint消息,才足以领略应该往系统的如何Joinpoint上织入横切逻辑。

举例定义切入点表达式  execution (* com.sample.service.impl..*.*(..))

Maven依赖(不包括slf4j部分信任)

Aspect-切面

是对系统中的横切关切点逻辑进行模块化封装的AOP概念实体。

Aspect能够满含两个Pointcut以及对应的Advice定义。

execution()是最常用的切点函数,其语法如下所示:

 <!-- aspectj --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> <!-- javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.39</version> </dependency>

Advice-通知

是单一横切关切点逻辑的载体,代表将会织入到Joinpoint处的横切逻辑。

一经将Aspect比作OOP中的Class,那么Advice就也正是Class中的Method。

奉公守法Advice在Joinpoint处实行机缘的距离或然达成作用的两样,

Advice可分成以下具体方式:BeforeAdvice,After Advice(Afterreturning
Advice、

AfterThrowing Advice、AfterFinallyAdvice),Around Advice,Introduction。

 整个表明式能够分为五个部分:

代码部分

Introduction-引入

向现成类增添新的秘籍或质量

 1、execution(): 表明式主体。

import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.AfterReturning;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import com.alibaba.fastjson.JSON;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import javassist.CtMethod;import javassist.Modifier;import javassist.NotFoundException;import javassist.bytecode.CodeAttribute;import javassist.bytecode.LocalVariableAttribute;import javassist.bytecode.MethodInfo;@Aspect@Componentpublic class MethodLogAop { private static final Logger LOGGER = LoggerFactory.getLogger(MethodLogAop.class); /** * 切点 * 配置需要添加切面通知的包路径 */ @Pointcut("(execution(* com.wolfgy.demo.service..*.*//||(execution(* com.wolfgy.demo.web..*.* public void webLog(){} /** * 前置通知 * @param joinPoint 切点 * @throws Throwable 异常 */ @Before") public void doBefore(JoinPoint joinPoint) throws Throwable { String classType = joinPoint.getTarget().getClass().getName(); Class<?> clazz = Class.forName(classType); String clazzName = clazz.getName(); LOGGER.info("类名:" + clazzName); String methodName = joinPoint.getSignature().getName(); LOGGER.info("方法名:" + methodName); String[] paramNames = getFieldsName(this.getClass(), clazzName, methodName); Object[] args = joinPoint.getArgs(); for(int k=0; k<args.length; k++){ LOGGER.info("参数名:" + paramNames[k] + ",参数值:" + JSON.toJSONString; } } /** * 得到方法参数的名称 * @param cls 类 * @param clazzName 类名 * @param methodName 方法名 * @return 参数名数组 * @throws NotFoundException 异常 */ private static String[] getFieldsName(Class<?> cls, String clazzName, String methodName) throws NotFoundException { ClassPool pool = ClassPool.getDefault(); ClassClassPath classPath = new ClassClassPath; pool.insertClassPath(classPath); CtClass cc = pool.get(clazzName); CtMethod cm = cc.getDeclaredMethod(methodName); MethodInfo methodInfo = cm.getMethodInfo(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); String[] paramNames = new String[cm.getParameterTypes().length]; int pos = Modifier.isStatic(cm.getModifiers ? 0 : 1; for (int i = 0; i < paramNames.length; i++){ paramNames[i] = attr.variableName; //paramNames即参数名 } return paramNames; } /** * 后置通知 * 打印返回值日志 * @param ret 返回值 * @throws Throwable 异常 */ @AfterReturning(returning = "ret", pointcut = "webLog public void doAfterReturning(JoinPoint joinPoint, Object ret) throws Throwable { String classType = joinPoint.getTarget().getClass().getName(); Class<?> clazz = Class.forName(classType); String clazzName = clazz.getName(); LOGGER.info("类名:" + clazzName); String methodName = joinPoint.getSignature().getName(); LOGGER.info("方法名:" + methodName); LOGGER.info("返回值 : " + JSON.toJSONString; }}

Weaving-织入

把切面应用到对象对象并创立新的代理对象

 2、第一个*号:表示回去类型,*号表示全数的等级次序。

代码效果

切点表达式

Spring的切点表达式是Aspect提醒器的一个子集

表达式 说明
args() 限制连接点匹配参数为指定类型的执行方法
@args() 限制连接点匹配参数由指定注解标注的执行方法
this() 限制连接点匹配AOP代理的bean引用指定类型的类
target 限制连接点目标对象为指定类型的类
@target() 限制连接点匹配特定的执行对象
within() 限制连接点匹配指定的类型
@within() 限制连接点匹配指定注解所标注的类型
@annotation 限制匹配带有指定注解的连接点
execution() 匹配是连接点的执行方法

案例

1)通过艺术具名定义切点
execution(public *
(..))
十一分全部目的类的public方法,但不相配SmartSeller和protected
voidshowGoods()方法。
第一个
意味着回到类型,首个*表示办法名,而..代表私自入参的不二等秘书籍;

execution(*
To(..))l
相配目的类具备以To为后缀的秘技。它相称NaiveWaiter和NaughtyWaiter的greetTo()和
serveTo()方法。第一个
意味着回到类型,而*To代表私行以To为后缀的主意;

2)通过类定义切点
execution(com.baobaotao.Waiter.(..))l
相称Waiter接口的兼具办法,它相配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()
方法。第一个意味着回到任意档案的次序,com.baobaotao.Waiter.表示Waiter接口中的全体办法;

execution(com.baobaotao.Waiter+.(..))l
相称Waiter接口及其全数达成类的秘诀,它不光匹配NaiveWaiter和NaughtyWaiter类的
greetTo()和serveTo()那八个Waiter接口定义的法子,同有毛病间还相称NaiveWaiter#smile()
和NaughtyWaiter#joke()那多个不在Waiter接口中定义的措施。

3)通过类包定义切点
在类超情势串中,“.”表示包下的全体类,而“..”表示包、子孙包下的全部类。
execution(*
com.baobaotao.*(..))l
相配com.baobaotao包下全体类的全数办法;

execution(*
com.baobaotao..(..))l
相配com.baobaotao包、子孙包下全部类的持有办法,
如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的全体类的
具有办法都协作。“..”出现在类名中时,后边总得跟“
”,表示包、子孙包下的全体类;

execution(*
com...Dao.find*(..))l
相称包名前缀为com的别的包下类名后缀为Dao的法子,方法名必得以find为前缀。
如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的
艺术都卓殊切点。

4)通过艺术入参定义切点
切点表达式中方法入参部分比较复杂,能够接纳“”和“..”通配符,其中“”表示大肆档次的参数,
而“..”表示任性档期的顺序参数且参数个数不限。

execution(*
joke(String,int)))l

配joke(String,int)方法,且joke()方法的第贰个入参是String,第一个入参是int。
它匹配NaughtyWaiter#joke(String,int)方法。假设措施中的入参类型是java.lang包下的类,
能够直接使用类名,不然必需运用全限定类名,如joke(java.util.List,int);

execution(*
joke(String,*)))l
相配目的类中的joke()方法,该办法第一个入参为String,第四个入参能够是轻巧档案的次序,
如joke(Strings1,String s2)和joke(String s1,double d2)都匹配,
但joke(String s1,doubled2,String s3)则不匹配;

execution(*
joke(String,..)))l
相配指标类中的joke()方法,该方法第
多个入参为String,前面能够有私自个入参且入参类型不限,
如joke(Strings1)、joke(String s1,String s2)和joke(String s1,double
d2,Strings3)都匹配。

execution(*
joke(Object+)))

配目的类中的joke()方法,方法具备三个入参,且入参是Object类型或该类的子类。
它匹配joke(Strings1)和joke(Client
c)。假使我们定义的切点是execution(*joke(Object)),
则只相配joke(Object object)而不匹配joke(Stringcc)或joke(Client c)。

切点表明式的连日

多个切点表达式能够一同选择,and、or、not连接,或许使用&&、||、!

bean

bean()提醒器是Spring新引进的提醒器

bean(‘user’),匹配id为user的bean

!bean(‘user’),不匹配id为user的bean

 3、包名:表示须求拦截的包名,后边的多个句点表示近些日子包和当下包的具备子包,com.sample.service.impl包、子孙包下全体类的措施。

2017-11-23 13:17:28.865 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 类名:com.wolfgy.demo.service.DemoService2017-11-23 13:17:28.867 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 方法名:save2017-11-23 13:17:29.025 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 参数名:project,参数值:{"name":"demo"}2017-11-23 13:17:29.273 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 类名:com.wolfgy.demo.service.DemoService2017-11-23 13:17:29.273 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 方法名:save2017-11-23 13:17:29.273 INFO 5056 --- [nio-8081-exec-1] com.wolfgy.demo.aspectj.MethodLogAop : 返回值 : null

案例一

package org.zln.spring.demo02.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
 * 定义切面
 * Created by sherry on 17/3/9.
 */
@Aspect
public class Audience {
    /**
     * 切点
     */
    @Pointcut("execution(* org.zln.spring.demo02.aop.Performance.*(..))")
    public void performance() {
    }
/**
     * 前置通知
     */
    @Before("performance()")
    public void silenceCellPhones() {
        System.out.println("前置通知:表演前手机静音");
    }
@Before("performance()")
    public void takeSeats() {
        System.out.println("前置通知:就坐");
    }
@AfterReturning("performance()")
    public void applause() {
        System.out.println("返回通知:表演结束,鼓掌");
    }
@AfterThrowing("performance()")
    public void demandRefund() {
        System.out.println("异常通知:表演失败");
    }
@Around("performance()")
    public void watch(ProceedingJoinPoint proceedingJoinPoint) {
        System.out.println("环绕通知1");
        try {
            proceedingJoinPoint.proceed();
            System.out.println("环绕通知2");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

package org.zln.spring.demo02.aop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
 * Created by nbcoolkid on 2017-10-02.
 */
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class SpringAopCfg {
@Bean
    public Audience audience(){
        return new Audience();
    }
}

围绕文告的proceedingJoinPoint.proceed();

相比奇妙,若无这句,也就是代码阻塞,借使调用多次,相当于推行数十次对象措施

 4、第二个*号:表示类名,*号表示具有的类。

语法表明式:execution(<修饰符> <返回类型> <类路径> <方法名>(<参数列表>) <异常模式> )当中,修饰符和至极是可选的,假使不加类路线,则私下认可对富有的类生效。常用实例:1.经过艺术签名、再次回到值定义切点:execution(public * *Service:定位于具备类下重返值任性、方法入参类型、数量任性,public类型的方法execution(public String *Service:定位于具有类下再次回到值为String、方法入参类型、数量自便,public类型的措施2.经过类包定义切点:execution(* com.yc.controller.BaseController+.*:相称任性再次来到类型,对应包下BaseController类及其子类等猖獗方法。execution(* com.*.:相配放肆再次回到类型,com包下全数类的装有办法execution(* com..*.:相配大肆再次来到类型,com包、子包下全部类的享有办法(注意:.表示该包下全数类,..则涵括其子包。)3.由此艺术入参定义切点(这里*代表自便档案的次序的贰个参数,..意味着任意档案的次序猖狂数量的参数)execution(* speak(Integer,*)):相称大肆重临类型,全数类中唯有八个入参,第三个入参为Integer,第一个入参自便的艺术execution(* speak(..,Integer,..)):相配大肆再次回到类型,全数类中最少有七个Integer入参,但任务任性的秘诀。

案例二

package com.tdk.train.trainbasic.frame;

import com.tdk.train.trainbasic.utils.JsonUtils;
import lombok.extern.slf4j.Slf4j;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 全局请求响应日志打印
 * Created by nbcoolkid on 2017-11-21.
 */
@Aspect
@Component
@Slf4j
public class ReqResAop {
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void performance() {
    }

    @Around("performance()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String url = request.getRequestURI();

        Object[] objects = proceedingJoinPoint.getArgs();
        Object result = null;
        try {
            String reqStr = JsonUtils.toJson(objects);
            result = proceedingJoinPoint.proceed();// result的值就是被拦截方法的返回值
            String resStr = JsonUtils.toJson(result);
            long end = System.currentTimeMillis();
            log.info("\n===============请求===============\n" +
                            "url:{}\n" +
                            "{}\n" +
                            "===============响应===============\n" +
                            "{}\n" +
                            "=============耗时:{} 毫秒============================"
                    , url, reqStr, resStr,(end-start));
            log.info("===============END===============");
        }catch (Exception e){
            log.error(e.getMessage(),e);
            //这里不抛出异常的话,RestControllerAdvice就拦截不到了,因为此处捕获了
            throw new RuntimeException(e.getMessage());
        }

        return result;

    }
}

 5、*(..):最终那些星号表示方法名,*号表示具备的法门,前面括弧里面表示方法的参数,多少个句点表示别的参数。

上述。希望小编的文章对你能具备助于。作者不可能担保文中全体说法的百分之百没有错,但自个儿能确定保证它们都是自己的理解和醒来以及拒绝直接复制黏贴(确实要求引用的部分小编会附上源地址)。有如何思想、见解或吸引,款待留言钻探。

 

 

 

 

AspectJ的Execution表达式

execution()

execution()是最常用的切点函数,其语法如下所示:

 

execution(<修饰符形式>? <重回类型方式>
<方法有名的模特式>(<参数方式>) <卓殊格局>?)
 除了回到类型格局、方法超级模特式和参数格局外,别的项都是可选的。与其直接教学该办法的运用法规,还比不上通过多个个具体的事例进行精通。上边,咱们付出各个使用execution()函数实例。

 

1)通过措施签字定义切点

 execution(public * *(..))l

相当全体目的类的public方法,但不相配SmartSeller和protected void
showGoods()方法。第多个*表示回到类型,第2个*意味着办法名,而..代表私行入参的章程;

 

 execution(* *To(..))l

优异指标类具备以To为后缀的办法。它相称NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第三个*代表回到类型,而*To代表私下以To为后缀的办法;

 

Author

发表评论

电子邮件地址不会被公开。 必填项已用*标注