Spring AOP源码:代理的创建过程

news2024/9/20 1:12:14

前言

上篇文章讲解了AOP解析工作,将配置文件解析并封装成beanDefinition,由于配置文件中有5个通知方法,before、after、around、after-returning、after-throwing,这里会将其解析成5个advisor通知类。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

		<bean id="accountAdvice" class="service.impl.AccountAdvice" ></bean>
		<bean id="myAccount" class="service.impl.MyAccount" ></bean>

		<aop:config>
			<aop:pointcut id="pointCut" expression="execution(* service.impl.*.*())"/>
			<aop:aspect ref="accountAdvice">
				<aop:after method="after" pointcut-ref="pointCut"></aop:after>
				<aop:before method="before" pointcut-ref="pointCut"></aop:before>
				<aop:around method="around" pointcut-ref="pointCut"></aop:around>
				<aop:after-returning method="afterReturn" pointcut-ref="pointCut"></aop:after-returning>
				<aop:after-throwing method="afterThrow" pointcut-ref="pointCut"></aop:after-throwing>
			</aop:aspect>
		</aop:config>


</beans>

解析后的BeanDefinitions集合信息:

在这里插入图片描述

正文

之前Spring IOC文章讲解过Bean的实例化过程,在实例化Bean之后会进行属性填充(populateBean)、执行初始化方法(initializeBean)。而代理类的生成就在执行初始化方法(initializeBean)中,在该方法中会执行beanPostProcessor方法,如果该bean需要代理,则会进行代理工作。

本章案例中需要被代理的类是myAccount,所以我们来到myAccount实例化过程中的initializeBean方法中;

	protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
		// 如果安全管理器不为空
		if (System.getSecurityManager() != null) {
			// 以特权的方式执行回调bean中的Aware接口方法
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareMethods(beanName, bean);
				return null;
			}, getAccessControlContext());
		}
		else {
			// Aware接口处理器,调用BeanNameAware、BeanClassLoaderAware、beanFactoryAware
			invokeAwareMethods(beanName, bean);
		}

		Object wrappedBean = bean;
		//如果mdb不为null || mbd不是"synthetic"。一般是指只有AOP相关的prointCut配置或者Advice配置才会将 synthetic设置为true
		if (mbd == null || !mbd.isSynthetic()) {
			// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessBeforeInitialization初始化方法。
			// 返回的Bean实例可能是原始Bean包装器
			wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
		}

		try {
			//调用初始化方法,先调用bean的InitializingBean接口方法,后调用bean的自定义初始化方法
			invokeInitMethods(beanName, wrappedBean, mbd);
		}
		catch (Throwable ex) {
			//捕捉调用初始化方法时抛出的异常,重新抛出Bean创建异常:调用初始化方法失败
			throw new BeanCreationException(
					(mbd != null ? mbd.getResourceDescription() : null),
					beanName, "Invocation of init method failed", ex);
		}
		//如果mbd为null || mbd不是"synthetic"
		if (mbd == null || !mbd.isSynthetic()) {
			// 将BeanPostProcessors应用到给定的现有Bean实例,调用它们的postProcessAfterInitialization方法。
			// 返回的Bean实例可能是原始Bean包装器
			wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
		}

		//返回包装后的Bean
		return wrappedBean;
	}

AOP的代理类生成过程在BeanPostProcessor的after方法中调用

applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName),见方法1详解

方法1:postProcessAfterInitialization

进入到AspectJAwareAdivsorAutoProxyCreator类中的postProcessAfterInitialization方法;

	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			// 获取当前bean的key:如果beanName不为空,则以beanName为key,如果为FactoryBean类型则beanName名称前面会加上“&”符号
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			// 判断当前bean是否正在被代理,如果正在被代理则不进行代理操作
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// 如果它需要被代理,则需要封装指定的bean
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

wrapIfNecessary(bean, beanName, cacheKey),见方法2详解

