spring 中自动代理生成器的实现

为什么需要自动代理生成器

在 spring 中,提供了 org.springframework.aop.framework.ProxyFactoryBean 来实现对目标 bean 的增强,但此工具类存在以下缺点:

  • 目标 bean 被增强后,获取实例对象时,使用的是配置的代理 bean 的 id,而非配置的目标 bean 的 id。
  • 配置繁琐,不利于规模使用,一个代理对象只能代理一个 bean,如果有多个 bean 需要代理增强时,需要配置多个代理对象。

基于此,在 spring 中提供了自动代理生成器,来解决 ProxyFactoryBean 存在的问题,常用的自动代理生成器有两个:

  • org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator
  • org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator 

 

 

通过类关系可以知道,这两个类都是 AbstractAutoProxyCreator 子类,AbstractAutoProxyCreator 实现了 BeanPostProcessor,前面的文章介绍过 spring 中 BeanPostProcessor 的调用时机,在创建实例对象时,调用实现了 BeanPostProcessor 接口中的方法,完成对目标 bean 的增强,这样,使用时直接使用目标 bean 的 id 即可。

下面来看看这两个自动代理生成器具体是怎么实现的,先来看看他们共同的父类 AbstractAutoProxyCreator。

AbstractAutoProxyCreator

在 AbstractAutoProxyCreator 中,实现了 BeanPostProcessor 中的接口方法。

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
	Object cacheKey = getCacheKey(beanClass, beanName);

	if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (this.advisedBeans.containsKey(cacheKey)) {
			return null;
		}
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null;
		}
	}

	// Create proxy here if we have a custom TargetSource.
	// Suppresses unnecessary default instantiation of the target bean:
	// The TargetSource will handle target instances in a custom fashion.
	TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
	if (targetSource != null) {
		if (StringUtils.hasLength(beanName)) {
			this.targetSourcedBeans.add(beanName);
		}
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
		Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	return null;
}
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
	if (bean != null) {
		Object cacheKey = getCacheKey(bean.getClass(), beanName);
		if (this.earlyProxyReferences.remove(cacheKey) != bean) {
			return wrapIfNecessary(bean, beanName, cacheKey);
		}
	}
	return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	return bean;
}

 在实例化之前,对一些不需要增强的类进行标记,这样在初始化时,当执行到 wrapIfNecessary ,对不需要增强的类就直接返回了。在方法 wrapIfNecessary 中,调用了一个抽象方法,AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean,留作子类来实现,根据名称可知,对获取 Advice 和 Advisor 的不同,就是这两个自动代理生成器实现的主要不同。

DefaultAdvisorAutoProxyCreator

@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();
}

对于 DefaultAdvisorAutoProxyCreator,其实现方法位于父类 AbstractAdvisorAutoProxyCreator,继承 AbstractAutoProxyCreator。

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	extendAdvisors(eligibleAdvisors);
	if (!eligibleAdvisors.isEmpty()) {
		eligibleAdvisors = sortAdvisors(eligibleAdvisors);
	}
	return eligibleAdvisors;
}

找到合适的 Advisor,其实现逻辑如下:

  1. 找到所有的 Advisor 候选者 
  2. 对 Advisor 候选者进行过滤
  3. 扩展 Advisor,针对 DefaultAdvisorAutoProxyCreator,并未扩展
  4. 排序,默认采用 AnnotationAwareOrderComparator 比较器实现排序

下面来详细介绍: 

public List<Advisor> findAdvisorBeans() {
	// 先看下是否已缓存
	String[] advisorNames = this.cachedAdvisorBeanNames;
	if (advisorNames == null) {
		advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
				this.beanFactory, Advisor.class, true, false);
		this.cachedAdvisorBeanNames = advisorNames;
	}
	if (advisorNames.length == 0) {
		return new ArrayList<>();
	}

	List<Advisor> advisors = new ArrayList<>();
	for (String name : advisorNames) {
		if (isEligibleBean(name)) {
			if (this.beanFactory.isCurrentlyInCreation(name)) {
				...
			}
			else {
				try {
					advisors.add(this.beanFactory.getBean(name, Advisor.class));
				}
				catch (BeanCreationException ex) {
					...
				}
			}
		}
	}
	return advisors;
}

借助工具类 BeanFactoryAdvisorRetrievalHelper,此时会遍历系统中注册的所有 beanName, 根据 beanName 得到 RootBeanDefinition,包含配置 bean 的所有信息,解析出 bean 对应的 Class,判断是要匹配的 Advisor 类型,将 beanName 加入 advisorNames,最后将 advisorNames 赋值给 cachedAdvisorBeanNames 缓存起来。接着遍历 advisorNames,对未创建的  advisor 调用 beanFactory.getBean 进行创建。

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) {
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		return true;
	}
}
// AopUtils
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;
	}

	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;
}

 对 Advisor 候选者进行过滤。我们知道增强是针对某个类中的某个方法进行增强,所以只有目标类和类中的方法都能匹配到给定的过滤规则,才算是一个合格的 Advisor。类的匹配通过 ClassFilter 完成,方法的匹配通过 MethodMatcher 来完成,这两个类都是接口,实际使用时,既可以使用 spring 中已经实现的实现类,也可以自定义实现来进行扩展,满足自己的匹配需求。spring 中常用的方法匹配器有以下几个:

  • NameMatchMethodPointcut:NameMatchMethodPointcutAdvisor 使用,对应属性 mappedName 和 mappedNames,支持通配符匹配。
  • JdkRegexpMethodPointcut:RegexpMethodPointcutAdvisor 使用,通过配置的正则表达式进行匹配,对应属性 pattern 和 patterns。

