Let’s learn SF framework series 7.4-spring-AOP-AOP proxy creation together

After the AOP BeanDefinition is loaded, Spring provides an automatic proxy mechanism, allowing the container to automatically generate an AOP proxy bean based on the target bean. This article describes how to implement this.

basic mechanism

During the startup process of Spring, an external intervention processing mechanism is provided before and after bean instantiation and initialization (see " Let's learn SF Framework Series 5.3-spring-Beans-bean interaction with Spring container " for details). AOP automatically completes this work through the BeanPostProcessor class. The following is the automatic proxy creation class relationship diagram:
Insert image description here
As can be seen from the above figure, the automatically created proxy classes are inherited from AbstractAutoProxyCreator, and AbstractAutoProxyCreator implements the interface SmartInstantiationAwareBeanPostProcessor, which is inherited from BeanPostProcessor.

Start the portal

During the bean creation process, the automatic proxy creation class can have four intervention opportunities:
1. Before instantiation (SmartInstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation)
2. After instantiation (SmartInstantiationAwareBeanPostProcessor.postProcessAfterInstantiation)
3. Before initialization (BeanPostProcessor.postProcessBeforeInitialization)
4. After initialization (BeanPostProcessor.postProcessAfterInitialization)
Actual code tracking, AbstractAutoProxyCreator implements the following two interfaces:
Before instantiation: postProcessBeforeInstantiation
After initialization: postProcessAfterInitialization
tracking startup process, the real entrance is AbstractAutoProxyCreator.postProcessAfterInitialization. This is more realistic: because during application development, the target object bean does not need to know anything about AOP. It can work normally with AOP and can work normally without AOP. Therefore, it is more appropriate for the target object bean to be converted into a proxy object after initialization. .

Source code tracking

AbstractAutoProxyCreator.postProcessAfterInitialization(@Nullable Object bean, String beanName)

