文档章节

Spring5.0源码深度解析之SpringBean的Aop通知调用链源码分析

须臾之余
 须臾之余
发布于 07/30 15:06
字数 2899
阅读 64
收藏 0

SpringAOP原理探究

思考:springAOP底层运用了什么设计模式?

生成代理类:代理设计模式、底层五个通知形成调用链采用:责任链设计模式

下面我们回顾下SpringAop实现流程:

1、配置@EnableAspectJAutoProxy:开启AOP权限

2、@Import(AspectJAutoProxyRegistrar.class):往IOC容器中注入SpringAOP切面类

3、registerAspectJAnnotationAutoProxyCreatorIfNecessary():注册切面类

4、AnnotationAwareAspectJAutoProxyCreator.class:注册到IOC容器中,【AOP的入口】

5、AnnotationAwareAspectJAutoProxyCreator:祖宗是BeanPostProcessor接口,而实现BeanPostProcessor接口后,当Spring加载这个Bean会在实例化前调用其后置处理器实现增强

6、postProcessAfterInitialization:后置处理器【AOP实现核心逻辑】

####6.1、wrapIfNecessary()判断该对象是否在AOP的扫包范围内,真正创建代理类的地方

#########6.1.1、getAdvicesAndAdvisorsForBean创建代理对象包括获取增强方法和根据获取的增强进行代理

#########6.1.2、createAopProxy()判断被代理类是否实现了接口,如果有实现了接口的化,是采用JDK动态代理,否则情况下就使用CGLIB代理

####6.2、根据条件判断使用JdkDynamicAopProxy或者JdkDynamicAopProxy方法实现代理

####6.3、最终执行目标方法的时候,就会进入到JdkDynamicAopProxy 的invoke方法或者JdkDynamicAopProxy的intercept方法

####6.5、底层使用集合存放使用通知,然后再使用责任链设计模式循环的调用

创建代理

我们先来总结下JDK和CGLIB方式

  • 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
  • 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
  • 如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换

JDK动态代理和CGLIB字节码生成的区别?

JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或者方法最好不要声明为final

获取代理

1.JDK代理使用示例

public interface UserService {
    //目标方法
    public abstract void add();
}
public class UserServiceImpl implements UserService {
    /**
     * dynamic.proxy.UserService#add()
     */
    public void add() {
        System.out.println("add.......");
    }
}
public class MyInvocationHandler implements InvocationHandler {

    //目标对象
    private Object target;