BeanNameAutoProxyCreator 

BeanNameAutoProxyCreator 会通过属性 beanNames 来设置要匹配的 bean 名称。在调用 BeanNameAutoProxyCreator#getAdvicesAndAdvisorsForBean 来获取指定拦截器时,会遍历配置的 beanNames,来判断是否和正在创建的 bean 名称相匹配,符合条件返回 true,支持名称通配符匹配,*xxx、xxx*、*xxx*。

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

	return (isSupportedBeanName(beanClass, beanName) ?
			PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS : DO_NOT_PROXY);
}
private boolean isSupportedBeanName(Class<?> beanClass, String beanName) {
	if (this.beanNames != null) {
		boolean isFactoryBean = FactoryBean.class.isAssignableFrom(beanClass);
		for (String mappedName : this.beanNames) {
			if (isFactoryBean) {
				if (!mappedName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
					continue;
				}
				mappedName = mappedName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
			}
			if (isMatch(beanName, mappedName)) {
				return true;
			}
		}

		BeanFactory beanFactory = getBeanFactory();
		String[] aliases = (beanFactory != null ? beanFactory.getAliases(beanName) : NO_ALIASES);
		for (String alias : aliases) {
			for (String mappedName : this.beanNames) {
				if (isMatch(alias, mappedName)) {
					return true;
				}
			}
		}
	}
	return false;
}

 这样,匹配的话会返回 PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS,其实是一个 Object[0],返回赋值给 specificInterceptors。可以看到此时并不像 DefaultAdvisorAutoProxyCreator 那样,已经获取到了匹配的 Advisor,那么具体的 Advisor 到底是在什么时候获取的呢?

代理的创建

在 AbstractAutoProxyCreator#wrapIfNecessary 中,调用完 getAdvicesAndAdvisorsForBean 返回  specificInterceptors 之后,对 specificInterceptors 进行判断,不为 DO_NOT_PROXY,进行代理的创建。

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()) {
		// Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios)
		if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
			// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.
			for (Class<?> ifc : beanClass.getInterfaces()) {
				proxyFactory.addInterface(ifc);
			}
		}
	}
	else {
		// No proxyTargetClass flag enforced, let's apply our default checks...
		if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}

	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	customizeProxyFactory(proxyFactory);

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

	// Use original ClassLoader if bean class not locally loaded in overriding class loader
	ClassLoader classLoader = getProxyClassLoader();
	if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {
		classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
	}
	return proxyFactory.getProxy(classLoader);
}

在这段代码中,核心思想就是封装一个 ProxyFactory,设置完相关属性后,利用 ProxyFactory 创建代理。

有一点需注意,advisorsPreFiltered 方法。继承 AbstractAdvisorAutoProxyCreator 的子类,由于在获取 Advisor 时在方法 AbstractAdvisorAutoProxyCreator#findEligibleAdvisor 中调用了 AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply,即对获取的 candidateAdvisors进行了过滤,所以在 AbstractAdvisorAutoProxyCreator 中对 advisorsPreFiltered 方法进行了重写。

@Override
protected boolean advisorsPreFiltered() {
	return true;
}

即采用 AbstractAdvisorAutoProxyCreator 子类的自动代理生成器,创建代理时 AdvisedSupport 中 preFiltered 恒为 true。这一点后面方法调用创建拦截器链时会用到。

下面我们主要看下其中的 buildAdvisors 方法。

protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {
	// Handle prototypes correctly...
	Advisor[] commonInterceptors = resolveInterceptorNames();

	List<Object> allInterceptors = new ArrayList<>();
	if (specificInterceptors != null) {
		if (specificInterceptors.length > 0) {
			// specificInterceptors may equal PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS
			allInterceptors.addAll(Arrays.asList(specificInterceptors));
		}
		if (commonInterceptors.length > 0) {
			if (this.applyCommonInterceptorsFirst) {
				allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
			}
			else {
				allInterceptors.addAll(Arrays.asList(commonInterceptors));
			}
		}
	}	

	Advisor[] advisors = new Advisor[allInterceptors.size()];
	for (int i = 0; i < allInterceptors.size(); i++) {
        // 适配器适配
		advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
	}
	return advisors;
}
private Advisor[] resolveInterceptorNames() {
	BeanFactory bf = this.beanFactory;
	ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) bf : null);
	List<Advisor> advisors = new ArrayList<>();
	for (String beanName : this.interceptorNames) {
		if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
			Assert.state(bf != null, "BeanFactory required for resolving interceptor names");
			Object next = bf.getBean(beanName);
			advisors.add(this.advisorAdapterRegistry.wrap(next));
		}
	}
	return advisors.toArray(new Advisor[0]);
}

 这个方法位于 AbstractAutoProxyCreator,遍历配置的 interceptorNames,利用 bf.getBean 根据 beanName 获取对象,接着利用 advisorAdapterRegistry 适配为 Advisor,加入 advisors,最后转换为数组,赋值给 commonInterceptors。接着会将 specificInterceptors 和 commonInterceptors 合并,默认将 commonInterceptors 放在 specificInterceptors 之前。

