springboot aop示例

25 阅读3分钟

maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.6</version>
</dependency>

为了方便以后对调用耗时进行分析,所以打算用aop写个简易的代理,代码全网都有,简单介绍一下:

@Aspect
/*
  @AspectJ refers to a style of declaring aspects as regular Java classes annotated with annotations.
 * To use @AspectJ aspects in a Spring configuration,
 * you need to enable Spring support for configuring Spring AOP based on @AspectJ aspects
 * and auto-proxying beans based on whether or not they are advised by those aspects.
 * To enable @AspectJ support with Java @Configuration,
 * add the @EnableAspectJAutoProxy annotation, as the following example shows:
 * <code>
 * @Configuration
 * @EnableAspectJAutoProxy
 * public class AppConfig {
 *
 * }
 * </code>
 */
@Component
/*
 * Spring Component annotation is used to denote a class as Component.
 * It means that Spring framework will autodetect these classes for dependency injection
 * when annotation-based configuration and classpath scanning is used.
 */
public class TimerAop {
    private final static Logger logger = LoggerFactory.getLogger(TimerAop.class);
​
    @Pointcut("execution(public * com.kinya.neko.controller.*.*(..))")
    public void recordLog(){}
​
    @Around("recordLog()")
    public Object execAround(ProceedingJoinPoint pj) throws Throwable{
        System.out.println("method name is:"+pj.getSignature());
        System.out.println("aop:around BEGIN:--------");
        Object retVal;
        try {
            retVal = pj.proceed();
        } catch (Throwable throwable) {
            System.out.println("-----------end with exception");
            throw throwable;
        }
​
        System.out.println("-----------aop:around FINISH");
        return retVal;
    }
}

@Aspect 注解:

It is a class that contains advices, joinpoints etc.

该注解正如上面的英文所述,其标识了一个含有aop切点以及对应切点动作的类,本文中暂且译为切面。配合@EnableAspectJAutoProxy 注解在配置类@Configuration ,实现切面的自动代理,详见代码注释。仅单独使用@Aspect时无法在运行时生成代理的哦~

@Pointcut 注解:

It is an expression language of AOP that matches join points. Join point is any point in your program such as method execution, exception handling, field access etc. Spring supports only method execution join point.

简而言之,@Pointcut指出了切面的切入点,在什么条件下(特定函数调用,异常抛出等)会触发切面动作

@Around 注解:

表示切面动作(execAround())发生的具体时间。除around外,aop中还有其他触发时间点,这里不一一赘述主要我觉得看的人应该都会

@Component 注解:

spring会自动检测被@Component标注的类,这些类在运行时生命周期将交由spring容器进行管理


通常对于spring aop的介绍到这里就结束了,但是这里还有一点小问题:spring是如何使用单例aop bean完成对不同业务 bean的代理呢?

我们知道spring aop基于cglib动态生成代理类字节码加载到jvm中。由于cglib采用继承被代理类实现代理,所以对于不同被代理类来说,即使切面动作是一样的,代理类也是完全不一样的(因为基类就不相同)。这样看来,似乎用单例aop bean不能满足对多业务bean的代理。

首先在启动前设置系统参数,使得生成的代理类可以存储到本地

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir"));

被代理类如下

@Controller
public class LoginController extends NekoController {
    @Autowired
    LoginService loginService;
​
    @PostMapping("/login")
    @ResponseBody
    public ResultWrapper login(@RequestBody UserBean requestUser){
        String pass = StringUtils.trimToEmpty(requestUser.getPassword());
        String name = StringUtils.trimToEmpty(requestUser.getName());
        if(StringUtils.isEmpty(pass) || StringUtils.isEmpty(name)){
            throw new NekoException(ErrorDesc.PARAM_EMPTY);
        }
        requestUser.setName(name);
        requestUser.setPassword(pass);
​
        if(loginService.login(requestUser)) {
            return new ResultWrapper(new Object());
        }else{
            throw new NekoException(ErrorDesc.NOT_IMPL);
        }
    }

不过在运行中并不是直接调用,其代理类如下

public class LoginController$$EnhancerBySpringCGLIB$$bea38d3b extends LoginController implements SpringProxy, Advised, Factory {
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private boolean CGLIB$BOUND;
    private MethodInterceptor CGLIB$CALLBACK_0;
    ...
    public final ResultWrapper login(UserBean var1) {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
​
        return var10000 != null ? (ResultWrapper)var10000.intercept(this, CGLIB$login$0$Method, new Object[]{var1}, CGLIB$login$0$Proxy) : super.login(var1);
    }
    
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        LoginController$$EnhancerBySpringCGLIB$$bea38d3b var1 = (LoginController$$EnhancerBySpringCGLIB$$bea38d3b)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }
​
            Callback[] var10001 = (Callback[])var10000;
            ...
            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)var10001[0];
        }
​
    }

在这里会进入到MethodInterceptor.intercept,其实现类为DynamicAdvisedInterceptor.intercept

@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    ...
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();
​
    Object var19;
    try {
        ...
        target = targetSource.getTarget();
        Class<?> targetClass = target != null ? target.getClass() : null;
        // 获取调用函数(CGLIB$login$0$Method)所有切面
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && CglibAopProxy.CglibMethodInvocation.isMethodProxyCompatible(method)) {
        // 无切面时调用
        ...
            try {
                retVal = methodProxy.invoke(target, argsToUse);
            } catch (CodeGenerationException var17) {
            ...
        } else {
            // 有切面时调用
            retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
        }
​
        retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
        var19 = retVal;
    } finally {
        ...
​
    }
