首页 技术 正文
技术 2022年11月20日
0 收藏 512 点赞 4,787 浏览 39751 个字

对@Transactional注解的类进行动态代理

同前文《Spring AOP源码分析》中分析动态代理入口一样,都是在initializeBean时执行。

Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}

初始化时,调用applyBeanPostProcessorsAfterInitialization方法

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
......
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}return wrappedBean;
}

其中processor为AnnotationAwareAspectJAutoProxyCreator时

@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
//AnnotationAwareAspectJAutoProxyCreator
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}

因AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator ,AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator,AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator,调父类AbstractAutoProxyCreator的postProcessAfterInitialization方法

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

调用AbstractAutoProxyCreator#wrapIfNecessary()

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

最后返回specificInterceptors不为null,为何不为null,下一段落具体分析。开始创建Proxy。调用AbstractAutoProxyCreator#createProxy(),set

advisors和targetSource

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
......
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
......
return proxyFactory.getProxy(getProxyClassLoader());
}

ProxyCreatorSupport#createAopProxy() –> DefaultAopProxyFactory#createAopProxy(),判断走JDK动态代理还是CGLIB代理,此时由于代理是类,返回new ObjenesisCglibAopProxy(config)

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
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)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

走CglibAopProxy#getProxy()

@Override
public Object getProxy(@Nullable ClassLoader classLoader) {
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;
......// 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);
}
......
}
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());
}

获取Advisor详细过程

既然specificInterceptors不为null,便从下面开始逆向分析

Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);

this为AnnotationAwareAspectJAutoProxyCreator,但此类并没有getAdvicesAndAdvisorsForBean方法,于是开始找父类,调用AbstractAdvisorAutoProxyCreator#getAdvicesAndAdvisorsForBean()

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

DO_NOT_PROXY为null,既然最后取不为null,所以肯定findEligibleAdvisors不为null。找AbstractAdvisorAutoProxyCreator#findEligibleAdvisors()方法:

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

此时可使用idea的Evaluate Expression ,查看findCandidateAdvisors()的结果,发现事务的Advisor已经在里面,便可先查看AbstractAdvisorAutoProxyCreator#findCandidateAdvisors()

protected List<Advisor> findCandidateAdvisors() {
return this.advisorRetrievalHelper.findAdvisorBeans();
}

调用BeanFactoryAdvisorRetrievalHelper#findAdvisorBeans();此时cachedAdvisorBeanNames字符串数组中有一个值”org.springframework.transaction.config.internalTransactionAdvisor”,直接在IOC容器中根据name获取Bean,添加到advisors中。

public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = this.cachedAdvisorBeanNames;
if (advisorNames == null) {
......
}
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));
}
}
}
}
}

所以先查看cachedAdvisorBeanNames是什么时候被赋值的。而cachedAdvisorBeanNames被赋值只有上面方法advisorNames == null时:

public List<Advisor> findAdvisorBeans() {
if (advisorNames == null) {
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
this.cachedAdvisorBeanNames = advisorNames;
}
}

所以查看BeanFactoryUtils.beanNamesForTypeIncludingAncestors()。