Create a bean proxy object.

	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
    
    
		if (bean != null) {
    
    
			// 为给定的bean类和bean名称构建一个缓存键
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
    
    
				// bean没有对应的代理,则创建代理
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

AbstractAutoProxyCreator.wrapIfNecessary(Object bean, String beanName, Object cacheKey)

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    
    
		// 如果目标bean已存在(在bean实例化前创建的),直接返回
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    
    
			return bean;
		}
		// 目标bean不是advisedBean,直接返回
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    
    
			return bean;
		}
		// 如果是基础类(是不能被代理的)或者 应该跳过的bean(“跳过”意思是给定的bean不应被此后处理器进行自动代理),直接返回
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
    
    
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		/* 创建代理类 */
		// 获取所有适用于当前Bean的Advisors 注1
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		/* 存在适用于当前Bean的Advisors */
		if (specificInterceptors != DO_NOT_PROXY) {
    
    
			// 标记该bean是被代理类的bean
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 创建代理bean  注2
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			// 标记bean的代理类型
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		// 标记该bean非被代理类的bean
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

Note 1/ Note 2 The methods and processes are relatively complicated, please see the special chapters below.

Get all Advisors applicable to the current Bean

AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean( Class<?> beanClass, String beanName, @Nullable TargetSource targetSource)

	// 过渡类
	@Override
	@Nullable
	protected Object[] getAdvicesAndAdvisorsForBean(
			Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {
    
    

		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
    
    
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}

	// 查找适用于bean所有符合条件的Advisors
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    
    
		// 查找BeanFactory所有Advisors
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 找适用于bean的Advisors
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
    
    
			// 排序
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
	// 查找BeanFactory所有Advisors
	protected List<Advisor> findCandidateAdvisors() {
    
    
		Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
		// 返回所有Advisors
		return this.advisorRetrievalHelper.findAdvisorBeans();
	}

	// 找适用于bean的Advisors
	protected List<Advisor> findAdvisorsThatCanApply(
			List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {
    
    
		// 设置代理创建容器当前处理的beanName
		ProxyCreationContext.setCurrentProxiedBeanName(beanName);
		try {
    
    
			// 适配查找
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		}
		finally {
    
    
			// 清理代理创建容器当前处理的beanName
			ProxyCreationContext.setCurrentProxiedBeanName(null);
		}
	}

BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()

// Find all Advisors in BeanFactory

	public List<Advisor> findAdvisorBeans() {
    
    
		// 获取所有已缓存的AdvisorBeanNames
		String[] advisorNames = this.cachedAdvisorBeanNames;
		/* 缓存没有 */
		if (advisorNames == null) {
    
    
			// 在beanFactory中查找所有Advisor类
			advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
					this.beanFactory, Advisor.class, true, false);
			// 结果放到缓存
			this.cachedAdvisorBeanNames = advisorNames;
		}
		// 不存在Advisor类
		if (advisorNames.length == 0) {
    
    
			return new ArrayList<>();
		}

		List<Advisor> advisors = new ArrayList<>();
		/* 逐一判断每个advisor是否适合当前bean */
		for (String name : advisorNames) {
    
    
			if (isEligibleBean(name)) {
    
      // isEligibleBean返回的总是true
				if (this.beanFactory.isCurrentlyInCreation(name)) {
    
    
					// 当前bean正被创建中,就跳过
					if (logger.isTraceEnabled()) {
    
    
						logger.trace("Skipping currently created advisor '" + name + "'");
					}
				}
				else {
    
    
					try {
    
    
						// 获取每个advisor的bean实例(当成普通bean获取实例),加入到集合中
						advisors.add(this.beanFactory.getBean(name, Advisor.class));
					}
					catch (BeanCreationException ex) {
    
    
						Throwable rootCause = ex.getMostSpecificCause();
						if (rootCause instanceof BeanCurrentlyInCreationException) {
    
    
							BeanCreationException bce = (BeanCreationException) rootCause;
							String bceBeanName = bce.getBeanName();
							if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {
    
    
							// 如果异常是因为bean正在创建中引起的,则continue处理下一个advisor
								if (logger.isTraceEnabled()) {
    
    
									logger.trace("Skipping advisor '" + name +
											"' with dependency on currently created bean: " + ex.getMessage());
								}
								continue;
							}
						}
						throw ex;
					}
				}
			}
		}
		return advisors;
	}

Note: Advisor classes are prototype types, so they need to be created every time they are obtained.

AopUtils.findAdvisorsThatCanApply(List candidateAdvisors, Class<?> clazz)

Select the Advisors appropriate for the beanClass from the Advisors selection

	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    
    
		// 后选择为空
		if (candidateAdvisors.isEmpty()) {
    
    
			return candidateAdvisors;
		}

		// 选择结果集合
		List<Advisor> eligibleAdvisors = new ArrayList<>();

		/* 处理引介增强 declare-parents*/
		for (Advisor candidate : candidateAdvisors) {
    
    
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
    
    
				eligibleAdvisors.add(candidate);
			}
		}
		
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
    
    
			if (candidate instanceof IntroductionAdvisor) {
    
    
				// 上一步已处理
				continue;
			}
			if (canApply(candidate, clazz, hasIntroductions)) {
    
    
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}

	// advisor是否适合class --过渡类
	public static boolean canApply(Advisor advisor, Class<?> targetClass) {
    
    
		return canApply(advisor, targetClass, false);
	}
	// advisor是否适合class
	public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
    
    
		if (advisor instanceof IntroductionAdvisor) {
    
    
			// 引介增强器判断是否合适(同切点判断方式不一样)
			return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
		}
		else if (advisor instanceof PointcutAdvisor pca) {
    
    
			// 切点判断是否合适
			return canApply(pca.getPointcut(), targetClass, hasIntroductions);
		}
		else {
    
    
			// 没有切点的话,假定是合适的 (why?)
			return true;
		}
	}
	// 用切点判断advisor是否适合class
	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    
    
		Assert.notNull(pc, "Pointcut must not be null");
		// 判断切点表达式是否匹配目标类
		if (!pc.getClassFilter().matches(targetClass)) {
    
    
			return false;
		}

		// 判断方法是否匹配
		MethodMatcher methodMatcher = pc.getMethodMatcher();
		if (methodMatcher == MethodMatcher.TRUE) {
    
    
			// No need to iterate the methods if we're matching any method anyway...
			return true;
		}

		// 判断是否引介增强匹配 注1
		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
    
    
			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
		}

		/* 对目标类和所有实现的接口类的方法进行匹配判断 */
		Set<Class<?>> classes = new LinkedHashSet<>();
		// 如果目标类不是代理类,加入待处理类
		if (!Proxy.isProxyClass(targetClass)) {
    
    
			classes.add(ClassUtils.getUserClass(targetClass));
		}
		// 获取目标类实现的所有接口(包括由祖先类实现的)
		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
		// 逐一对目标类或实现接口类的方法进行切点匹配判断
		for (Class<?> clazz : classes) {
    
    
			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
			for (Method method : methods) {
    
    
				if (introductionAwareMethodMatcher != null ?
						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
						methodMatcher.matches(method, targetClass)) {
    
    
					return true;
				}
			}
		}

		return false;
	}