方法2:wrapIfNecessary

	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		// 判断是否已经处理过,处理过则直接返回
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		// 这里advisedBeans缓存了已经进行了代理流程处理的bean,如果缓存中存在,则可以直接返回
		//这里存在两种bean,一种是不需要代理的bean,一种是真正需要代理的,避免下次判断时可以直接返回,不走下面处理逻辑
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		// 这里isInfrastructureClass()用于判断当前bean是否为Spring系统自带的bean,如:Advice、Pointcut、Advisor、AopInfrastructureBean的实现类
		// 不用进行代理的;shouldSkip()则用于判断当前bean是否应该被略过
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			// 对当前bean进行缓存
			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) {
			// 对当前bean的代理状态进行缓存
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			// 根据获取到的Advices和Advisors为当前bean生成代理对象
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			// 缓存生成的代理bean的类型,并且返回生成的代理bean
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

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

shouldSkip(bean.getClass(), beanName),见方法3详解
getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null),见方法4详解
createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)),见方法7详解

方法3:shouldSkip

	protected boolean shouldSkip(Class<?> beanClass, String beanName) {
		// TODO: Consider optimization by caching the list of the aspect names
		//尝试从beanFacotry工厂中获取配置文件中定义好的五个通知方法,它们被封装成了advisor对象,所以这里能获取到5个
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		for (Advisor advisor : candidateAdvisors) {
			//判断通知类中的切面类名称,如果当前bean是切面类,则不应该被代理,会跳过
			if (advisor instanceof AspectJPointcutAdvisor &&
					((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
				return true;
			}
		}
		return super.shouldSkip(beanClass, beanName);
	}

方法4:getAdvicesAndAdvisorsForBean

	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();
	}
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		// 将当前系统中所有的切面类的切面逻辑进行封装,从而得到目标Advisor
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		// 对获取到的所有Advisor进行判断,判断其中的表达式是否能够匹配上要被代理的类
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		// 提供的hook方法,用于对目标Advisor进行扩展
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			// 对需要代理的Advisor按照一定的规则进行排序
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}

findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName),见方法5详解
extendAdvisors(eligibleAdvisors),见方法6详解

方法5:findAdvisorsThatCanApply

	protected List<Advisor> findAdvisorsThatCanApply(
			List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

		ProxyCreationContext.setCurrentProxiedBeanName(beanName);
		try {
			// 从候选的通知器中找到合适正在创建的实例对象的通知器
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		}
		finally {
			ProxyCreationContext.setCurrentProxiedBeanName(null);
		}
	}
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
		// 若候选的增强器集合为空 直接返回
		if (candidateAdvisors.isEmpty()) {
			return candidateAdvisors;
		}
		// 定义一个合适的增强器集合对象
		List<Advisor> eligibleAdvisors = new ArrayList<>();
		// 循环我们候选的通知类对象
		for (Advisor candidate : candidateAdvisors) {
			// 判断我们的通知类对象是不是实现了IntroductionAdvisor 
			if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
				eligibleAdvisors.add(candidate);
			}
		}
		// 判断是否有合适的通知类
		boolean hasIntroductions = !eligibleAdvisors.isEmpty();
		for (Advisor candidate : candidateAdvisors) {
			// 判断我们的通知类对象是不是实现了IntroductionAdvisor
			if (candidate instanceof IntroductionAdvisor) {
				// already processed
				// 在上面已经处理过,不需要处理
				continue;
			}

			// 真正的判断增强器是否合适当前类型,这里会通过切入点表达式进行判断是否适用于当前类
			if (canApply(candidate, clazz, hasIntroductions)) {
				eligibleAdvisors.add(candidate);
			}
		}
		return eligibleAdvisors;
	}

方法6:extendAdvisors

	protected void extendAdvisors(List<Advisor> candidateAdvisors) {
		AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);
	}
public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {
		// Don't add advisors to an empty list; may indicate that proxying is just not required
		if (!advisors.isEmpty()) {
			boolean foundAspectJAdvice = false;
			for (Advisor advisor : advisors) {
				// Be careful not to get the Advice without a guard, as this might eagerly
				// instantiate a non-singleton AspectJ aspect...
				//判断是否是通知类,并将其属性打上标签
				if (isAspectJAdvice(advisor)) {
					foundAspectJAdvice = true;
					break;
				}
			}
			//这里会往advisors缓存中添加ExposeInvocationInterceptor对象,该对象为后续代理类调用过程中的拦截链做准备
			if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {
				advisors.add(0, ExposeInvocationInterceptor.ADVISOR);
				return true;
			}
		}
		return false;
	}