由此也可以看出,两个自动代理生成器获取 Advisor 的不同:

  • DefaultAdvisorAutoProxyCreator:主要是通过调用 getAdvicesAndAdvisorsForBean 方法来获取,如果获取不到,返回 DO_NOT_PROXY,就不会创建代理,只有获取到 Advisor,才会创建代理,此时如果也配置的 interceptorNames 属性,在创建代理调用 buildAdvisors 时会将配置的 interceptorNames 解析为 commonInterceptors,作为 specificInterceptors 的补充。
  • BeanNameAutoProxyCreator:调用 getAdvicesAndAdvisorForBean 方法时,只会拿当前 beanName 和配置的 interceptorNames 匹配,并不会创建实际的 Advisor,只有在 beanName 符合,创建代理调用 buildAdvisors 时,才会根据配置的 interceptorsNames 创建具体的 Advisor。

至此,就完成了创建代理前的所有准备工作,接着创建代理,此处只有一处需要注意,就是:对于不存在接口的目标类,默认采用 ObjenesisCglibAopProxy 生成代理;对于存在接口的目标类,默认采用 JdkDynamicAopProxy 生成代理。如果对存在接口的目标类也想使用 ObjenesisCglibAopProxy,可在配置自动代理生成器时配置属性,optimize、proxyTargetClass,两个属性配置一个即可。

// DefaultAopProxyFactory
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
	if (!NativeDetector.inNativeImage() &&
			(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)) {
			return new JdkDynamicAopProxy(config);
		}
		return new ObjenesisCglibAopProxy(config);
	}
	else {
		return new JdkDynamicAopProxy(config);
	}
}

下面来看看这两种生成代理的方式具体是如何创建代理的。

ObjenesisCglibAopProxy

这个类继承 CglibAopProxy,是对 CglibAopProxy 创建代理的一种扩展,采用 CglibAopProxy 创建代理类,然后采用 Objenesis 实现实例化。Objenesis 的原理,前面的文章已经介绍过了,Objenesis 底层探究。

下面来看看 CglibAopProxy 创建代理的过程。

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	...
	try {
		Class<?> rootClass = this.advised.getTargetClass();
		Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

		Class<?> proxySuperClass = rootClass;
		if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
			proxySuperClass = rootClass.getSuperclass();
			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 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.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 createProxyClassAndInstance(enhancer, callbacks);
	}
	catch (CodeGenerationException | IllegalArgumentException ex) {
		...
	}
	catch (Throwable ex) {
		...
	}
}
@Override
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
	// cglib 创建代理类
	Class<?> proxyClass = enhancer.createClass();
	Object proxyInstance = null;

	if (objenesis.isWorthTrying()) {
		try {
            // Objenesis 针对这个代理类进行实例化
			proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
		}
		catch (Throwable ex) {
			...
		}
	}

	if (proxyInstance == null) {
		// Regular instantiation via default constructor...
		try {
			Constructor<?> ctor = (this.constructorArgs != null ?
					proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
					proxyClass.getDeclaredConstructor());
			ReflectionUtils.makeAccessible(ctor);
			proxyInstance = (this.constructorArgs != null ?
					ctor.newInstance(this.constructorArgs) : ctor.newInstance());
		}
		catch (Throwable ex) {
			...
		}
	}

	((Factory) proxyInstance).setCallbacks(callbacks);
	return proxyInstance;
}

 代码很好理解,封装 enhancer,调用方法,有一点注意,getCallbacks。