    /**
     * 构造函数
     * @param target:目标对象
     */
    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    /**
     * 执行目标对象的方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("-------------before-----------");
        Object result = method.invoke(target, args);
        System.out.println("-------------after------------");
        return result;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(),this);
    }
}
public class ProxyTest {
    public static void main(String[] args) {
        //实例化目标对象
        UserService userService = new UserServiceImpl();
        //实例化InvocationHandler
        MyInvocationHandler invocationHandler = new MyInvocationHandler(userService);
        //根据目标对象生成代理对象
        UserService proxy = (UserService) invocationHandler.getProxy();
        //调用代理对象的方法
        proxy.add();
    }
}

输出结果:

JdkProxy使用示例源码分析

我们再次回顾下JDK代理方式,在整个创建过程中,对于InvocationHandler的创建是最为核心的,在自定义的InvocationHandler中需要重写三个函数

构造函数,将代理的对象传入

invoke方法,此方法中实现了AOP增强的所有逻辑

getProxy方法,此方法千篇一律,但是必不可少。

那么我们看看Spring中的JDK代理实现是不是也是这么做的呢?继续之前的跟踪,到达JdkDynamicAopProxy的getProxy方法。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));//如果循环遍历找到需要代理的对象,则会进入到这里创建代理
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    } else {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) {
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory)this.beanFactory, beanName, beanClass);
    }

    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    if (!proxyFactory.isProxyTargetClass()) {
        if (this.shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        } else {
            this.evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }

    Advisor[] advisors = this.buildAdvisors(beanName, specificInterceptors);
    proxyFactory.addAdvisors(advisors);
    proxyFactory.setTargetSource(targetSource);
    this.customizeProxyFactory(proxyFactory);
    proxyFactory.setFrozen(this.freezeProxy);
    if (this.advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }

    return proxyFactory.getProxy(this.getProxyClassLoader());
}

获取了五个通知:

public Object getProxy(@Nullable ClassLoader classLoader) {
    return this.createAopProxy().getProxy(classLoader);
}

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
    }

    Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
    this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
    return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

我们可以推断出,在JdkDynamicAopProxy中一定会有个invoke函数,并且JdkDynamicAopProxy会把AOP的核心逻辑写在里面。我们查看代码发现确实是有invoke方法

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    TargetSource targetSource = this.advised.targetSource;
    Object target = null;

    Class var9;
    try {
        if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
            Boolean var19 = this.equals(args[0]);
            return var19;
        }

        if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
            Integer var18 = this.hashCode();
            return var18;
        }

        if (method.getDeclaringClass() != DecoratingProxy.class) {
            Object retVal;
            if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
                return retVal;
            }
            //有时候目标对象内部的自我调用将无法实施切面中的增强则需要通过此属性暴露代理
            if (this.advised.exposeProxy) {
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
            }

            target = targetSource.getTarget();
            Class<?> targetClass = target != null ? target.getClass() : null;
            //获取当前方法的拦截器链
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
            if (chain.isEmpty()) {
                Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
                //如果没有发现任何拦截器那么直接调用切点方法
                retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
            } else {
                //将拦截器封装在ReflectiveMethodInvocation,以便于使用其proceed进行链接
                MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                //执行拦截器链
                retVal = invocation.proceed();
            }

            Class<?> returnType = method.getReturnType();
            //返回结果
            if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                retVal = proxy;
            } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
                throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
            }

            Object var13 = retVal;
            return var13;
....
}

上面函数中主要是 创建了一个拦截器链,并使用ReflectiveMethodInvocation类进行了链的封装,而在ReflectiveMethodInvocation类的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;
            //匹配则执行拦截器,不匹配不执行拦截器
            return dm.methodMatcher.matches(this.method, this.targetClass, this.arguments) ? dm.interceptor.invoke(this) : this.proceed();
        } else {
            //普通拦截,直接调用拦截器,将this作为参数传递以保证当前实例中调用链的执行
            return ((MethodInterceptor)interceptorOrInterceptionAdvice).invoke(this);
        }
    }
}

proceed方法中,ReflectiveMethodInvocation中的主要职责是维护了链接调用的计数器,记录当前调用链接的位置,以便链可以有序的进行下去。

CGLIB使用示例源码分析

public class EnhancerDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(EnhancerDemo.class);
        enhancer.setCallback(new MethodInterceptorImpl());

        EnhancerDemo demo = (EnhancerDemo) enhancer.create();
        demo.test();
        System.out.println(demo);

    }

    private void test() {
        System.out.println("EnhancerDemo #test()....");
    }

    private static class MethodInterceptorImpl implements MethodInterceptor{

        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("------before invoke----"+method);
            Object result = methodProxy.invokeSuper(o, objects);
            System.out.println("-----after invoke------"+method);
            return result;
        }
    }
}

运行结果

生成的对象为:com.xiaoxu.cglib.EnhancerDemo$$EnhancerByCGLIB$$c9092bbe@78e03bb5的实例,这个类是运行时由CGLIB产生的。

我们看下Spring源码对CGLIB的实现:

public Object getProxy(@Nullable ClassLoader classLoader) {
    if (logger.isDebugEnabled()) {
        logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource());
    }

    try {
        Class<?> rootClass = this.advised.getTargetClass();
        Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
        Class<?> proxySuperClass = rootClass;
        int x;
        if (ClassUtils.isCglibProxyClass(rootClass)) {
            proxySuperClass = rootClass.getSuperclass();
            Class<?>[] additionalInterfaces = rootClass.getInterfaces();
            Class[] var5 = additionalInterfaces;
            int var6 = additionalInterfaces.length;

            for(x = 0; x < var6; ++x) {
                Class<?> additionalInterface = var5[x];
                this.advised.addInterface(additionalInterface);
            }
        }
        //验证class
        this.validateClassIfNecessary(proxySuperClass, classLoader);
        //创建及配置Enhancer 
        Enhancer enhancer = this.createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader && ((SmartClassLoader)classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }

        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new CglibAopProxy.ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
        //设置拦截器
        Callback[] callbacks = this.getCallbacks(rootClass);
        Class<?>[] types = new Class[callbacks.length];

        for(x = 0; x < types.length; ++x) {
            types[x] = callbacks[x].getClass();
        }

        enhancer.setCallbackFilter(new CglibAopProxy.ProxyCallbackFilter(this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);
        return this.createProxyClassAndInstance(enhancer, callbacks);
    } catch (IllegalArgumentException | CodeGenerationException var9) {
        throw new AopConfigException("Could not generate CGLIB subclass of class [" + this.advised.getTargetClass() + "]: Common causes of this problem include using a final class or a non-visible class", var9);
    } catch (Throwable var10) {
        throw new AopConfigException("Unexpected AOP exception", var10);
    }
}

这里最重要的是通过getCallbacks方法设置拦截器链

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
    //对属性expose-proxy处理
    boolean exposeProxy = this.advised.isExposeProxy();
    boolean isFrozen = this.advised.isFrozen();
    boolean isStatic = this.advised.getTargetSource().isStatic();
    //将拦截器封装在DynamicAdvisedInterceptor中
    Callback aopInterceptor = new CglibAopProxy.DynamicAdvisedInterceptor(this.advised);
    Object targetInterceptor;
    if (exposeProxy) {
        targetInterceptor = isStatic ? new CglibAopProxy.StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource());
    } else {
        targetInterceptor = isStatic ? new CglibAopProxy.StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.DynamicUnadvisedInterceptor(this.advised.getTargetSource());
    }

    Callback targetDispatcher = isStatic ? new CglibAopProxy.StaticDispatcher(this.advised.getTargetSource().getTarget()) : new CglibAopProxy.SerializableNoOp();
    Callback[] mainCallbacks = new Callback[]{aopInterceptor, (Callback)targetInterceptor, new CglibAopProxy.SerializableNoOp(), (Callback)targetDispatcher, this.advisedDispatcher, new CglibAopProxy.EqualsInterceptor(this.advised), new CglibAopProxy.HashCodeInterceptor(this.advised)};
    Callback[] callbacks;
    if (isStatic && isFrozen) {
        Method[] methods = rootClass.getMethods();
        Callback[] fixedCallbacks = new Callback[methods.length];
        this.fixedInterceptorMap = new HashMap(methods.length);

        for(int x = 0; x < methods.length; ++x) {
            List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
            fixedCallbacks[x] = new CglibAopProxy.FixedChainStaticTargetInterceptor(chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
            this.fixedInterceptorMap.put(methods[x].toString(), x);
        }
        //将拦截器加入到Callback中
        callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
        System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
        System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
        this.fixedInterceptorOffset = mainCallbacks.length;
    } else {
        callbacks = mainCallbacks;
    }
    return callbacks;
}

getCallbacks中spring考虑了很多情况,但是对于我们来说,只需要理解最常用的就可以了,CGLIB中对于方法的拦截是通过将自定义的拦截器(实现MethodInterceptor接口)加入到Callback中

并在调用代理时直接激活拦截器中的intercept方法来实现。由此可推断,对于CGLIB方式实现的代理,其核心逻辑必然在DynamicAdvisedInterceptor中的intercept中。

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    Object oldProxy = null;
    boolean setProxyContext = false;
    Object target = null;
    TargetSource targetSource = this.advised.getTargetSource();

    Object var16;
    try {
        if (this.advised.exposeProxy) {
            oldProxy = AopContext.setCurrentProxy(proxy);
            setProxyContext = true;
        }
        target = targetSource.getTarget();
        Class<?> targetClass = target != null ? target.getClass() : null;
        //获取拦截器链
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
             //如果拦截器链为空则直接激活源原方法
            retVal = methodProxy.invoke(target, argsToUse);
        } else {
            //进入链
            retVal = (new CglibAopProxy.CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy)).proceed();
        }
        retVal = CglibAopProxy.processReturnType(proxy, target, method, retVal);
        var16 = retVal;
    } finally {
        if (target != null && !targetSource.isStatic()) {
            targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
            AopContext.setCurrentProxy(oldProxy);
        }
    }
    return var16;
}

上述的实现与JDK方式实现代理中的invoke方法大同小异,首先是构造链,然后封装此链进行串联调用,区别在于JDK中直接构造ReflectiveMethodInvocation,而在CGLIB中使用CglibMethodInvocation.

CglibMethodInvocation继承自ReflectiveMethodInvocation,但是process方法并没有重写。

SpringBean的AOP

主要靠的是后置处理器BeanPostProcessor:在Bean对象初始化前后做一些增强

AnnotationAwareAspectJAutoProxyCreator的祖宗是BeanPostProcessor

纯手写SpringAop调用链思路

【0】环绕通知之前执行→【1】前置通知→目标方法→【2】后置通知→【3】环绕通知之后执行

责任链设计模式,底层通过递归算法+责任链

如何存放起来:使用集合存放这些通知,集合当中不存放我们的方法,只存放链,那么如何插入我们的目标方法?

纯手写SpringAop调用链

public interface MethodInterceptor {
    /**
     * 定义共同通知骨架
     */
    public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException;
}
public class BeforMethodInterceptor implements MethodInterceptor {
    public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println(">>>>前置通知<<<<");
        // 执行我们的目标方法
        methodInvocation.process();// 递归调用
    }
}
public class AfterMethodInterceptor implements MethodInterceptor {
    public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException {
        // 执行我们的前置通知
        methodInvocation.process();
        System.out.println(">>>后置通知<<<");
    }
}
public class AroundMethodInterceptor implements MethodInterceptor {
    public void invoke(MethodInvocation methodInvocation) throws InvocationTargetException, IllegalAccessException {
        System.out.println("环绕通知在目标方法之前执行..");
        methodInvocation.process();
        System.out.println("环绕通知在目标方法之后执行..");
    }
}
public class UserService {
    public void login(String userName, Integer age) {
        System.out.println("userName:" + userName + ",age:" + age);
    }
}