方法7:createProxy

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

		// 给bean定义设置暴露属性
		if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
			AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
		}

		// 创建代理工厂
		ProxyFactory proxyFactory = new ProxyFactory();
		// 获取当前类中相关属性
 		proxyFactory.copyFrom(this);
		// 决定对于给定的bean是否应该使用targetClass而不是他的接口代理,检查proxyTargetClass设置以及preserverTargetClass属性
		if (!proxyFactory.isProxyTargetClass()) {
			// 判断是 使用jdk动态代理 还是cglib代理
			//如果被代理类有preserveTargetClass,则设置ProxyTargetClass为true,尝试使用Cglib进行代理
			if (shouldProxyTargetClass(beanClass, beanName)) {
				proxyFactory.setProxyTargetClass(true);
			}
			else {
				// 判断是否有实现接口,如果没有实现接口则设置setProxyTargetClass为true,尝试使用Cglib进行代理
				evaluateProxyInterfaces(beanClass, proxyFactory);
			}
		}

		// 将被代理类名称及其适配的通知对象构建通知类
		Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
		proxyFactory.addAdvisors(advisors);
		// 设置到要代理的类
		proxyFactory.setTargetSource(targetSource);
		//空方法,用于子类拓展实现
		customizeProxyFactory(proxyFactory);

		// 控制代理工程被配置之后,是否还允许修改通知,默认值是false
		proxyFactory.setFrozen(this.freezeProxy);
		if (advisorsPreFiltered()) {
			proxyFactory.setPreFiltered(true);
		}
		// 真正创建代理对象
		return proxyFactory.getProxy(getProxyClassLoader());
	}

proxyFactory.getProxy(getProxyClassLoader()),见方法8详解

方法8:getProxy

	public Object getProxy(@Nullable ClassLoader classLoader) {
		// createAopProxy() 用来创建我们的代理工厂
		return createAopProxy().getProxy(classLoader);
	}
	protected final synchronized AopProxy createAopProxy() {
		if (!this.active) {
			// 监听调用AdvisedSupportListener实现类的activated方法
			activate();
		}
		// 通过AopProxyFactory获得AopProxy,这个AopProxyFactory是在初始化函数中定义的,使用的是DefaultAopProxyFactory
		return getAopProxyFactory().createAopProxy(this);
	}

getAopProxyFactory().createAopProxy(this),见方法9详解

方法9:createAopProxy

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		// 这段代码用来判断选择哪种创建代理对象的方式
		// config.isOptimize()   是否对代理类的生成使用策略优化 其作用是和isProxyTargetClass是一样的 默认为false
		// config.isProxyTargetClass() 是否使用Cglib的方式创建代理对象 默认为false
		// hasNoUserSuppliedProxyInterfaces目标类是否有接口存在 且只有一个接口的时候接口类型是SpringProxy类型
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			// 上面的三个方法有一个为true的话,则进入到这里
			// 从AdvisedSupport中获取被代理类
			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.");
			}
			// 判断目标类是否是接口 如果目标类是接口的话,则还是使用JDK的方式生成代理对象
			// 如果目标类是Proxy的子类或其实现类时,使用JDK动态代理
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
				return new JdkDynamicAopProxy(config);
			}
			// 配置了使用Cglib进行动态代理或者目标类没有接口,那么使用Cglib的方式创建代理对象
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			// 使用JDK的提供的代理方式生成代理对象
			return new JdkDynamicAopProxy(config);
		}
	}

JDK动态代理根Cglib动态代理在前面的文章中我们讲解过其源码实现过程,可以看这两篇文章进行学习,过程跟Spring AOP过程差不多。
《动态代理:Cglib原理讲解》
《动态代理:JDK动态代理源码学习》

总结

  1. 当切面类指定了proxy-target-class="true"属性时,代表要使用Cglib(proxyTargetClass属性会为true),此时有机会是Cglib代理。
  2. 条件1不满足时,看被代理类是否有preserverTargetClass属性,且值为true时,会将proxyTargetClass属性设置为true,此时有机会是Cglib代理。
  3. optimize为true时,此时有机会是Cglib代理。
  4. 被代理类只有一个接口,并且该接口是SpringProxy类型,此时有机会是Cglib代理。
  5. 被代理类没有实现接口,此时会将proxyTargetClass属性设置为true,此时有机会是Cglib代理。