public static String[] beanNamesForTypeIncludingAncestors(
ListableBeanFactory lbf, Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {String[] result = lbf.getBeanNamesForType(type, includeNonSingletons, allowEagerInit);
......
return result;
}

Evaluate知lbf.getBeanNamesForType()得到结果,于是进ListableBeanFactory#getBeanNamesForType()方法

@Override
public String[] getBeanNamesForType(@Nullable Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
if (!isConfigurationFrozen() || type == null || !allowEagerInit) {
return doGetBeanNamesForType(ResolvableType.forRawClass(type), includeNonSingletons, allowEagerInit);
}
}

由于前面allowEagerInit传值为false,!allowEagerInit所以为true。所以调用ListableBeanFactory#doGetBeanNamesForType()根据type获取beanName

  1. 遍历所有BeanDefinition
  2. 将beanName, type, allowFactoryBeanInit入参调用AbstractBeanFactory#isTypeMatch()方法
  3. 满足type(org.springframework.aop.Advisor)的beanName 加入result集合中进行返回
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
List<String> result = new ArrayList<>();for (String beanName : this.beanDefinitionNames) {
if (!isAlias(beanName)) {
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// Only check bean definition if it is complete.
if (!mbd.isAbstract() && (allowEagerInit ||
(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {
//false
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();
boolean matchFound = false;
//false
boolean allowFactoryBeanInit = (allowEagerInit || containsSingleton(beanName));
//false
boolean isNonLazyDecorated = (dbd != null && !mbd.isLazyInit());
if (!isFactoryBean) {
if (includeNonSingletons || isSingleton(beanName, mbd, dbd)) {
matchFound = isTypeMatch(beanName, type, allowFactoryBeanInit);
}
}if (matchFound) {
result.add(beanName);
}
}
}
}
}
......return StringUtils.toStringArray(result);
}

其中isTypeMatch方法执行结果为true

protected boolean isTypeMatch(String name, ResolvableType typeToMatch, boolean allowFactoryBeanInit)
throws NoSuchBeanDefinitionException {// Attempt to get the actual ResolvableType for the bean.
ResolvableType beanType = null;// We don't have an exact type but if bean definition target type or the factory
// method return type matches the predicted type then we can use that.
if (beanType == null) {
ResolvableType definedType = mbd.targetType;
if (definedType == null) {
definedType = mbd.factoryMethodReturnType;
}
if (definedType != null && definedType.resolve() == predictedType) {
beanType = definedType;
}
}// If we have a bean type use it so that generics are considered
if (beanType != null) {
return typeToMatch.isAssignableFrom(beanType);
}
}

BeanFactoryAdvisorRetrievalHelper.cachedAdvisorBeanNames赋值后,回头看AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors()。其中注释也可看出AnnotationAwareAspectJAutoProxyCreator.aspectJAdvisorsBuilder.buildAspectJAdvisors()是获取所有@Aspect 注解里面的前置、后置、环绕等通知,最后包装的advisors

@Override
protected List<Advisor> findCandidateAdvisors() {
// Add all the Spring advisors found according to superclass rules.
List<Advisor> advisors = super.findCandidateAdvisors();
// Build Advisors for all AspectJ aspects in the bean factory.
if (this.aspectJAdvisorsBuilder != null) {
advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
}
return advisors;
}

所以返回AbstractAdvisorAutoProxyCreator#findEligibleAdvisors()中,会进行一次筛选

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

调用AbstractAdvisorAutoProxyCreator#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);
}
}

具体在AopUtils#findAdvisorsThatCanApply(),和AopUtils#canApply()中过滤实现

public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new ArrayList<>();
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) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
}

所以最后顺着理流程是:

  • 在最开始实例化AnnotationAwareAspectJAutoProxyCreator之前,在AbstractAutowireCapableBeanFactory#createBean()中调用在AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation(),最后调用AbstractAutoProxyCreator#postProcessBeforeInstantiation()进行实例化前处理。
  • 而在判断是否需要跳过时(AspectJAwareAdvisorAutoProxyCreator#shouldSkip()),会去找符合条件的Advisor。
  • 找符合条件的Advisor这个过程主要通过传入Advisor.class type,最后在DefaultListableFactory#doGetBeanNamesForType()中,先取出所有BeanDefinition进行遍历,将beanName, type, allowFactoryBeanInit入参调用AbstractBeanFactory#isTypeMatch()方法,满足type(org.springframework.aop.Advisor)的beanName 加入result集合中进行返回。
  • 上面得到的结果赋值给BeanFactoryAdvisorRetrievalHelper.cachedAdvisorBeanNames
  • cachedAdvisorBeanNames不为null便会根据beanName和Advisor.class type从IOC容器中取出Bean,返回findCandidateAdvisors()
  • findCandidateAdvisors()包含了@Aspect注解的Advisors,通过findAdvisorsThatCanApply方法进行过滤出仅事务相关的Advisor

beanName为org.springframework.transaction.config.internalTransactionAdvisor 的Bean具体生成过程

总结:

  • 在ConfigurationClassParser#parse()中会对deferredImportSelectorHandler进行处理(在处理@ComponentScan 自己所写@Component的类后)
  • 处理过程中会调用SpringFactoriesLoader#loadFactoryNames(),去查找所有jar下面META-INF/spring.factories中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
  • ConfigurationClassPostProcessor#processConfigBeanDefinitions()中loadBeanDefinitions对TransactionAutoConfiguration进行BeanDefinition的加载,并注册IOC容器

TransactionAutoConfiguration的BeanDefinition加载解析

TransactionAutoConfiguration的自动配置

调用链:

AbstractApplicationContext#refresh() –> AbstractApplicationContext#invokeBeanFactoryPostProcessors() –> PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors() –> PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors() –> ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry()–> ConfigurationClassPostProcessor#processConfigBeanDefinitions() –> ConfigurationClassPostProcessor#parse() –> ConfigurationClassParser#process() –> ConfigurationClassParser$DeferredImportSelectorGroupingHandler#processGroupImports() –> ConfigurationClassParser#processImports() –> ConfigurationClassParser#processConfigurationClass() –> ConfigurationClassParser#doProcessConfigurationClass()

ConfigurationClassParser#parse() 对延迟ImportSelector进行处理

private final DeferredImportSelectorHandler deferredImportSelectorHandler = new DeferredImportSelectorHandler();public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectorHandler.process();
}