MethodInvocation 能够把链串起来

public interface MethodInvocation {
    //调用链形成
    public void process() throws InvocationTargetException, IllegalAccessException;

}
public class DefaultMethodInvacation implements MethodInvocation {
    /**
     * 存放所有的通知
     */
    private List<MethodInterceptor> listMethodInterceptor;
    private Object target;// 目标对象
    private Method method;// 目标方法
    private Object args[];// 目标参数
    // 最终使用反射机制执行目标方法
    private int index;// 记录当前链调用的位置
    public DefaultMethodInvacation(List<MethodInterceptor> listMethodInterceptor, Object target, Method method, Object[] args) {
        this.listMethodInterceptor = listMethodInterceptor;
        this.target = target;
        this.method = method;
        this.args = args;
    }
    /**
     * 调用链形成
     */
    @Override
    public void process() throws InvocationTargetException, IllegalAccessException {
        if (index == listMethodInterceptor.size()) {
            method.invoke(target, args); //  执行目标
            return;
        }
        MethodInterceptor methodInterceptor = listMethodInterceptor.get(index++);
        methodInterceptor.invoke(this);
    }
}

执行结果:

>>>>前置通知<<<<
环绕通知在目标方法之前执行..
userName:mayikt,age:12
环绕通知在目标方法之后执行..
>>>后置通知<<<