以上5个条件满足其中1个,且被代理类不是接口或不是Proxy的子类或者实现类时使用Cglib代理。

因此,我们可以得出结论:

  • 被代理类没有接口时不一定使用Cglib进行代理,因为可能被代理类是一个接口或者是Proxy的子类或者实现类
  • 被代理类有接口时不一定使用JDK进行代理:
    • 有可能切面类指定了proxyTargetClass为true
    • 有可能被代理类的只有一个接口且是SpringProxy类型
    • 有可能代理工厂的optimize属性为true

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

opencv——图像阈值设定及常见的滤波操作

1、图像阈值ret,dstcv2.threshold(src,thresh,maxval,type)src:输入图&#xff0c;只能是单通道图&#xff0c;也就是灰度图。thresh:阈值。maxval:当像素超过了阈值&#xff0c;所赋予的值。type:二值化操作的类型&#xff0c;包括binary&#xff0c;binary_iny,trunc,tozero,…

Python编程 闭包

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;b网络豆的主页​​​​​​ 目录 前言 一.闭包 1.什么是闭包 前景引入&#xff1a; 2.闭包的定义需要满足以下…

Redis序列化、乱码问题

Redis序列化问题 每当初学者学习Redis&#xff0c;并且使用SpringBoot整合Redis的时候&#xff0c;总会看到别人使用这种东西—配置类&#xff0c;然后自己又看不懂&#xff0c;oh&#xff0c;fuck&#xff01;&#xff01; 这是为什么&#xff0c;为什么要有这个配置类&…

Pytorch优化器全总结(四)常用优化器性能对比 含代码

目录 写在前面 一、优化器介绍 1.SGDMomentum 2.Adagrad 3.Adadelta 4.RMSprop 5.Adam 6.Adamax 7.AdaW 8.L-BFGS 二、优化器对比 优化器系列文章列表 Pytorch优化器全总结&#xff08;一&#xff09;SGD、ASGD、Rprop、Adagrad Pytorch优化器全总结&#xff08;二…

设计模式学习(七):Factory Method工厂模式

目录 一、什么是Factory Method模式 二、Factory Method示例代码 2.1 类之间的关系 2.2 Product类 2.3 Factory类 2.4 IDCard类 2.5 IDCardFactory类 2.6 用于测试的Main类 2.7 运行结果 三、拓展思路的要点 3.1 框架与具体加工 3.2 使用模式与开发人员之间的沟通 …

(12)go-micro微服务JWT跨域认证

文章目录一 JWT介绍二 JWT优缺点三 JWT使用1. 导包和数据定义2.生成JWT3.解析JWT4.完整代码四 最后一 JWT介绍 JWT 英文名是 Json Web Token &#xff0c;是一种用于通信双方之间传递安全信息的简洁的、URL安全的表述性声明规范&#xff0c;经常用在跨域身份验证。 JWT 以 JS…

拐点检测常用算法总结

目录概览问题定义符号定义研究方法损失函数概览 问题定义 拐点检测名为 change point detection&#xff0c;对于一条不平缓的时间序列曲线&#xff0c;认为存在一些时间点 (t1,t2,...,tk)( t_1 , t_2 , . . . , t_k )(t1​,t2​,...,tk​) &#xff0c;使得曲线在这些点对应…

Java SPI的介绍、JDBC中SPI的应用、自己实现一个SPI应用

目录1. Java SPI介绍2. Java SPI的运行流程3. Java SPI在JDBC中的应用4. Java SPI的三大规范要素5. 自己实现一个SPI应用5.1 Service接口5.2 运营商1的Service Provider5.3 运营商2的Service Provider5.3 手机使用网络1. Java SPI介绍 SPI(Service Provider Interface)是一种基…

在别墅大宅中打造全屋智能,总共需要几步?

关于智能家居&#xff0c;很多读者可能会想起一些不那么愉快的回忆&#xff1a;2014年左右的智能家居浪潮&#xff0c;涌现出了众多带蓝牙互联功能的家电产品&#xff0c;但数据无法互联互通、单品体验升级有限&#xff0c;加上一些企业竞争失败产品不再更新&#xff0c;留给消…