private Callback[] getCallbacks(Class<?> rootClass) throws Exception {
	// Parameters used for optimization choices...
	boolean exposeProxy = this.advised.isExposeProxy();
	boolean isFrozen = this.advised.isFrozen();
	// SingletonTargetSource 中 isStatic 恒为 true
	boolean isStatic = this.advised.getTargetSource().isStatic();

	// Choose an "aop" interceptor (used for AOP calls).
	Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

	// Choose a "straight to target" interceptor. (used for calls that are
	// unadvised but can return this). May be required to expose the proxy.
	Callback targetInterceptor;
	if (exposeProxy) {
		targetInterceptor = (isStatic ?
				new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) :
				new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()));
	}
	else {
		targetInterceptor = (isStatic ?
				new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) :
				new DynamicUnadvisedInterceptor(this.advised.getTargetSource()));
	}

	// Choose a "direct to target" dispatcher (used for
	// unadvised calls to static targets that cannot return this).
	Callback targetDispatcher = (isStatic ?
			new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

	Callback[] mainCallbacks = new Callback[] {
			aopInterceptor,  // for normal advice
			targetInterceptor,  // invoke target without considering advice, if optimized
			new SerializableNoOp(),  // no override for methods mapped to this
			targetDispatcher, this.advisedDispatcher,
			new EqualsInterceptor(this.advised),
			new HashCodeInterceptor(this.advised)
	};

	Callback[] callbacks;

	// If the target is a static one and the advice chain is frozen,
	// then we can make some optimizations by sending the AOP calls
	// direct to the target using the fixed chain for that method.
	// TargetSource isStatic 返回 true 且自动代理生成器设置了 frozen 属性
	if (isStatic && isFrozen) {
		Method[] methods = rootClass.getMethods();
		Callback[] fixedCallbacks = new Callback[methods.length];
		this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

		// TODO: small memory optimization here (can skip creation for methods with no advice)
		for (int x = 0; x < methods.length; x++) {
			Method method = methods[x];
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
			fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
					chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
			this.fixedInterceptorMap.put(method, x);
		}

		// Now copy both the callbacks from mainCallbacks
		// and fixedCallbacks into the callbacks array.
		callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
		// 复制 mainCallbacks 到 callbacks
		System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
		// 复制 fixedCallbacks 到 callbacks
		System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
		this.fixedInterceptorOffset = mainCallbacks.length;
	}
	else {
		callbacks = mainCallbacks;
	}
	return callbacks;
}

此处主要关注 CglibAopProxy.DynamicAdvisedInterceptor 这个内部类,作为 aopIntercprtor,会在后续方法调用时使用到。

JdkDynamicAopProxy

此种方式相对于 CglibAopProxy,能简单一点。

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
	...
	// JdkDynamicAopProxy 也实现了 InvocationHnadler 接口
	return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
}

这样代理对象的创建也就完成了,接着就到了对方法的调用。 

方法调用,拦截器链的创建

获取到返回的代理对象后,对方法的调用便由代理对象发起,不同的代理方式生成的代理对象,发起方法调用时也有不同。

ObjenesisCglibAopProxy

对于 Cglib 这种方式,我们知道,这是底层操作 ASM 重新创建了一个类,我们称之为 newClass,在这个 newClass 中,对需要拦截增强的方法,通过 org.springframework.cglib.proxy.MethodInterceptor#interceptor 发起调用。

// 这是某个 newClass 中的增强方法
public final void doSome() {
	MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
	if (var10000 == null) {
		CGLIB$BIND_CALLBACKS(this);
		var10000 = this.CGLIB$CALLBACK_0;
	}

	if (var10000 != null) {
		var10000.intercept(this, CGLIB$doSome$1$Method, CGLIB$emptyArgs, CGLIB$doSome$1$Proxy);
	} else {
		super.doSome();
	}
}

对于 Aop,此处的 var10000 就是前面创建的 aopInterceptor,即 CglibAopProxy.DynamicAdvisedInterceptor

@Override
@Nullable
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();
	try {
		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}
		// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
		Object retVal;
		// Check whether we only have one InvokerInterceptor: that is,
		// no real advice, but just reflective invocation of the target.
		if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
			// We can skip creating a MethodInvocation: just invoke the target directly.
			// Note that the final invoker must be an InvokerInterceptor, so we know
			// it does nothing but a reflective operation on the target, and no hot
			// swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = invokeMethod(target, method, argsToUse, methodProxy);
		}
		else {
			// We need to create a method invocation...
			retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
		}
		retVal = processReturnType(proxy, target, method, retVal);
		return retVal;
	}
	finally {
		if (target != null && !targetSource.isStatic()) {
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			// Restore old proxy.
			AopContext.setCurrentProxy(oldProxy);
		}
	}
}

核心思想就是调用 getInterceptorsAndDynamicInterceptionAdvice 获取针对 targetClass 中 method 方法的拦截器链,然后封装 CglibMethodInvocation 发起调用。

// AdvisedSupport
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
	// method 和 method.hashCode() 创建缓存,提高效率
	MethodCacheKey cacheKey = new MethodCacheKey(method);
	List<Object> cached = this.methodCache.get(cacheKey);
	if (cached == null) {
		cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
				this, method, targetClass);
		this.methodCache.put(cacheKey, cached);
	}
	return cached;
}
// DefaultAdvisorChainFactory
@Override
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
		Advised config, Method method, @Nullable Class<?> targetClass) {

	// This is somewhat tricky... We have to process introductions first,
	// but we need to preserve order in the ultimate list.
	AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
	// 获取 advisors
	Advisor[] advisors = config.getAdvisors();
	List<Object> interceptorList = new ArrayList<>(advisors.length);
	Class<?> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
	Boolean hasIntroductions = null;

	for (Advisor advisor : advisors) {
		if (advisor instanceof PointcutAdvisor) {
			// Add it conditionally.
			PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
			if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
				MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
				boolean match;
				if (mm instanceof IntroductionAwareMethodMatcher) {
					if (hasIntroductions == null) {
						hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
					}
					match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
				}
				else {
					match = mm.matches(method, actualClass);
				}
				if (match) {
					MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
					if (mm.isRuntime()) {
						// Creating a new object instance in the getInterceptors() method
						// isn't a problem as we normally cache created chains.
						for (MethodInterceptor interceptor : interceptors) {
							interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
						}
					}
					else {
						interceptorList.addAll(Arrays.asList(interceptors));
					}
				}
			}
		}
		else if (advisor instanceof IntroductionAdvisor) {
			IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
			if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
				Interceptor[] interceptors = registry.getInterceptors(advisor);
				interceptorList.addAll(Arrays.asList(interceptors));
			}
		}
		else {
			Interceptor[] interceptors = registry.getInterceptors(advisor);
			interceptorList.addAll(Arrays.asList(interceptors));
		}
	}

	return interceptorList;
}

 核心思想就是,将创建代理时 ProxyFactory 中设置的 advisors 转换成 org.aopalliance.intercept.MethodInterceptor,封装 interceptorList。