Note 1: Point-cut matching is mainly based on matching expressions or methods of the same type, and will not be further tracked. The related classes are mainly AspectJExpressionPointcut. The related class diagram is as follows:
Insert image description here

Create proxy bean

AbstractAutoProxyCreator.createProxyClass(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource)

Create proxy bean

	//过渡类
	private Class<?> createProxyClass(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {
    
    
		return (Class<?>) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true);
	}

	//创建代理bean
	private Object buildProxy(Class<?> beanClass, @Nullable String beanName,			@Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) {
    
    
		// 在ConfigurableListableBeanFactory中设置目标类为原始类
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
    
    
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}
		
		// 创建代理工厂
		ProxyFactory proxyFactory = new ProxyFactory();
		// 获取当前类的ProxyConfig属性 (两者都祖先类都有ProxyConfig)
		proxyFactory.copyFrom(this);
		// 判断当前代理工厂(来自上一步的copy属性)是基于TargetClass代理还是基于接口代理
		if (proxyFactory.isProxyTargetClass()) {
    
    
			// 代理工厂是基于TargetClass代理
			if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
    
    
				// bean是处理类或lambdas类
				for (Class<?> ifc : beanClass.getInterfaces()) {
    
    
					// 把接口方法加入到代理工厂
					proxyFactory.addInterface(ifc);
				}
			}
		}
		else {
    
    
			// 代理工厂是基于接口代理
			// 确定bean是目标类而不是接口
			if (shouldProxyTargetClass(beanClass, beanName)) {
    
    
				proxyFactory.setProxyTargetClass(true);
			}
			else {
    
    
				// bean的接口加到代理工厂
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		// 封装所有Advisors
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		// 把Advisors加到代理工行
		proxyFactory.addAdvisors(advisors);
		proxyFactory.setTargetSource(targetSource);
		customizeProxyFactory(proxyFactory);

		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
    
    
			proxyFactory.setPreFiltered(true);
		}

		// 如果bean类不是用重写类类加载器加载,就使用原始类加载器
		ClassLoader classLoader = getProxyClassLoader();
		if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
    
    
			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
		}
		// 创建代理类返回
		return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader));
	}

ProxyFactory.getProxyClass(@Nullable ClassLoader classLoader)

	public Object getProxyClass(@Nullable ClassLoader classLoader) {
    
    
		// createAopProxy()创建AOP代理器,再由代理器生成代理类(getProxyClass(classLoader))
		return createAopProxy().getProxyClass(classLoader);
	}

	// createAopProxy()-创建AOP代理器的实现过程
	protected final synchronized AopProxy createAopProxy() {
    
    
		if (!this.active) {
    
    
			activate();
		}
		return getAopProxyFactory().createAopProxy(this);
	}
	// createAopProxy(this)-创建AOP代理器的实现过程 注1
	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    
    
		// optimize 默认false,为true表示可启用CGLIB动态代理器
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
    
    
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
    
    
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) {
    
    
				// 接口类、代理目标类或Lambda类用JDK的动态代理器
				return new JdkDynamicAopProxy(config);
			}
			// 用CGLIB动态代理器
			return new ObjenesisCglibAopProxy(config);
		}
		else {
    
    
			// 接口类、代理目标类或Lambda类用JDK的动态代理器
			return new JdkDynamicAopProxy(config);
		}
	}

Note 1: Class diagram relationship of JDK dynamic proxy or CGLIB dynamic proxy:
Insert image description here