EXCEL工具介绍

目录1. 锁定功能2. 固定1. 锁定功能 锁定&#xff1a;F4 公式引用单元格&#xff0c;有“相对引用”与“绝对引用” 美元符号“ $ ”在excel公式中的作用是在“绝对引用”时&#xff0c;锁定行号或列标&#xff08;单元格地址由列标行号组成&#xff0c;如A1&#xff0c;A为列…

国产软件不惧微软,WPS力扛大旗,新型办公软件争相助力

金山作为程序员的“黄埔军校”&#xff0c;输入了一批批互联网大佬&#xff0c;小米的雷军、哔哩哔哩的陈睿、蓝港互动的王峰等都师出金山。WPS作为金山拳头作品&#xff0c;有着“国民软件”美誉&#xff0c;功能强大&#xff0c;使用体验一点不输微软word&#xff0c;然而有一…

打工人必学的法律知识(三)——《中华人民共和国劳动争议调解仲裁法》

PS &#xff1a; 对与日常工作比较紧密的部分做摘录 中华人民共和国劳动争议调解仲裁法http://www.npc.gov.cn/npc/c198/200712/756d4eceb95e420a87c97545a58d931c.shtml 目录 一、调解 二、仲裁 三、申请和受理 四、开庭和裁决 五、附则 第六条 发生劳动争议&#xff0…

python镜像设置

winr 输入 %USERPROFILE% 新建pip目录&#xff0c;目录内新建pip.ini 输入&#xff1a; [global] index-urlhttp://mirrors.aliyun.com/pypi/simple/ trusted-hostmirrors.aliyun.com

计算机网络概括

1 前言计算机网络是指将位于不同地理位置&#xff0c;但具有独立功能的多台设备&#xff0c;通过通信设备和线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件、网络通信协议的协调管理下&#xff0c;实现资源共享和信息传递的计算机系统。简单来说&#xff0c;…

STM32模拟SPI总线读写RFID模块RC522

STM32模拟SPI总线读写RFID模块RC522 RC522是一款NXP 公司的支持ISO14443A协议的高频&#xff08;13.56MHz&#xff09;RFID射频芯片。RFID有ID和IC两种卡应用类型&#xff0c;RC522属于IC卡的应用类型。NFC则属于增强的IC卡类型&#xff0c;支持双向通信和更多类型的协议。 I…

es官网翻译之Exploring Your Cluster

Exploring Your Cluster 探索你的集群 The rest api rest 风格的 api Now that we have our node (and cluster) up and running, the next step is to understand how to 现在 我们已经将我们自己的节点(和集群) 启动并运行着, 下一个步骤是知道如何 communicate with it…

Java面试题每日10问(12)

1. What is String Pool? String pool is the space reserved in the heap memory that can be used to store the strings.The main advantage of using the String pool is whenever we create a string literal; the JVM checks the “string constant pool” first.If th…

速度为单GPU1.6倍,kaggle双GPU(ddp模式)加速pytorch攻略

accelerate 是huggingface开源的一个方便将pytorch模型迁移到 GPU/multi-GPUs/TPU/fp16 模式下训练的小巧工具。和标准的 pytorch 方法相比&#xff0c;使用accelerate 进行多GPU DDP模式/TPU/fp16 训练你的模型变得非常简单(只需要在标准的pytorch训练代码中改动不几行代码就可…

linux基功系列之man帮助命令实战

文章目录前言一、man命令介绍二、常用参数2.1 语法2.2 常用参数2.3 man页面的操作命令man命令使用案例1. 直接查看手册2. -aw 参数找到可以被查询的章节2.3 一次性查阅所有章节2.4 搜索手册页2.5 -L 设置查询语言总结前言 linux系统中的命令数量有上千的&#xff0c;即使是常用…

前端——周总结系列二

1 JS数组排序sort()方法 不传参数排序&#xff0c;默认根据Unicode排序 附录 传参数&#xff0c;使用比较函数&#xff0c;自己定义比较规则 简单数组排序 // 升序 function ascSort(a, b) {return a - b; } // 降序 function ascSort(a, b) {return b - a; }数组对象排序…