可以看到,在创建拦截器链时,对 advisor 进行了过滤。

当自动代理生成器是 AbstractAdvisorAutoProxyCreator 的子类时,config.isPreFiltered() 恒为 true,所以为 Advisor 配置的 ClassFilter 在此处不会生效,只会进行下面的 MethodMatcher 匹配。

当自动代理生成器是 BeanNameAutoProxyCreator 时,此时 config.isPreFiltered() 为 false,为 Advisor 配置的 ClassFilter 匹配会生效。

封装 interceptorList 时会通过适配器将 Advisor 适配成 MethodInterceptor

// DefaultAdvisorAdapterRegistry
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
	List<MethodInterceptor> interceptors = new ArrayList<>(3);
	Advice advice = advisor.getAdvice();
	if (advice instanceof MethodInterceptor) {
		interceptors.add((MethodInterceptor) advice);
	}
	for (AdvisorAdapter adapter : this.adapters) {
		if (adapter.supportsAdvice(advice)) {
			interceptors.add(adapter.getInterceptor(advisor));
		}
	}
	if (interceptors.isEmpty()) {
		throw new UnknownAdviceTypeException(advisor.getAdvice());
	}
	return interceptors.toArray(new MethodInterceptor[0]);
}

 我们知道,在 spring 中,定义了四种通知类型:

  • 前置通知 MethodBeforeAdvice
  • 后置通知 AfterReturningAdvice
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor
  • 异常通知 ThrowsAdvice

实际使用时,实现对应的接口即可。 此处获取拦截器时,可以看到,先从 Advisor 中获取 Advice,如果是 org.aopalliance.intercept.MethodInterceptor 类型,直接加入 interceptors 集合,如果不是,遍历 adapters 适配器集合,逐个适配。在 DefaultAdvisorAdapterRegistry 中,添加了三个适配器,分别对应前置通知、后置通知、异常通知。

public DefaultAdvisorAdapterRegistry() {
	registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
	registerAdvisorAdapter(new AfterReturningAdviceAdapter());
	registerAdvisorAdapter(new ThrowsAdviceAdapter());
}

以 MethodBeforeAdviceAdapter 为例,看下具体是如何适配的。

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

	@Override
	public boolean supportsAdvice(Advice advice) {
		return (advice instanceof MethodBeforeAdvice);
	}

	@Override
	public MethodInterceptor getInterceptor(Advisor advisor) {
		MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
		return new MethodBeforeAdviceInterceptor(advice);
	}

}

 可以看到整个类很简单,就两个方法,一个用来判断是否是支持的 Advice 类型,另一个用于返回相应的拦截器,这个拦截器持有这个支持的 Advice。

这样,就完成了从 Advisor 到 org.aopalliance.intercept.MethodInterceptor 的转换。

得到拦截器链 chain 后,封装 CglibMethodInvocation,发起方法调用,调用很简单,委托给父类 ReflectiveMethodInvocation 执行。

// CglibMethodInvocation
@Override
@Nullable
public Object proceed() throws Throwable {
	try {
		return super.proceed();
	}
	catch (RuntimeException ex) {
		throw ex;
	}
	catch (Exception ex) {
		...
	}
}
// ReflectiveMethodInvocation
@Override
@Nullable
public Object proceed() throws Throwable {
	// We start with an index of -1 and increment early.
	// currentInterceptorIndex 初始值 -1
	// interceptorsAndDynamicMethodMatchers 封装的拦截器链,是一个 list 集合
	if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
		return invokeJoinpoint();
	}

	Object interceptorOrInterceptionAdvice =
			this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
	if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
		// Evaluate dynamic method matcher here: static part will already have
		// been evaluated and found to match.
		InterceptorAndDynamicMethodMatcher dm =
				(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
		Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
		if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
			return dm.interceptor.invoke(this);
		}
		else {
			// Dynamic matching failed.
			// Skip this interceptor and invoke the next in the chain.
			return proceed();
		}
	}
	else {
		// It's an interceptor, so we just invoke it: The pointcut will have
		// been evaluated statically before this object was constructed.
		return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
	}
}

