Spring源码 – bean创建的生命周期之实例化-createBeanInstance(Spring Framework 5.3.7-SNAPSHO)
2023-02-26 10:18:17 时间
构造函数的选取逻辑
- 在doCreateBean中给属性赋值之前,调用createBeanInstance
// Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { // factoryBeanInstanceCache存储的是:beanName对应的FactoryBean实例对象 instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 根据构造函数实例化,创建Bean实例 instanceWrapper = createBeanInstance(beanName, mbd, args); }
- 进入createBeanInstance,整体逻辑的描述如源码中注释,构造函数的返回主要是通过自动注入autowireConstructor和使用默认构造函数instantiateBean,重点关注determineConstructorsFromBeanPostProcessors,它里面的逻辑主要是调用SmartInstantiationAwareBeanPostProcessor类型的后置处理器,determineCandidateConstructors方法返回候选的构造器集合,源码接着下一步
// Make sure bean class is actually resolved at this point. Class<?> beanClass = resolveBeanClass(mbd, beanName); if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean class isn't public, and non-public access not allowed: " + beanClass.getName()); } // Spring5新增的判断逻辑 Supplier<?> instanceSupplier = mbd.getInstanceSupplier(); if (instanceSupplier != null) { return obtainFromSupplier(instanceSupplier, beanName); } if (mbd.getFactoryMethodName() != null) { return instantiateUsingFactoryMethod(beanName, mbd, args); } // Shortcut when re-creating the same bean... // 解析构造函数,根据参数和类型判断使用哪个构造函数进行实例化 boolean resolved = false; boolean autowireNecessary = false; if (args == null) { synchronized (mbd.constructorArgumentLock) { if (mbd.resolvedConstructorOrFactoryMethod != null) { resolved = true; autowireNecessary = mbd.constructorArgumentsResolved; } } } // 如果已经解析,则直接从从缓存中获取 if (resolved) { if (autowireNecessary) { // 构造函数自动注入 return autowireConstructor(beanName, mbd, null, null); } else { // 使用默认构造函数构造 return instantiateBean(beanName, mbd); } } // Candidate constructors for autowiring? // 根据参数解析构造函数 Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // Spring5新增 // Preferred constructors for default construction? ctors = mbd.getPreferredConstructors(); if (ctors != null) { return autowireConstructor(beanName, mbd, ctors, null); } // No special handling: simply use no-arg constructor. // 使用默认构造函数构造 return instantiateBean(beanName, mbd);
- determineConstructorsFromBeanPostProcessors的源码
if (beanClass != null && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { Constructor<?>[] ctors = bp.determineCandidateConstructors(beanClass, beanName); if (ctors != null) { return ctors; } } } return null;
- AutowiredAnnotationBeanPostProcessor的determineCandidateConstructors方法,分为两步:[email protected];二、执行真正的构造器选择逻辑
// Let's check for lookup methods here... if (!this.lookupMethodsChecked.contains(beanName)) { if (AnnotationUtils.isCandidateClass(beanClass, Lookup.class)) { try { Class<?> targetClass = beanClass; do { ReflectionUtils.doWithLocalMethods(targetClass, method -> { Lookup lookup = method.getAnnotation(Lookup.class); if (lookup != null) { Assert.state(this.beanFactory != null, "No BeanFactory available"); LookupOverride override = new LookupOverride(method, lookup.value()); try { RootBeanDefinition mbd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName); mbd.getMethodOverrides().addOverride(override); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(beanName, "Cannot apply @Lookup to beans without corresponding bean definition"); } } }); targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); } catch (IllegalStateException ex) { throw new BeanCreationException(beanName, "Lookup method resolution failed", ex); } } this.lookupMethodsChecked.add(beanName); } // Quick check on the concurrent map first, with minimal locking. Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass); if (candidateConstructors == null) { // Fully synchronized resolution now... synchronized (this.candidateConstructorsCache) { candidateConstructors = this.candidateConstructorsCache.get(beanClass); // 双重检查,避免多线程的并发问题 if (candidateConstructors == null) { Constructor<?>[] rawCandidates; try { // 获取所有声明的构造器 rawCandidates = beanClass.getDeclaredConstructors(); } catch (Throwable ex) { throw new BeanCreationException(beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } // 最终适用的构造器集合 List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length); // 存放required=true的构造器 Constructor<?> requiredConstructor = null; // 存放默认的构造器 Constructor<?> defaultConstructor = null; Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass); int nonSyntheticConstructors = 0; for (Constructor<?> candidate : rawCandidates) { if (!candidate.isSynthetic()) { nonSyntheticConstructors++; } else if (primaryConstructor != null) { continue; } // 查找当前构造器上面的注解 MergedAnnotation<?> ann = findAutowiredAnnotation(candidate); // 没有注解 if (ann == null) { Class<?> userClass = ClassUtils.getUserClass(beanClass); if (userClass != beanClass) { try { Constructor<?> superCtor = userClass.getDeclaredConstructor(candidate.getParameterTypes()); ann = findAutowiredAnnotation(superCtor); } catch (NoSuchMethodException ex) { // Simply proceed, no equivalent superclass constructor found... } } } // 有注解 if (ann != null) { if (requiredConstructor != null) { // 说明已经存在required=true的构造器,抛异常 throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); } // 判断注解的属性required的值 boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found constructor with 'required' Autowired annotation: " + candidate); } // 把当前构造器存入requiredConstructor requiredConstructor = candidate; } candidates.add(candidate); } else if (candidate.getParameterCount() == 0) { // 如果当前构造器上面没有注解,并且参数个数为0,则存入默认构造器defaultConstructor defaultConstructor = candidate; } } if (!candidates.isEmpty()) { // Add default constructor to list of optional constructors, as fallback. // 没有required=true的构造器 if (requiredConstructor == null) { // 默认构造器不为空,则存入最终适用的构造器 if (defaultConstructor != null) { candidates.add(defaultConstructor); } else if (candidates.size() == 1 && logger.isInfoEnabled()) { logger.info("Inconsistent constructor declaration on bean with name '" + beanName + "': single autowire-marked constructor flagged as optional - " + "this constructor is effectively required since there is no " + "default constructor to fall back to: " + candidates.get(0)); } } candidateConstructors = candidates.toArray(new Constructor<?>[0]); } else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) { // 如果适用的构造器只有1个,并且参数的个数大于0,则直接选用该构造器 candidateConstructors = new Constructor<?>[] {rawCandidates[0]}; } else if (nonSyntheticConstructors == 2 && primaryConstructor != null && defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) { // 如果有2个构造器,并且默认的构造器不为空 candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor}; } else if (nonSyntheticConstructors == 1 && primaryConstructor != null) { candidateConstructors = new Constructor<?>[] {primaryConstructor}; } else { // 上面都不符合,返回空 candidateConstructors = new Constructor<?>[0]; } // 存入缓存,方便下一次获取 this.candidateConstructorsCache.put(beanClass, candidateConstructors); } } } // 结果不为空则直接返回,否则返回null return (candidateConstructors.length > 0 ? candidateConstructors : null);
-
对上述第二步的流程做一个说明,
- 首先对候选的构造器结果candidateConstructors作双重检查,以防多线程并发带来的问题;
- 接着通过Class<?>反射获取所有声明的构造函数
-
对上述通过反射获取的构造函数循环遍历
(福利推荐:阿里云、腾讯云、华为云服务器最新限时优惠活动,云服务器1核2G仅88元/年、2核4G仅698元/3年,点击这里立即抢购>>>)
- 对于每个构造器,[email protected],@Value,javax.inject.Inject等注解
- 如果当前构造器上面没有注解,并且参数个数为0,则存入默认构造器defaultConstructor
- 如果有注解,并且存放required=true的构造器不为空,则抛异常;如果为空,判断注解的属性required的值,把当前构造器存入requiredConstructor
- 结果判断:没有required=true的构造器,并且默认构造器不为空,则存入最终适用的构造器
- 如果适用的构造器只有1个,并且参数的个数大于0,则直接选用该构造器
- 如果有2个构造器,并且默认的构造器不为空,也存入最终适用的构造器
- 如果构造器只有一个,并且primaryConstructor不为空,则直接存入结果构造器集合
- 以上都不符合,则返回空,在推出该函数后,如果为空,则系统最后会采用默认的构造函数
- 把结果存入缓存,便于下一次直接获取
- 举例说明:
- [email protected],且required默认,即:值为true,则Spring会采用被注解标注的那一个
@Autowired public Color(Blue blue, Cyan cyan) { this.blue = blue; this.cyan = cyan; } public Color(Cyan cyan) { this.cyan = cyan; } // 默认构造函数 public Color() {}
- [email protected],两个required都是true,Spring会抛异常
// ----------------------------示例代码---------------------------- @Autowired public Color(Blue blue, Cyan cyan) { this.blue = blue; this.cyan = cyan; } @Autowired public Color(Cyan cyan) { this.cyan = cyan; } // 默认构造函数 public Color() {} }
- [email protected],一个required是true,一个required是false
- 情形(一),抛异常:“Found constructor with ‘required’ Autowired annotation already XXX”
// ----------------------------示例代码---------------------------- @Autowired(required = false) public Color(Blue blue, Cyan cyan) { this.blue = blue; this.cyan = cyan; } @Autowired public Color(Cyan cyan) { this.cyan = cyan; } // 默认构造函数 public Color() {}
- 情形(二),也会抛异常:“Found constructor with ‘required’ Autowired annotation:”
// ----------------------------示例代码---------------------------- @Autowired public Color(Blue blue, Cyan cyan) { this.blue = blue; this.cyan = cyan; } @Autowired(required = false) public Color(Cyan cyan) { this.cyan = cyan; } // 默认构造函数 public Color() {}
- [email protected],两个required都是false,Spring经过处理之后会根据选取的原则选择一个构造函数,结果candidates会存入下面示例代码中所有的构造函数,返回出去之后,外层函数会调用autowireConstructor,选择一个构造函数,源码分析如下:
// ----------------------------示例代码---------------------------- @Autowired(required = false) public Color(Blue blue, Cyan cyan) { this.blue = blue; this.cyan = cyan; } @Autowired(required = false) public Color(Cyan cyan) { this.cyan = cyan; } // 默认构造函数 public Color() {} // ---------------上述情形的源码判断逻辑--------------- if (ann != null) { if (requiredConstructor != null) { // 说明已经存在required=true的构造器,抛异常 throw new BeanCreationException(beanName, "Invalid autowire-marked constructor: " + candidate + ". Found constructor with 'required' Autowired annotation already: " + requiredConstructor); } // 判断注解的属性required的值 boolean required = determineRequiredStatus(ann); if (required) { if (!candidates.isEmpty()) { throw new BeanCreationException(beanName, "Invalid autowire-marked constructors: " + candidates + ". Found constructor with 'required' Autowired annotation: " + candidate); } // 把当前构造器存入requiredConstructor requiredConstructor = candidate; } candidates.add(candidate); }
- AbstractAutowireCapableBeanFactory的autowireConstructor源码
new ConstructorResolver(this).autowireConstructor(beanName, mbd, ctors, explicitArgs)
- ConstructResolver的autowireConstructor方法,流程比较长,总的逻辑是:先选取待使用的构造函数(选取的原则是:构造函数排序,public优先且按参数数量降序,非public按参数数量降序),把构造的实例加入BeanWrapper中,根据实例化策略、构造函数以及构造函数参数实例化Bean(实例化逻辑调用在instantiate方法,源码接着后面。)
BeanWrapperImpl bw = new BeanWrapperImpl(); this.beanFactory.initBeanWrapper(bw); Constructor<?> constructorToUse = null; ArgumentsHolder argsHolderToUse = null; Object[] argsToUse = null; // explicitArgs通过getBean方法传入,如果不为空,则直接使用 if (explicitArgs != null) { argsToUse = explicitArgs; } else { // 如果在getBean方法没有指定,则尝试从配置文件中解析 Object[] argsToResolve = null; synchronized (mbd.constructorArgumentLock) { constructorToUse = (Constructor<?>) mbd.resolvedConstructorOrFactoryMethod; if (constructorToUse != null && mbd.constructorArgumentsResolved) { // Found a cached constructor... // 从缓存中获取 argsToUse = mbd.resolvedConstructorArguments; if (argsToUse == null) { argsToResolve = mbd.preparedConstructorArguments; } } } // 如果缓存中存在 if (argsToResolve != null) { // 解析参数类型,如:给定构造函数是(int, int),通过此方法后("1", "1")会被解析为(1, 1) argsToUse = resolvePreparedArguments(beanName, mbd, bw, constructorToUse, argsToResolve); } } // 没有被缓存 if (constructorToUse == null || argsToUse == null) { // Spring5更改逻辑顺序,Spring4此段逻辑在[2.]之后 // 1.Take specified constructors, if any. Constructor<?>[] candidates = chosenCtors; if (candidates == null) { Class<?> beanClass = mbd.getBeanClass(); try { candidates = (mbd.isNonPublicAccessAllowed() ? beanClass.getDeclaredConstructors() : beanClass.getConstructors()); } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Resolution of declared constructors on bean Class [" + beanClass.getName() + "] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex); } } if (candidates.length == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) { Constructor<?> uniqueCandidate = candidates[0]; if (uniqueCandidate.getParameterCount() == 0) { synchronized (mbd.constructorArgumentLock) { mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate; mbd.constructorArgumentsResolved = true; mbd.resolvedConstructorArguments = EMPTY_ARGS; } bw.setBeanInstance(instantiate(beanName, mbd, uniqueCandidate, EMPTY_ARGS)); return bw; } } // 2. Need to resolve the constructor. boolean autowiring = (chosenCtors != null || mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); ConstructorArgumentValues resolvedValues = null; int minNrOfArgs; if (explicitArgs != null) { minNrOfArgs = explicitArgs.length; } else { // 提取配置文件中配置的构造函数参数 ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues(); // 用于承接解析后的构造函数参数值 resolvedValues = new ConstructorArgumentValues(); // 能解析到的构造函数参数个数 minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues); } /** * 根据构造函数参数锁定对应的构造函数,根据参数个数匹配,在匹配之前需要完成排序 */ // 构造函数排序,public优先且按参数数量降序,非public按参数数量降序 AutowireUtils.sortConstructors(candidates); int minTypeDiffWeight = Integer.MAX_VALUE; Set<Constructor<?>> ambiguousConstructors = null; Deque<UnsatisfiedDependencyException> causes = null; for (Constructor<?> candidate : candidates) { int parameterCount = candidate.getParameterCount(); // 由于构造函数已经按照规则排过序,当已经找到了满足该条件的一个构造函数,会进入到此逻辑 if (constructorToUse != null && argsToUse != null && argsToUse.length > parameterCount) { // Already found greedy constructor that can be satisfied -> // do not look any further, there are only less greedy constructors left. break; } if (parameterCount < minNrOfArgs) { continue; } ArgumentsHolder argsHolder; Class<?>[] paramTypes = candidate.getParameterTypes(); // 配置文件中配置构造函数:1.通过指定参数位置索引创建;2.指定参数名称设定参数值 if (resolvedValues != null) { try { // 从注解中获取参数名称 String[] paramNames = ConstructorPropertiesChecker.evaluate(candidate, parameterCount); if (paramNames == null) { ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer(); if (pnd != null) { paramNames = pnd.getParameterNames(candidate); } } // 根据参数名称和类型创建ArgumentsHolder argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw, paramTypes, paramNames, getUserDeclaredConstructor(candidate), autowiring, candidates.length == 1); } catch (UnsatisfiedDependencyException ex) { if (logger.isTraceEnabled()) { logger.trace("Ignoring constructor [" + candidate + "] of bean '" + beanName + "': " + ex); } // Swallow and try next constructor. if (causes == null) { causes = new ArrayDeque<>(1); } causes.add(ex); continue; } } else { // Explicit arguments given -> arguments length must match exactly. if (parameterCount != explicitArgs.length) { continue; } argsHolder = new ArgumentsHolder(explicitArgs); } // 探测是否有不确定性的构造函数存在,如:不同构造函数的参数为父子关系 int typeDiffWeight = (mbd.isLenientConstructorResolution() ? argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes)); // Choose this constructor if it represents the closest match. // 选择当前最接近的匹配作为构造函数 if (typeDiffWeight < minTypeDiffWeight) { constructorToUse = candidate; argsHolderToUse = argsHolder; argsToUse = argsHolder.arguments; minTypeDiffWeight = typeDiffWeight; ambiguousConstructors = null; } else if (constructorToUse != null && typeDiffWeight == minTypeDiffWeight) { if (ambiguousConstructors == null) { ambiguousConstructors = new LinkedHashSet<>(); ambiguousConstructors.add(constructorToUse); } ambiguousConstructors.add(candidate); } } if (constructorToUse == null) { if (causes != null) { UnsatisfiedDependencyException ex = causes.removeLast(); for (Exception cause : causes) { this.beanFactory.onSuppressedException(cause); } throw ex; } throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Could not resolve matching constructor " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)"); } else if (ambiguousConstructors != null && !mbd.isLenientConstructorResolution()) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Ambiguous constructor matches found in bean '" + beanName + "' " + "(hint: specify index/type/name arguments for simple parameters to avoid type ambiguities): " + ambiguousConstructors); } if (explicitArgs == null && argsHolderToUse != null) { // 把解析的构造函数加入缓存 argsHolderToUse.storeCache(mbd, constructorToUse); } } Assert.state(argsToUse != null, "Unresolved constructor arguments"); // 把构造的实例加入BeanWrapper中:根据实例化策略、构造函数以及构造函数参数实例化Bean bw.setBeanInstance(instantiate(beanName, mbd, constructorToUse, argsToUse)); return bw;
- ConstructResolver的instantiate
try { InstantiationStrategy strategy = this.beanFactory.getInstantiationStrategy(); if (System.getSecurityManager() != null) { return AccessController.doPrivileged((PrivilegedAction<Object>) () -> strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse), this.beanFactory.getAccessControlContext()); } else { // 策略默认是SimpleInstantiationStrategy return strategy.instantiate(mbd, beanName, this.beanFactory, constructorToUse, argsToUse); } } catch (Throwable ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Bean instantiation via constructor failed", ex); }
- SimpleInstantiationStrategy的instantiate
if (!bd.hasMethodOverrides()) { if (System.getSecurityManager() != null) { // use own privileged to change accessibility (when security is on) AccessController.doPrivileged((PrivilegedAction<Object>) () -> { ReflectionUtils.makeAccessible(ctor); return null; }); } return BeanUtils.instantiateClass(ctor, args); } else { return instantiateWithMethodInjection(bd, beanName, owner, ctor, args); }
- BeanUtils#instantiateClass,实例化class的最后一步,先对基本数据类型转换为默认值,最后通过反射实例化
Assert.notNull(ctor, "Constructor must not be null"); try { ReflectionUtils.makeAccessible(ctor); if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) { return KotlinDelegate.instantiateClass(ctor, args); } else { Class<?>[] parameterTypes = ctor.getParameterTypes(); Assert.isTrue(args.length <= parameterTypes.length, "Can't specify more arguments than constructor parameters"); Object[] argsWithDefaultValues = new Object[args.length]; for (int i = 0 ; i < args.length; i++) { if (args[i] == null) { Class<?> parameterType = parameterTypes[i]; argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null); } else { argsWithDefaultValues[i] = args[i]; } } return ctor.newInstance(argsWithDefaultValues); } } catch (InstantiationException ex) { throw new BeanInstantiationException(ctor, "Is it an abstract class?", ex); } catch (IllegalAccessException ex) { throw new BeanInstantiationException(ctor, "Is the constructor accessible?", ex); } catch (IllegalArgumentException ex) { throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", ex); } catch (InvocationTargetException ex) { throw new BeanInstantiationException(ctor, "Constructor threw exception", ex.getTargetException()); }
你还在原价购买阿里云、腾讯云、华为云、天翼云产品?那就亏大啦!现在申请成为四大品牌云厂商VIP用户,可以3折优惠价购买云服务器等云产品,并且可享四大云服务商产品终身VIP优惠价,还等什么?赶紧点击下面对应链接免费申请VIP客户吧:
相关文章
- Jgit的使用笔记
- 利用Github Action实现Tornadofx/JavaFx打包
- 叹息!GitHub Trending 即将成为历史!
- 微软软了?开源社区讨论炸锅,GitHub CEO 亲自来答
- GitHub Trending 列表频现重复项,前后端都没去重?
- Photoshop Elements 2021版本软件安装教程(mac+windows全版本都有)
- (ps全版本)Photoshop 2020的安装与破解教程(mac+windows全版本都有)
- (ps全版本)Photoshop cc2018的安装与破解教程(mac+windows全版本,包括2023
- 环境搭建:Oracle GoldenGate 大数据迁移到 Redshift/Flat file/Flume/Kafka测试流程
- 每个开发人员都要掌握的:最小 Linux 基础课
- 来撸羊毛了!Windows 环境下 Hexo 博客搭建,并部署到 GitHub Pages
- 超实用!手把手入门 MongoDB:这些坑点请一定远离
- 【GitHub日报】22-10-09 zustand、neovim、webtorrent、express 等4款App今日上新
- 【GitHub日报】22-10-10 brew、minio、vite、seaweedfs、dbeaver 等8款App今日上新
- 【GitHub日报】22-10-11 cobra、grafana、vue、ToolJet、redwood 等13款App今日上新
- Photoshop 2018 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2017 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2020 下载及安装教程(mac+windows全版本都有,包括最新的2023)
- Photoshop 2023 资源免费下载(mac+windows全版本都有,包括最新的2023)
- 最新版本Photoshop CC2018软件安装教程(mac+windows全版本都有,包括2023