调用DeferredImportSelectorGroupingHandler#processGroupImports()

public void process() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
try {
if (deferredImports != null) {
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 给ConfigurationClassParser.groupings赋值
deferredImports.forEach(handler::register);
// DeferredImportSelectorGroupingHandler处理
handler.processGroupImports();
}
}
finally {
this.deferredImportSelectors = new ArrayList<>();
}

主要handler.processGroupImports()中grouping.getImports()会查找出所有ConfigurationClassParser$DeferredImportSelectorGroupingHandler中的imports

public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
}
}
}

ConfigurationClassParser$DeferredImportSelectorGroupingHandler#getImports()

public Iterable<Group.Entry> getImports() {
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
return this.group.selectImports();
}

会调用AutoConfigurationImportSelector#process()

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}

会调用AutoConfigurationImportSelector#getAutoConfigurationEntry()

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
......
return new AutoConfigurationEntry(configurations, exclusions);
}

其中AutoConfigurationImportSelector#getCandidateConfigurations()会查找所有jar下面META-INF/spring.factories,进行loadFactoryNames。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
return configurations;
}

SpringFactoriesLoader#loadFactoryNames()

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
......
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

SpringFactoriesLoader#loadSpringFactories()

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
}return result;
}

其中spring-boot-autoconfigure-XX.jar下面META-INF/spring.factories文件中就有:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration

附:其中ConfigurationClassParser.groupings赋值,是在ConfigurationClassParser#register()执行的

public void register(DeferredImportSelectorHolder deferredImport) {
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
DeferredImportSelectorGrouping grouping = this.groupings.computeIfAbsent(
(group != null ? group : deferredImport),
key -> new DeferredImportSelectorGrouping(createGroup(group)));
grouping.add(deferredImport);
this.configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());
}
TransactionAutoConfiguration进行@Configuration处理

此过程主要是处理TransactionAutoConfiguration内部类EnableTransactionManagement

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class, Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class)
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration {}@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration {}}}

在ConfigurationClassParser#doProcessConfigurationClass()处理内部类时

protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass, filter);
}
}

调用ConfigurationClassParser#processMemberClasses()时,内部类有两个,本文主要看EnableTransactionManagement类处理逻辑,TransactionTemplateConfiguration进行类似处理

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
Predicate<String> filter) throws IOException {
if (!memberClasses.isEmpty()) {
for (SourceClass candidate : candidates) {
if (this.importStack.contains(configClass)) {
......
}
else {
this.importStack.push(configClass);
try {
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}
}
}

进入内部类EnableTransactionManagementConfiguration的@Configuration的处理逻辑,调用ConfigurationClassParser#processConfigurationClass(),仍走到ConfigurationClassParser#doProcessConfigurationClass()处理

而通过上面EnableTransactionManagementConfiguration的代码可知,它有两个内部类,其中JdkDynamicAutoProxyConfiguration的proxyTargetClass 默认false,CglibAutoProxyConfiguration默认为true,所以默认CGLIB代理

而这里所以仍执行TransactionAutoConfiguration$EnableTransactionManagementConfiguration 的processMemberClasses逻辑

其中看EnableTransactionManagement代码可知, Import了TransactionManagementConfigurationSelector

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
}

所以在ConfigurationClassParser中调用TransactionManagementConfigurationSelector#selectImports()

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {if (importCandidates.isEmpty()) {
return;
}if (checkForCircularImports && isChainedImportOnStack(configClass)) {
......
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {if (selector instanceof DeferredImportSelector) {}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());}
}
}
}
}
}

此时的adviceMode为proxy

public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {AutoProxyRegistrar.class.getName(),
ProxyTransactionManagementConfiguration.class.getName()};
case ASPECTJ:
return new String[] {determineTransactionAspectClass()};
default:
return null;
}
}
}

返回的imports数组中包括org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration

此时再对这两个imports数组进行ConfigurationClassParser#processImports操作

对AbstractTransactionManagementConfiguration进行processConfigurationClass操作。

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}}

会执行doProcessConfigurationClass中@Bean处理逻辑,其中String TRANSACTION_ADVISOR_BEAN_NAME = “org.springframework.transaction.config.internalTransactionAdvisor”;

protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}

于是往ConfigurationClassParser.configurationClasses中进行缓存,在下一步loadBeanDefinitions中使用

private final Map<ConfigurationClass, ConfigurationClass> configurationClasses = new LinkedHashMap<>();protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
this.configurationClasses.put(configClass, configClass);
}
loadBeanDefinitions

this.reader.loadBeanDefinitions(configClasses)进行BeanDefinition的加载,并注册到IOC容器

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
this.reader.loadBeanDefinitions(configClasses);
}
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}