JDK dynamic proxy-JdkDynamicAopProxy generates proxy class

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
    
    
		if (logger.isTraceEnabled()) {
    
    
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
	}

	/* 下面方法来自:java.lang.reflect.Proxy (均是jdk本身的,同spring无关)*/
	/** 新生成指定接口的代理实例,该实例将方法调用分派给指定的调用处理程序
	@param loader 定义代理类的类加载器
	@param interfaces 要实现的代理类的接口列表
	@param h InvocationHandler是方法调用处理器,就是在invoke方法时进行代理封装
	*/
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) {
    
    
		// h 不能为空                                          
        Objects.requireNonNull(h);

		// 获取调用本方法的方法的调用类
        @SuppressWarnings("removal")
        final Class<?> caller = System.getSecurityManager() == null
                                    ? null
                                    : Reflection.getCallerClass();

        //查找或生成指定的代理类的构造器
        Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);

		// 构建代理类新实例并返回
        return newProxyInstance(caller, cons, h);
    }
    //查找或生成指定的代理类的构造器
    private static Constructor<?> getProxyConstructor(Class<?> caller,
                                                      ClassLoader loader,
                                                      Class<?>... interfaces)
    {
    
    
        // optimization for single interface
        // 只有一个接口需做代理
        if (interfaces.length == 1) {
    
    
            Class<?> intf = interfaces[0];
            if (caller != null) {
    
    
            	// 检查是否允许被代理
                checkProxyAccess(caller, loader, intf);
            }
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        } else {
    
    
        // 多个接口需做代理
            //克隆拿到接口
            final Class<?>[] intfsArray = interfaces.clone();
            if (caller != null) {
    
    
                checkProxyAccess(caller, loader, intfsArray);
            }
            final List<Class<?>> intfs = Arrays.asList(intfsArray);
            return proxyCache.sub(intfs).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
    }   
    // 生成新实例 
    private static Object newProxyInstance(Class<?> caller, 
                                           Constructor<?> cons,
                                           InvocationHandler h) {
    
    
        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
    
    
            if (caller != null) {
    
    
                checkNewProxyPermission(caller, cons.getDeclaringClass());
            }
			// 用构建器生成新实例
            return cons.newInstance(new Object[]{
    
    h});
        } catch (IllegalAccessException | InstantiationException e) {
    
    
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
    
    
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
    
    
                throw (RuntimeException) t;
            } else {
    
    
                throw new InternalError(t.toString(), t);
            }
        }
    }

CGLIB proxy-CglibAopProxy generates proxy classes

	// 过渡类
	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
    
    
		return buildProxy(classLoader, false);
	}
	
	private Object buildProxy(@Nullable ClassLoader classLoader, boolean classOnly) {
    
    
		if (logger.isTraceEnabled()) {
    
    
			logger.trace("Creating CGLIB proxy: " + 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;
			// 类是CGLIB类(beanName字含有“$$“,注意同java嵌套类区别(含有"$");针对引入增强
			if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
    
    
				// 取上级类
				proxySuperClass = rootClass.getSuperclass();
				// 获取所有接口加入到advisor
				Class<?>[] additionalInterfaces = rootClass.getInterfaces();
				for (Class<?> additionalInterface : additionalInterfaces) {
    
    
					this.advised.addInterface(additionalInterface);
				}
			}

			// Validate the class, writing log messages as necessary.
			validateClassIfNecessary(proxySuperClass, classLoader);

			// Configure CGLIB Enhancer...
			// Enhancer是CGLIB封装的增强类,支持CGLIB功能处理
			Enhancer enhancer = 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.setAttemptLoad(true);
			enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

			//获取代理的回调方法集合
			Callback[] callbacks = getCallbacks(rootClass);
			Class<?>[] types = new Class<?>[callbacks.length];
			for (int x = 0; x < types.length; x++) {
    
    
				types[x] = callbacks[x].getClass();
			}
			// fixedInterceptorMap only populated at this point, after getCallbacks call above
			enhancer.setCallbackFilter(new ProxyCallbackFilter(
					this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
			enhancer.setCallbackTypes(types);

			// Generate the proxy class and create a proxy instance.
			return (classOnly ? createProxyClass(enhancer) : createProxyClassAndInstance(enhancer, callbacks));
		}
		catch (CodeGenerationException | IllegalArgumentException ex) {
    
    
			throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() +
					": Common causes of this problem include using a final class or a non-visible class",
					ex);
		}
		catch (Throwable ex) {
    
    
			// TargetSource.getTarget() failed
			throw new AopConfigException("Unexpected AOP exception", ex);
		}
	}
	// 用enhancer创建目标类代理实例
	protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    
    
		enhancer.setInterceptDuringConstruction(false);
		enhancer.setCallbacks(callbacks);
		return (this.constructorArgs != null && this.constructorArgTypes != null ?
				enhancer.create(this.constructorArgTypes, this.constructorArgs) :
				enhancer.create());
	}

Guess you like

Origin blog.csdn.net/davidwkx/article/details/131810003