本文参考

参考书籍:Spring源码深度解析

蚂蚁课堂:http://www.mayikt.com/

 

© 著作权归作者所有

须臾之余
粉丝 125
博文 67
码字总数 178724
作品 0
吉安
程序员
私信 提问
Sprig AOP的实现及源码解析

在介绍AOP之前,想必很多人都听说AOP是基于动态代理和反射来实现的,那么在看AOP之前,你需要弄懂什么是动态代理和反射及它们又是如何实现的。 想了解JDK的动态代理及反射的实现和源码分析,...

zsq_fengchen
2018/11/27
0
0
Spring AOP 源码分析 - 筛选合适的通知器

1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析。本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出合适的通知器(Advisor...

java高级架构牛人
2018/06/21
25
0
Spring AOP 源码分析系列文章导读

简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解。在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅读了 AOP ...

java高级架构牛人
2018/06/21
32
1
Spring源码分析之AOP解析

作者: 一字马胡 转载标志 【2018-02-02】 更新日志 日期 更新内容 备注 2018-02-02 创建分析文档 Spring源码分析系列文章(三) 前言 本文是Spring源码分析系列的第三篇文章,前两篇文章分别...

一字马胡
2018/01/02
0
0
Spring IOC 容器源码分析 - 创建原始 bean 对象

1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续。在上一篇文章中,我们从战略层面上领略了方法的全过程。本篇文章,我们就从战术的层面上,详细分析方法中的一个重要的调用,即...

coolblog.xyz
2018/06/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

可能是国内第一篇全面解读 Java 现状及趋势的文章

作者 | 张晓楠 Dragonwell JDK 最新版本 8.1.1-GA 发布,包括全新特性和更新! 导读:InfoQ 发布《2019 中国 Java 发展趋势报告》,反映 Java 在中国发展的独特性,同时也希望大家对 Java 有...

阿里云官方博客
28分钟前
6
0
Spring Boot 2.x基础教程:Swagger静态文档的生成

前言 通过之前的两篇关于Swagger入门以及具体使用细节的介绍之后,我们已经能够轻松地为Spring MVC的Web项目自动构建出API文档了。如果您还不熟悉这块,可以先阅读: Spring Boot 2.x基础教程...

程序猿DD
31分钟前
4
0
《毅力》读书笔记

1.确信你全身心地投入 2.准备好为目标进行艰难的跋涉 3.通过减少需要使用毅力的情形,为将来的挑战做好准备 4.尽可能具体细致地确定你的目标和实现目标的过程 5.把挑战分解为小而易于管理的小...

lingch
32分钟前
3
0
zk中快速选举FastLeaderElection实现

选举涉及概念 服务器状态 投票 如何选择投票? 协议 选举 如何进行选举? epoch 发送者 接收者 发送队列 接收队列 服务器状态 public enum ServerState { LOOKING,寻找Leader状态,当服务处于...

writeademo
35分钟前
4
0
教你玩转Linux—磁盘管理

Linux磁盘管理好坏直接关系到整个系统的性能问题,Linux磁盘管理常用三个命令为df、du和fdisk。 df df命令参数功能:检查文件系统的磁盘空间占用情况。可以利用该命令来获取硬盘被占用了多少...

Linux就该这么学
38分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部