ReflectiveMethodInvocation,这是一个 CglibAopProxy 和 JdkDynamicAopProxy 共用的类,也是spring Aop 中方法调用的核心。

每次一进入方法,拿 currentInterceptorIndex 与 拦截器链 chain 的最大索引做比较,相等时,表示已经执行完了最后一个拦截器,此时会发起 invokeJoinPoint,调用连接点,其实就是调用目标方法。不相等时,获取当前拦截器,由于 currentInterceptorIndex 从 -1 开始,每次先进行自增,这样下一次进入时就表示已经执行了第 currentInterceptorIndex 对应的拦截器,然后根据索引获取当前拦截器,调用 invoke 方法。

以前置顾问为例,来看下拦截器的执行,通过前面的适配器可知,前置顾问被适配成了 MethodBeforeAdviceInterceptor。

@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {
	this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());
	return mi.proceed();
}

通过调用自实现的顾问方法,完成方法的增强,接着 mi.proceed() 继续向前执行,此时又会执行到 CglibMethodInvocation,接着父类 ReflectiveMethodInvocation,进行下一轮判断。当执行了最后一个拦截器后,再次进入 ReflectiveMethodInvocation#proceed 方法进行判断后,会调用连接点方法。

@Override
protected Object invokeJoinpoint() throws Throwable {
	if (this.methodProxy != null) {
		try {
			return this.methodProxy.invoke(this.target, this.arguments);
		}
		catch (CodeGenerationException ex) {
			logFastClassGenerationFailure(this.method);
		}
	}
	return super.invokeJoinpoint();
}

CglibMethodInvocation 重写了 invokeJoinPoint 方法,methodProxy 由发起拦截调用 org.springframework.cglib.proxy.MethodInterceptor#intercept 时通过参数传入,最后通过原始对象调用目标方法。

关于 MethodProxy 的创建及生成原理,参考 cglib 代理的生成过程。

接着沿着拦截器链相反的方向,一步步退出返回。类似于入栈出栈。连接点方法就位于栈的最底端。

下面来看看 JdkDynamicAopProxy 中方法调用时是怎么操作的。

JdkDynamicAopProxy

JdkDynamicAopProxy 实现了java.lang.reflect.InvocationHandler 接口,方法调用时直接进入 invoke 方法执行。

@Override
@Nullable
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;

	try {
		if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
			// The target does not implement the equals(Object) method itself.
			return equals(args[0]);
		}
		else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
			// The target does not implement the hashCode() method itself.
			return hashCode();
		}
		else if (method.getDeclaringClass() == DecoratingProxy.class) {
			// There is only getDecoratedClass() declared -> dispatch to proxy config.
			return AopProxyUtils.ultimateTargetClass(this.advised);
		}
		else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
				method.getDeclaringClass().isAssignableFrom(Advised.class)) {
			// Service invocations on ProxyConfig with the proxy config...
			return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
		}

		Object retVal;

		if (this.advised.exposeProxy) {
			// Make invocation available if necessary.
			oldProxy = AopContext.setCurrentProxy(proxy);
			setProxyContext = true;
		}

		// Get as late as possible to minimize the time we "own" the target,
		// in case it comes from a pool.
		target = targetSource.getTarget();
		Class<?> targetClass = (target != null ? target.getClass() : null);

		// Get the interception chain for this method.
		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

		// Check whether we have any advice. If we don't, we can fall back on direct
		// reflective invocation of the target, and avoid creating a MethodInvocation.
		if (chain.isEmpty()) {
			// We can skip creating a MethodInvocation: just invoke the target directly
			// Note that the final invoker must be an InvokerInterceptor so we know it does
			// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
			Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
			retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
		}
		else {
			// We need to create a method invocation...
			MethodInvocation invocation =
					new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
			// Proceed to the joinpoint through the interceptor chain.
			retVal = invocation.proceed();
		}

		// Massage return value if necessary.
		Class<?> returnType = method.getReturnType();
		if (retVal != null && retVal == target &&
				returnType != Object.class && returnType.isInstance(proxy) &&
				!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
			// Special case: it returned "this" and the return type of the method
			// is type-compatible. Note that we can't help if the target sets
			// a reference to itself in another returned object.
			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);
		}
		return retVal;
	}
	finally {
		if (target != null && !targetSource.isStatic()) {
			// Must have come from TargetSource.
			targetSource.releaseTarget(target);
		}
		if (setProxyContext) {
			// Restore old proxy.
			AopContext.setCurrentProxy(oldProxy);
		}
	}
}

核心逻辑,获取 method 对应的拦截器链,封装 MethodInvocation,发起调用。

其中获取拦截器链的操作与 CglibAopProxy 一致,spring 将这一功能的实现委托给了 AdvisorChainFactory,AdvisedSupport 中的 advisorChainFactory 属性默认赋值 DefaultAdvisorChainFactory 实例对象。符合 java 设计模式中的单一职责模式。