​
    return var19;
}

之后开始调用方法,内部类CglibMethodInvocation声明如下:class CglibMethodInvocation extends ReflectiveMethodInvocation,不难看出方法调用仍然选择通过反射调用。CglibMethodInvocation.proceed调用super.proceed.

public Object proceed() throws Throwable {
    if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
        return this.invokeJoinpoint();
    } else {
        Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
            Class<?> targetClass = this.targetClass != null ? this.targetClass : this.method.getDeclaringClass();
            return dm.methodMatcher.matches(this.method, targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
        } else {
            return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
        }
    }
}

这里采用递归的方式实现对this.interceptorsAndDynamicMethodMatchers的遍历执行 AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor开始aop

最终调用由@Around注解标记的切面动作,在pj.proceed()方法中对被代理类二次代理。org.springframework.cglib.proxy.MethodProxy#invoke首先进入到初始化方法中,在这里生成被代理类的fastclass.

what is fastclass FastClass不使用反射类(Constructor或Method)来调用委托类方法,而是动态生成一个新的类(继承FastClass),向类中写入委托类实例直接调用方法的语句,用模板方式解决Java语法不支持问题,同时改善Java反射性能。 动态类为委托类方法调用语句建立索引,使用者根据方法签名(方法名+参数类型)得到索引值,再通过索引值进入相应的方法调用语句,得到调用结果。 引用自参考4

初始化方法中,使用了volatile变量的双重检验,实现安全的多线程单例。

private void init() {
    if (this.fastClassInfo == null) {
        synchronized(this.initLock) {
            if (this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
​
}

动态生成和加载代理类字节码文件 org.springframework.cglib.core.AbstractClassGenerator#create

protected Object create(Object key) {
    try {
        ClassLoader loader = this.getClassLoader();
        Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> cache = CACHE;
        AbstractClassGenerator.ClassLoaderData data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
        if (data == null) {
            Class var5 = AbstractClassGenerator.class;
            synchronized(AbstractClassGenerator.class) {
                cache = CACHE;
                data = (AbstractClassGenerator.ClassLoaderData)cache.get(loader);
                if (data == null) {
                    Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> newCache = new WeakHashMap(cache);
                    data = new AbstractClassGenerator.ClassLoaderData(loader);
                    newCache.put(loader, data);
                    CACHE = newCache;
                }
            }
        }
​
        this.key = key;
        Object obj = data.get(this, this.getUseCache());
        return obj instanceof Class ? this.firstInstance((Class)obj) : this.nextInstance(obj);
    } ...
}

其中CACHE使用了WeakHashMap实现,充分利用了弱引用的性质,当所有线程都无法访问到这个弱引用的指向时,就会被gc回收。 此外,由于使用原型模式,各代码生成器实现均依赖该函数,为了防止多线程中不同生成器同时调用create方法对static volatile Map<ClassLoader, AbstractClassGenerator.ClassLoaderData> CACHE改动,这里将AbstractClassGenerator.class作为锁对象。

public V get(K key) {
    KK cacheKey = this.keyMapper.apply(key);
    Object v = this.map.get(cacheKey);
    return v != null && !(v instanceof FutureTask) ? v : this.createEntry(key, cacheKey, v);
}

使用Future进行异步加载代理类字节码。

生成字节码

protected Class generate(AbstractClassGenerator.ClassLoaderData data) {
    Object save = CURRENT.get();
    CURRENT.set(this);
​
    try {
        ClassLoader classLoader = data.getClassLoader();
        if (classLoader != null) {
            String className;
            synchronized(classLoader) {
                className = this.generateClassName(data.getUniqueNamePredicate());
                data.reserveName(className);
                this.setClassName(className);
            }
​
            Class gen;
            if (this.attemptLoad) {
                try {
                    gen = classLoader.loadClass(this.getClassName());
                    Class var23 = gen;
                    return var23;
                } catch (ClassNotFoundException var19) {
                }
            }
            // 字节码文件
            byte[] b = this.strategy.generate(this);
            className = ClassNameReader.getClassName(new ClassReader(b));
            ProtectionDomain protectionDomain = this.getProtectionDomain();
            synchronized(classLoader) {
                // 反射调用构造器创建对象
                gen = ReflectUtils.defineClass(className, b, classLoader, protectionDomain, this.contextClass);
            }
​
            Class var8 = gen;
            return var8;
        }
        ...
    } ...
     finally {
        CURRENT.set(save);
    }
}

aop生成代理类

public class LoginController$$FastClassBySpringCGLIB$$b4174b23 extends FastClass {
    public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        LoginController var10000 = (LoginController)var2;
        int var10001 = var1;
​
        try {
            switch(var10001) {
            case 0:
                return var10000.login((UserBean)var3[0]);
            case 1:
                return new Boolean(var10000.equals(var3[0]));
            case 2:
                return var10000.toString();
            case 3:
                return new Integer(var10000.hashCode());
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
​
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

参考

[1]: Callback种类介绍

[2]: For this article, I will be showing an example that allows any JavaBean class to become "observable" so any listener can register with it to be notified of property changes.

[3]: Detailed explanation of CGLIB

[4]: CGLib FastClass

[5]: Java - The WeakHashMap Class

[6]: Future and FutureTask in java