重点在下面ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass()方法。当从configClass.getBeanMethods()可以获取到上一步beanMethod

private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

进行loadBeanDefinitionsForBeanMethod,通过创建ConfigurationClassBeanDefinition,设置一些属性值,最后向IOC容器中进行注册

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
......ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata, beanName);
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));if (metadata.isStatic()) {
......
}
else {
// instance @Bean method
beanDef.setFactoryBeanName(configClass.getBeanName());
beanDef.setUniqueFactoryMethodName(methodName);
}beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);// Replace the original bean definition with the target one, if necessary
BeanDefinition beanDefToRegister = beanDef;
if (proxyMode != ScopedProxyMode.NO) {
BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
new BeanDefinitionHolder(beanDef, beanName), this.registry,
proxyMode == ScopedProxyMode.TARGET_CLASS);
beanDefToRegister = new ConfigurationClassBeanDefinition(
(RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata, beanName);
}this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

调用DefaultListableBeanFactory#registerBeanDefinition()将BeanDefinition进行存储

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
......
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
}
}
}
}
}

拦截过程

调用到@Transactional注解的类或方法时,调用CglibAopProxy$DynamicAdvisedInterceptor#()

public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
TargetSource targetSource = this.advised.getTargetSource();
try {// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
......
}
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);
}
}
}

调用ReflectiveMethodInvocation#process()

public Object proceed() throws Throwable {Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
......
}
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);
}
}

调用TransactionInterceptor#invoke()

public Object invoke(MethodInvocation invocation) throws Throwable {
......
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

调用TransactionAspectSupport#invokeWithinTransaction()

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.
// 如果transaction attribute为空,该方法就是非事务(非编程式事务)
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);......PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 标准声明式事务:如果事务属性为空 或者 非回调偏向的事务管理器
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}commitTransactionAfterReturning(txInfo);
return retVal;
}else {
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
}
catch (Throwable ex) {
.....
}
finally {
cleanupTransactionInfo(txInfo);
}
});
}
.......
return result;
}
}

调用TransactionAspectSupport#createTransactionIfNecessary()判断是否有必要创建事务

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
status = tm.getTransaction(txAttr);
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

具体是否创建事务,传播级别及隔离级别处理在AbstractPlatformTransactionManager#getTransaction()方法中

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
//DataSouce或JPA。mysql会调用DataSourceTransactionManager#doGetTransaction()
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}

获取到transaction后,返回transactionAspectSupport#invokeWithinTransaction()看提交逻辑

先执行环绕增强,可自定义增强功能

invocation.proceedWithInvocation();

如果抛出异常,进行completeTransactionAfterThrowing处理,根据事务定义的,该异常需要回滚就回滚,否则提交事务

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
}
}
}

执行commitTransactionAfterReturning()

commitTransactionAfterReturning(txInfo)
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}

调用AbstractPlatformTransactionManager#commit()

@Override
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}processCommit(defStatus);
}

正常创建事务的执行AbstractPlatformTransactionManager#processCommit()

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}}
finally {
cleanupAfterCompletion(status);
}
}

执行DataSourceTransactionManager#doCommit(),获取jdbc的connection,进行事务命令提交

@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw translateException("JDBC commit", ex);
}
}

事务隔离级别及传播级别定义

public interface TransactionDefinition {// 支持当前事务;如果不存在,创建一个新的。
int PROPAGATION_REQUIRED = 0; //支持当前事务;如果不存在事务,则以非事务方式执行
int PROPAGATION_SUPPORTS = 1;//支持当前事务;如果当前事务不存在,抛出异常。
int PROPAGATION_MANDATORY = 2;//创建一个新事务,如果存在当前事务,则挂起当前事务
int PROPAGATION_REQUIRES_NEW = 3;//不支持当前事务,存在事务挂起当前事务;始终以非事务方式执行
int PROPAGATION_NOT_SUPPORTED = 4;//不支持当前事务;如果当前事务存在,抛出异常。
int PROPAGATION_NEVER = 5;//如果当前事务存在,则在嵌套事务中执行,如果当前没有事务,类似PROPAGATION_REQUIRED(创建一个新的)
int PROPAGATION_NESTED = 6;/**
* Use the default isolation level of the underlying datastore.
* All other levels correspond to the JDBC isolation levels.
* @see java.sql.Connection
*/
int ISOLATION_DEFAULT = -1;//读未提交
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;//读已提交
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;// 可重复读
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;//串行
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;/**
* Use the default timeout of the underlying transaction system,
* or none if timeouts are not supported.
*/
int TIMEOUT_DEFAULT = -1;
}

附:可参考https://www.cnblogs.com/dennyzhangdd/p/9602673.html这篇博文,里面有个时序图不错

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,071
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,549
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,397
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,174
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,809
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,889