封装 MethodInvocation,此处为 ReflectiveMethodInvocation,接着调用 ReflectiveMethodInvocation#proceed,过程与 CglibMethodInvocation 一致,不同点就在于 invokeJoinpoint 方法。

// ReflectiveMethodInvocation
@Nullable
protected Object invokeJoinpoint() throws Throwable {
	return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}

// AopUtils
@Nullable
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
		throws Throwable {

	// Use reflection to invoke the method.
	try {
		ReflectionUtils.makeAccessible(method);
		return method.invoke(target, args);
	}
	catch (InvocationTargetException ex) {
		...
	}
	catch (IllegalArgumentException ex) {
		...
	catch (IllegalAccessException ex) {
		...
	}
}

 即通过反射调用目标方法。

总结

通过前面的介绍,我们明白了 spring 中对自动代理生成器的实现。从 AbstractAutoProxyCreator#postProcessAfterInitialization 开始,对需要代理的 bean 创建代理。此处采用了模板方法设计模式,AbstractAutoProxyCreator#wrapIfNecessary 定义了代理的基本逻辑,调用了留作子类扩展实现的抽象方法 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean,而对这个抽象方法的实现,就是各个代理生成器的最大区别。

获取到 Advisor 后,开始创建代理,根据有无接口,及代理生成器中配置的 optimize/proxyTargetClass 属性来决定采用哪种方式的代理。

代理创建完成,利用返回的代理对象发起方法调用,针对目标方法,获取拦截器链,封装 MethodInvocation,执行 proceed 方法,这些逻辑实现基本一致,此处最大的不同就在于对连接点方法的调用。cglib 为减少反射开销,生成了一个父类为 FastClass 的工具类,通过原始目标 bean 对象调用目标方法,而 Jdk 动态代理则采用反射的方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/745193.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL使用教程

安装 PostgreSQL 您可以从 PostgreSQL 官方网站下载适合您操作系统的安装程序&#xff0c;并按照安装向导进行安装。 启动数据库服务器 安装完成后&#xff0c;根据您的操作系统&#xff0c;通过相应的方式启动数据库服务器。 连接到数据库 可以使用命令行工具&#xff08;如 p…

EE trade:利弗莫尔三步建仓法

在股市投资领域&#xff0c;利弗莫尔这个名字代表着无数的智慧和经历。他的三步建仓法成为了投资者们趋之若鹜的学习对象。本文将详细解析利弗莫尔的著名买入法&#xff0c;通过分步进攻方式&#xff0c;有效掌控市场并实现盈利。 一、利弗莫尔的三步建仓法详解 利弗莫尔三步…

SaaS 出海:Databend Cloud 的定位与实践

提到 “SaaS 出海”这个词大家肯定并不陌生&#xff0c;SaaS 企业将业务拓展到海外市场已经成为许多 SaaS 公司的重要战略方向。随着企业对于灵活性、可扩展性以及成本效益需求的不断增长&#xff0c; SaaS 模式提供了理想的解决方案。对于寻求出海机会的 SaaS 企业来说&#x…

秋招Java后端开发冲刺——关系型数据库篇(Mysql)

本文介绍关系型数据库及其代表Mysql数据库&#xff0c;并介常见面试题目。 一、数据库概述 1. 数据库&#xff08;Database, DB&#xff09;&#xff1a;是长期储存在计算机内的、有组织的、可共享的数据集合。 2. 数据库管理系统&#xff08;Database Management System, D…

高性能并行计算华为云实验五:PageRank算法实验

目录 一、实验目的 二、实验说明 三、实验过程 3.1 创建PageRank源码 3.2 makefile的创建和编译 3.3 主机配置文件建立与运行监测 四、实验结果与分析 4.1 采用默认的节点数量及迭代次数进行测试 4.2 分析并行化下节点数量与耗时的变化规律 4.3 分析迭代次数与耗时的变…

数据结构——跳表Skip List

本文对跳表的定义、实现、应用等进行简单总结。 一、 介绍 1.定义 跳表&#xff08;Skip List&#xff09;&#xff1a;是一种概率性数据结构&#xff0c;由William Pugh在1990年提出&#xff0c;主要用于在有序的元素集合上进行快速的搜索、插入和删除操作。跳表的效率与平衡…

百威英博旗下知名啤酒品牌Jupiler,创意助力比利时国足角逐欧洲杯冠军!

怎么说呢&#xff1f;今天非常开心。 因为今天分享的这个品牌创意案例很特别&#xff0c;和夏天、足球有关&#xff0c;和梦想、啤酒有关&#xff0c;还和QR Tiger 、二维彩虹有关。而把这一切连接在一起的&#xff0c;是一个小小的二维码。 这个夏天&#xff0c;百威英博旗下…

选专业,分析就业前景和市场需求

大学专业纷繁复杂&#xff0c;每个专业的就业前景和市场需求也天差地别&#xff0c;一般而言&#xff0c;就业前景优和市场需求的专业的学生更容易就业&#xff0c;更容易实现个人价值&#xff1f; 一、充分利用性格优势 在专业选择当中&#xff0c;如果我们自己对某个专业拥有…

背包模型——AcWing 423. 采药

背包模型 定义 背包模型是一种常见的算法问题模型&#xff0c;它主要涉及将一些物品放入一个容量有限的背包中&#xff0c;以达到某种最优目标&#xff0c;如最大化价值或最小化重量等。 运用情况 常用于资源分配、项目选择、货物装载等实际问题中。例如&#xff0c;在选择…

用AI解锁创意设计新思路

在数字化浪潮的推动下&#xff0c;创意设计领域正经历一场由人工智能&#xff08;AI&#xff09;引领的深刻变革。AI技术的崛起不仅显著提升了设计工作的效率&#xff0c;还为设计师们开辟了前所未有的创新空间。 随着AI技术的持续进步&#xff0c;传统的设计流程正在逐步被重…

Lua流媒体服务器支持(MP4视频、桌面直播、摄像头)

本来在做FFMPEG的项目&#xff0c;忽然想到Lua封装FFMPEG与SRS实现一个简易的直播网站何尝不是一个大胆的想法。 示例为初级版本&#xff0c;主要是用来验证可行性和功能性DEMO 演示效果&#xff1a; Lua流媒体直播服务器(支持MP4、桌面直播、摄像头)_哔哩哔哩_bilibili 代码简…

最佳实践 | HelpLook通过PartnerShare实现低成本的市场拓展

在如今许多行业市场竞争非常激烈&#xff0c;扩大品牌影响力、提升产品竞争力成为企业亟待攻克的难题之一。为此&#xff0c;HelpLook AI知识库对接了PartnerShare联盟系统&#xff0c;为SaaS产品如何做好全民分销带来了全新的解决思路。 PartnerShare凭借成熟的推广体系为Hel…

基于Python/MNE处理fnirs数据

功能性近红外光谱技术在脑科学领域被广泛应用&#xff0c;市面上也已经有了许多基于MATLAB的优秀工具包及相关教程&#xff0c;如&#xff1a;homer、nirs_spm等。而本次教程将基于Python的MNE库对fNIRS数据进行处理。 本次教程基于&#xff1a;https://mne.tools/stable/auto_…

宝兰德受邀出席华为开发者大会2024,携手共绘基础软件新篇章

6月21日-23日&#xff0c;华为开发者大会&#xff08;HDC 2024&#xff09;在东莞松山湖举行&#xff0c;作为全球开发者的年度盛会&#xff0c;本次大会汇聚了众多业界精英与前沿技术。华为分享了HarmonyOS、盘古大模型、昇腾AI云服务、GaussDB数据库、自研仓颉编程语言等最新…

一年Java转GO|19K|腾讯 CSIG 一二面经

面经哥只做互联网社招面试经历分享&#xff0c;关注我&#xff0c;每日推送精选面经&#xff0c;面试前&#xff0c;先找面经哥 背景 学历&#xff1a;本科工作经验&#xff1a;一年(不算实习)当前语言&#xff1a;Javabase&#xff1a;武汉部门\岗位&#xff1a;腾讯云‍ 一…

pdf压缩,pdf压缩在线,pdf文件太大怎么变小

在数字化时代&#xff0c;PDF文档因其跨平台、保持原样、易于阅读和打印等特点&#xff0c;成为了我们日常工作和生活中不可或缺的一部分。然而&#xff0c;随着PDF文件的不断累积&#xff0c;存储空间逐渐变得紧张&#xff0c;特别是在处理大量大型PDF文件时&#xff0c;如何有…

深圳大学 软件测试作业 #2

声明&#xff1a;本人上课摆烂选手&#xff0c;稍微听了下&#xff0c;答案仅供参考。 ———————— 1. 考虑下面这个代码&#xff0c;并回答以下的问题。 (a) 请画出上面代码的控制流程图。(20分) (b) 请画出上面代码的数据流程图。(10分) (c) 找出每个变量的定义使…

说点智驾领域的实话!感知|定位|规划控制|就业……

你们有没有一种感觉&#xff0c;近几年自动驾驶技术栈迭代太快&#xff0c;自己稍不留神就与当下主流技术产生脱节了。 其实说实话&#xff0c;并非只有你如此&#xff0c;行业内的工程师都有类似感受。 智能驾驶行业交流群&#xff1a;点击进 分享几个我们最近聊天中的几位朋…

低代码平台如何重塑项目管理:效率与创新的新边界

引言 随着数字化转型的加速和技术创新的推动&#xff0c;低代码开发平台在近年来逐渐崭露头角&#xff0c;成为企业和组织加速应用开发和创新的重要工具。低代码平台通过提供可视化的开发环境和预构建的组件&#xff0c;极大地简化了应用程序的开发过程&#xff0c;使非专业开发…

Vmvare12安装CentOS7.6

Vmvare12安装 注意事项 安装完成以后有这两个虚拟网卡。 CentOS官网镜像地址 https://www.centos.org/download/mirrors/Vmvare安装CentOS7.6 创建虚拟机 安装CentOS7.6 选择桌面版 磁盘分区 上述是确认使用自动分区。 设置密码 设置license information 欢迎页面 CentOS7…