浅浅读了一些spring2.0的源码和一些分析资料。其中jiwenke写的对spring的分析文章对我的帮助很大,这几天jiwenke又计划写一些更深入的spring分析文章了,十分的期待,对spring感兴趣的朋友不妨看一下http://jiwenke.iteye.com/的博客,在这里先期待jiwenke的好文了。
程序最迷人的地方有两个,一是算法,一是设计。
算法的迷人之处在于,他可以让你几天几夜睡不好觉,然后时不时有种想抱着电脑在那大喊:“我真是个天才!!天才!”的冲动,之后平静下来,再仔细
想想,好像问题又不是那么回事,然后你又接着想…又喊…然后又觉的不够好…青春就在这样的一惊一乍中华丽的溜走了~
不过还好,之后你突然发现自己的肺活量提升了一个排量…
设计不太好说,不同于算法,谁优谁劣,一比便知。小生入行不久,才疏学浅。在这里写下自己对于spring的一些看法和理解。希望各位牛人能够指教,小生感激不尽。水平有限,有不对的地方希望各位能够指正。
正文:
一个类一个责任(SRP:The Single-Responsibility Principle – A class should have only one reason to change)
There should never be more than one reason for a class to change.
永远不要让一个类存在多个改变的理由
唯有站在设计者的角度出发,才能明白他的做法。假设自己是spring的设计者,我们想写一个能够提供,管理各种bean的容器。这个容器可以创
建,管理,控制各种已在容器中注册的bean。但是每个bean的生命周期又不太一样,比如有些是prototye的,这种要求每次容器产生的一个新的
bean.有些是singleton要求是唯一的,有些是在容器与web程序交互时产生,要求是request ,session,
global。还有一些bean本身是一个工厂,他要求能够调用工厂的方法,返回工厂的产品。
假设一开始的时候,我们只是为了罗列功能来设计这样一个接口
-
public interface IOC {
-
-
//首先对于一些单例的bean我们需要去保存它,比如把它放入一个map,在下次请求时直接返回该bean
-
//所以就有了registerSingleton,getSingleton,removeSingleton这几个方法
-
void registerSingleton(String beanName, Object singletonObject);
-
Object getSingleton(String beanName);
-
void removeSingleton(String beanName);
-
-
-
//我们需要得到一个bean,这时要做个判断,如果它是一个单例则调用上面的getSingleton方法,其它类型的bean则创建它
-
Object getBean(String name);
-
//我们希望这个容器可以有一个父容器,如果该容器有父容器,那么先查找父容器中的bean
-
BeanFactory getParentBeanFactory();
-
//如果这个bean是一个工厂的话,那么我们需要一个从工厂得到产品的方法
-
Object getObjectFromFactoryBean(FactoryBean factory, String beanName, RootBeanDefinition mbd);
-
//最后,注册销毁这个bean时需要执行的回调方法
-
void destroyBean(String beanName, Object beanInstance);
-
-
-
//一个创建的方法,我们可以用从配置文件中解析出的bean定义创建这个bean。
-
//这个接口不能是对外的,bean的产生由容器来负责,其它组件做的只是问容器要bean就可以了
-
Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck);
-
//在这个创建过程中,需要给bean注入一些属性
-
Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck);
-
//可能这个bean需要一个切面,所以需要对它进行动态代理,但切面可能不止一个,我们定义很多BeanPostProcessor处理器来完成,最后返回一个最终的代理类
-
Object applyBeanPostProcessors(Object existingBean, String beanName);
-
-
-
//注册和得到bean的定义
-
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
-
BeanDefinition getBeanDefinition(String beanName);
-
-
-
//最后一个读取资源并解析,然后注册到容器中的方法
-
void loadBeanDefinitions(Resource resource);
-
}
这里有非常多的问题,首先这是一个非常胖的接口,致使实现类肩负了太多的责任!其中最明显的当属
-
void loadBeanDefinitions(Resource resource);
这个方法了,对于资源的读取,读取位置,方式,之后的xml解析,验证…一系列的操作,而且当读取,解析步骤发生变动修改时,这个类也要随之一起
修改。在这里,它的责任早就超过了容器的管理范围(容器的职责是对bean的管理,创建,等等),很显然应该把它取出。资源的读取由专门的资源读取组件完
成,容器组件只负责对bean的创建,管理,销毁。
可是这样还是不太好,虽然已经把一部分不属于容器的责任移了出去,但是这里容器还是承担着非常多的责任,其中的主要功能确可以划分为4个方面:
1 对单例bean的管理,注册,获取,销毁,销毁其依赖类;
2 得到bean,根据bean的作用范围,返回不同类型的bean,工厂则返回产品
3 根据bean定义生产一个bean,对其进行属性的注入,编辑,切面的生成,各种处理器的实现
4 注册bean的定义,查询
很显然,IOC这个接口太大了,我们需要将其进行拆分,将其中接口分为不同的地方
-
//对单例bean的管理,注册,获取,销毁,销毁其依赖类;
-
interface SingletonBeanRegistry {
-
void registerSingleton(String beanName, Object singletonObject);
-
Object getSingleton(String beanName);
-
void removeSingleton(String beanName);
-
}
-
-
//得到bean,根据bean的作用范围,返回不同类型的bean,工厂则返回产品
-
interface ConfigurableBeanFactory{
-
Object getBean(String name);
-
BeanFactory getParentBeanFactory();
-
Object getObjectFromFactoryBean(FactoryBean factory, String beanName, RootBeanDefinition mbd);
-
void destroyBean(String beanName, Object beanInstance);
-
}
-
-
//根据bean定义生产一个bean,对其进行属性的注入,切面的生成,各种处理器的实现
-
interface AutowireCapableBeanFactory{
-
Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck);
-
Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck);
-
Object applyBeanPostProcessors(Object existingBean, String beanName);
-
}
-
-
//注册bean的定义,查询
-
interface BeanDefinitionRegistry {
-
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
-
BeanDefinition getBeanDefinition(String beanName);
-
}
接口已经拆分开了,可然后怎么办呢?难道要像刚才开始拆分BeanDefineReader那样?
图1-1
可是这里每个类还需要与其它的类交互,
比如ConfigurableBeanFactory当调用getBean方法时需要先查询SingletonBeanRegistry中是否存
在这个单例bean,它需要调用SingletonBeanRegistry中的一系列的方法。而单例bean并不存在时又要调用
AutowireCapableBeanFactory的createBean方法来创建,如果是工厂类的话,还要到
BeanDefinitionRegistry中查询bean的定义
AutowireCapableBeanFactory当调用createBean方法时需要调用ConfigurableBeanFactory的getBean来查询它的依赖类在哪里,同样它在创建时也需要查询bean的定义。
而BeanDefinitionRegistry中还有一系列的方法需要其它几个类的帮助。
难道这么干??
图1-2
这简直就是一场灾难…
我们发现这个类如果要实现所有的接口,那么它实在是太笨重了,创建,获取,注册定义,管理,任何一个地方发生了变动,我们都需要修改这个类,而对
一个4000来行的程序进行修改,其中还包含了大量的重载代码,这会给维护带来很大的麻烦。因为你无法确定,你修改的这个方法到底有多少方法又在调用它。
而分离的话却让代码变的非常的混乱,复用性也低的可怜。
有时候一个类会有多个职责,这不是我们希望的,但有时这又是必须的。通常由于某些原因,迫使我们不得不绑定多个职责到一个类中,但我们至少可以通过接口的分割来分离应用程序关心的概念。
在spring中使用了继承的方式,逐层实现了容器的功能,每层都完成特定的任务,如果需要的任务属于子类的职责,则由抽象的父类提供抽象方法,由子类来完成。
来看springIOC容器图(点一下,全屏就大了)
图1-3
其中BeanFactory描述出容器的最基本功能
-
public interface BeanFactory {
-
-
String FACTORY_BEAN_PREFIX = "&";
-
-
Object getBean(String name) throws BeansException;
-
-
Object getBean(String name, Class requiredType) throws BeansException;
-
-
boolean containsBean(String name);
-
-
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
-
-
Class getType(String name) throws NoSuchBeanDefinitionException;
-
-
String[] getAliases(String name);
-
-
}
在第一次读完这4个类的源码的时候,我很简单的认为,会把它们分开是因为重构的关系。因为如果一开始拿到这个问题,可能最容易想到的就是先描述容
器的功能,比如像最高的接口BeanFactory描述出了容器的最基本功能。之后细看问题,可能会觉的要实现一个单例bean的管理接口,一个父子容器
的管理接口。这样就有了SingletonBeanRegistry,HierarchicalBeanFactory。但是接下来的实现好像写一起也可
以,就是非常的大。所以那时很简单的认为这里是在具体做的过程中发现实现的功能以及代码量都非常的大才将这几个类分离开的。可后来想想,这里应该是个考验
设计者能力的地方:即在问题展开之前,对于问题复杂度的预见和把握的能力。
来看几段具体的实现:
AbstractBeanFactory中的getBean方法是从容器获得bean的具体实现:
1
首先查询DefaultSingletonBeanRegistry中是否存在当前的bean,如果有则直接返回,如果是工厂bean则先得到bean在
生成产品类,由于AbstractBeanFactory继承自DefaultSingletonBeanRegistry这个实现非常的简单。
命令模式-Command
ps:这个世界上最简单,最实用的设计模式吧:)
图1-4
定义:Command模式是行为模式之一,Command模式通过被称为Command的类封装了对目标对象的调用行为以及调用参数
在查询完父类DefaultSingletonBeanRegistry后,接下来得到bean定义来判断bean的类型,然后生成bean。
-
if (当前bean定义是Singleton类型) {
-
sharedInstance = getSingleton(beanName, new ObjectFactory() {
-
public Object getObject() throws BeansException {
-
try {
-
return createBean(beanName, mergedBeanDefinition, args);
-
}
-
catch (BeansException ex) {
-
destroySingleton(beanName);
-
throw ex;
-
}
-
}
-
});
-
bean = getObjectForBeanInstance(sharedInstance, name, mergedBeanDefinition);
-
}
在这里AbstractBeanFactory留下了createBean(beanName, mergedBeanDefinition, args);这样一个抽象方法,由其子类来实现。
这里有ObjectFactory这样一个接口,通过写一个匿名类,实现它的getObject方法,来实现具体的过
程,getSingleton方法来自父类,参数为beanName,ObjectFactory进入该方法后,会调用ObjectFactory.
getObject()这个方法,之后会对bean进行其它操作,比如再放入map中,最后再将这个生成类返回。那么为什么要用这个
ObjectFactory呢?实际这是命令模式的一种方式,只不过这个命令是负责产生一个类。
-
public interface ObjectFactory {
-
Object getObject() throws BeansException;
-
}
跳过一个Prototype的判断,直接看它当为最后类型时的判断,比如request或是session
-
//从bean定义中得到当前的scope,比如request,session
-
String scopeName = mergedBeanDefinition.getScope();
-
final Scope scope = (Scope) this.scopes.get(scopeName);
-
//…
-
Object scopedInstance = scope.get(beanName, new ObjectFactory() {
-
public Object getObject() throws BeansException {
-
beforePrototypeCreation(beanName);
-
try {
-
Object bean = createBean(beanName, mergedBeanDefinition, args);
-
if (requiresDestruction(bean, mergedBeanDefinition)) {
-
scope.registerDestructionCallback(beanName,
-
new DisposableBeanAdapter(bean, beanName, mergedBeanDefinition, getBeanPostProcessors()));
-
}
-
return bean;
-
}
-
finally {
-
afterPrototypeCreation(beanName);
-
}
-
}
-
});
-
bean = getObjectForBeanInstance(scopedInstance, name, mergedBeanDefinition);
-
}
在这里与上面类似,Scope为一个接口有RequestScope,SessionScope
2种不同的实现,通过调用它的scope.get方法,传入beanName,和ObjectFactory,之后会在get方法中调用
ObjectFactory. getObject()方法,然后将bean放入RequestAttributes中,实现对其生命周期的控制。
Ps:在实际工作中,我们很少能够看到像书本上那样标准出现的完美的设计模式,实际上很多都是混用,或是变种的。命令模式的优点就在于它的精巧,
像这样实现一个匿名类,然后传递给相应的处理类,处理类所需要做的只是调用它的+do()方法,之后可能需要得到它的返回值再进一步处理。而在这里匿名类
的做法很有趣,我们可以在它的+do()也就是ObjectFactory中的getObject方法中放入一个抽象方法,而具体的实现又是由子类来完成
的。这样做提升了代码的复用度,使代码更简洁。
回到之前那个跳过的Prototype方法,同样也是由子类的createBean来完成创建过程,这里没有什么特别就不贴代码了.
说了这么半天createBean方法我们来看一下它吧,这个方法在每次执行时都会先查询当前bean的所有依赖bean,然后又调用父类中的
getbean方法,而getbean又调用createbean创建依赖bean.这样createBean与getbean形成了一个递归调用,我们
可以把一个类想象为一棵树,即要实例化当前bean首先要实例化它的依赖bean,而依赖类可能又需要依赖类。
-
//createbean 方法一开始的时候会先调用父类的getBean方法,寻找依赖类
-
if (mergedBeanDefinition.getDependsOn() != null) {
-
for (int i = 0; i < mergedBeanDefinition.getDependsOn().length; i++) {
-
getBean(mergedBeanDefinition.getDependsOn()[i]);
-
}
-
}
继续看这个createBean的创建过程
-
BeanWrapper instanceWrapper = createBeanInstance(beanName, mergedBeanDefinition, args);
BeanWrapper主要负责完成的是Bean的属性填充工作
-
//生成包装类后具体的属性填充工作,比如属性,依赖类
-
populateBean(beanName, mergedBeanDefinition, instanceWrapper);
接着会调用放置在beanPostProcessors中的一系列BeanPostProcessor处理器来完成bean最后的产生。先来看BeanPostProcessor的接口定义
-
public interface BeanPostProcessor {
-
-
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
-
-
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
-
-
}
策略设计模式-Strategy
图1-5
定义:Strategy模式也叫策略模式,是由GoF提出的23种软件设计模式的一种。Strategy模式是行为模式之一,它对一系列的算法加
以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。
Strategy模式主要用来平滑地处理算法的切换。
在BeanPostProcessor这个接口中有Object
postProcessBeforeInitialization(Object bean, String beanName),Object
postProcessAfterInitialization(Object bean, String
beanName)这2个方法,入参为Object,beanName。这里这个bean就是在create方法中刚刚创建的bean。这样一个
BeanPostProcessor的实现类就可以拿到创建好的bean,然后对它进行一系列的操作了。比如:动态代理
先看下它的类图
图1-6
Spring使用处理器链的方式来控制整个bean的生命周期,InstantiationAwareBeanPostProcessor是
BeanPostProcessor的子接口。相同的管理bean的生命周期的还
有,BeanFactoryAware,BeanNameAware,InitializingBean,DisposableBean。如果注册了相应
的BeanPostProcessor则在处理过程中会调用该处理器。
下面以applyBeanPostProcessorsAfterInitialization方法为例。
-
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)throws BeansException {
-
-
if (logger.isDebugEnabled()) {
-
logger.debug("Invoking BeanPostProcessors after initialization of bean '" + beanName + "'");
-
}
-
Object result = existingBean;
-
for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext();) {
-
BeanPostProcessor beanProcessor = (BeanPostProcessor) it.next();
-
result = beanProcessor.postProcessAfterInitialization(result, beanName);
-
}
-
return result;
-
}
在这里每个BeanPostProcessor处理器都会对目标类进行处理,结果会被当作下个处理器的目标对象,(注意这里的result被不断的放入postProcessAfterInitialization方法中)
在这里如果我们以Annotation方式的注册了AOP的话,那么会由
AnnotationAwareAspectJAutoProxyCreator来创建动态代理,其父类
AbstractAutoProxyCreator实现了postProcessAfterInitialization方法,其实这里还有一个模板设计
模式,即AbstractAutoProxyCreator提供具体的过程,而由子类BeanNameAutoProxyCreator和
AnnotationAwareAspectJAutoProxyCreator提供不同的实现,最终完成代理类的创建。
-
//AbstractAutoProxyCreator对BeanPostProcessor中postProcessBeforeInstantiation的实现
-
public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException {
-
TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
-
if (targetSource != null) {
-
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
-
return createProxy(beanClass, beanName, specificInterceptors, targetSource);
-
}
-
return null;
-
}
ApplicationContext
一个IOC容器似乎已经成型了,可是问题又来了,如何控制这个容器呢?如何给它注册各种bean定义,BeanPostProcessor,并与
外界程序交互呢?很显然我们不能把这一堆的API直接交给其它的程序员。这里终于到了我们的ApplicationContext登场了!!陈雄华的《精
通spring》写了这么一段话很形象
-
引用:如果说BeanFactory是Spring的心脏,那么ApplicationContext就是Spring的五脏六肺和躯干四肢了。
很形象的比喻,实际上ApplicationContext的作用就像是外界程序与IOC容器直接向交互的桥梁,或者说是IOC容器的门面!
外观模式-Facade
定义:Facade模式为一组具有类似功能的类群,比如类库,子系统等等,提供一个一致的简单的界面。这个一致的简单的界面被称作facade。
在应用系统中,为了实现某具有复杂功能的模块或子系统等时,往往需要为其设计和实现很多很小的类,也就是说,该模块或子系统是由一组具有类似功能
的类群组合而成。这样一来,怎么调用这些类就成了问题。Facade就是这样一种模式,设计一个被称为facade的类,该类提供一个简单的调用接口:
– 隐藏具体的实现细节,简化调用关系。
– 使得调用方的代码更加简洁明了。
– 通过facade,降低外部调用类与内部被调用类间的耦合程度。
– 可以为每个不同的任务需要,准备经过良好设计的简易的API。
呵,摘一个Robert C.Martin的例子来说明这个模式,摘自《Agile Software Development》
图1-7
我们应用程序需要通过DB对象存储ProductData对象到数据库,而DB这个类就是我们java.sql.*下的所有组件交互的门面。外部
组件似乎看不到connection,Statement这些类,它们看到的只是DB中的各种方法,而具体比如在store(ProductData
pd)这个方法中,它会调用各个在java.sql.*中需要的类。
来看我们的ApplicationContext的类图结构
图1-8
在这里请注意右侧的applicationContext接口,它实现了
BeanFactory,ListableBeanFactory,HierarchicalBeanFactory三个接口,而这三个接口正是前面我们
提到的IOC容器类时几个类分层实现的接口。AbstractApplicationContext实现了
ConfigurableApplicationContext在这个类中我们可以找其对各个方法的实现
-
//…
-
-
public Object getBean(String name) throws BeansException {
-
return getBeanFactory().getBean(name);
-
}
-
-
public int getBeanDefinitionCount() {
-
return getBeanFactory().getBeanDefinitionCount();
-
}
-
-
-
//等等等…大多都是以return getBeanFactory()开头执行调用IOC容器的某项操作
而其子类AbstractRefreshableApplicationContext则直接持有了对
DefaultListableBeanFactory(DefaultListableBeanFactory继承了
AutowireCapableBeanFactory)的引用。
这样ApplicationContext就彻底负责IOC容器的各种方法了,其最终的实现
ClassPathXmlApplicationContext(类路径加载),FileSystemXmlApplicationContext(文件
系统加载),而WebApplicationContext则是专门为web准备的。这里为bean添加了3个作用域
request,session,global
session。在web启动时会将其注册到servletContext这样struts等框架就可以通过servletContext的到
WebApplicationContext进而也就可以从容器中得到需要的bean了
最后请注意AbstractApplicationContext中的refresh()方法,这是一个标准的模板模式的应用,spring中由于很多继承方式的采用,模板模式用的很多,只所以把这个专门拿出来,是因为这个方法实在太模板…几乎和书上的一模一样了。
模板方法模式:Template Method
定义:Template Method模式也叫模板方法模式,是由GoF提出的23种设计模式中的一种。Template
Method模式是行为模式之一,它把具有特定步骤算法中的某些必要的处理委让给抽象方法,通过子类继承对抽象方法的不同实现改变整个算法的行为。
Template
Method模式正如其名,在作为抽象类的父类里,定义了一个具有固定算法并可以细分为多个步骤的模板方法(public),Template
Method模式把这些可以被细分的可变步骤抽象为可以被子类重载的抽象方法(protected
abstract),并通过在子类中的重载(重新定义),做到无需改变模板方法的算法步骤而可以重新定义该算法中的某些特定的步骤。
图1-9
在refresh中需要调用执行refreshBeanFactory()这个方法,而这个方法由子类实现,在其子类中这个方法将会完成资源文件
的读取。而另一个getBeanFactory()方法也是一个抽象方法,由其子
AbstractRefreshableApplicationContext类实现,其直接持有了对
DefaultListableBeanFactory的引用,将会把它转型然后返回。之后便可以根据bean的定义和IOC容器的引用来进行接下来的一
系列的操作了,比如注册BeanPostProcessor
-
public void refresh() throws BeansException, IllegalStateException {
-
-
refreshBeanFactory();
-
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
-
//之后的一系列的操作
-
-
// Invoke factory processors registered as beans in the context.
-
invokeBeanFactoryPostProcessors();
-
-
// Register bean processors that intercept bean creation.
-
registerBeanPostProcessors();
-
-
// Initialize message source for this context.
-
initMessageSource();
-
-
// Initialize event multicaster for this context.
-
initApplicationEventMulticaster();
-
-
// Initialize other special beans in specific context subclasses.
-
onRefresh();
-
-
// Check for listener beans and register them.
-
registerListeners();
-
-
// Instantiate singletons this late to allow them to access the message source.
-
beanFactory.preInstantiateSingletons();
-
-
// Last step: publish corresponding event.
-
publishEvent(new ContextRefreshedEvent(this));
-
}
最后会在其子类中调用,来完成整个容器的启动
-
public ClassPathXmlApplicationContext(String[] configLocations, ApplicationContext parent)throws BeansException {
-
-
super(parent);
-
this.configLocations = configLocations;
-
refresh();
-
}
后记:浅浅的读了spring的一部分源码,在这里结合自己的理解写了一些对它的看法,抛砖引玉吧,其中可能有一些错误的理解和不对的地方,若有高手能够指教一二,小生不胜感激了。小生屋中尚有一副茶具,愿以茶待客,与您促膝长谈
问题补充
恩,谢谢,我只是想看看一个问题的分析
我会注意的
问题补充
dennis_zane 写道
spring源码真没啥读的价值,核心的功能怎么实现的理解了就成,spring的源码质量说实话不怎么样,命名方式恶心不说,繁多的mark interface也让人云里雾里。要说模式的应用,还是junit的源码最好。
那读什么的源码比较好呢?
问题补充
dennis_zane 写道
山有峰则灵 写道
dennis_zane 写道
spring源码真没啥读的价值,核心的功能怎么实现的理解了就成,spring的源码质量说实话不怎么样,命名方式恶心不说,繁多的mark interface也让人云里雾里。要说模式的应用,还是junit的源码最好。
那读什么的源码比较好呢?
问题在于你读源码的目的是什么?想从中学到什么?带着目的去读总比东抓一把,西抓一把靠谱点。我在两种情况下会去读源码:遇到开源项目很奇怪的问
题时,就去读读他的源码,看看为什么,应该怎么修正,甚至可能可以去提交下issue甚至patch;想学习某项东西的时候去读相应的开源项目的源码,比
如为了学习nio,就去读cindy、mina,为了知道servlet容器的实现去读jetty源码,spring中值的学习比如反射的应用、AOP的
实现、持久层的封装,计较于非常具体的代码细节的话,你很容易拣到芝麻丢了西瓜。
谢谢,我会仔细思考您说的这些的,非常感谢指点。
我有时的确是看的太细了
呵呵,出了一身冷汗啊。还好问的早,还来得及改,哈哈哈
问题补充:
怎么跑这里了?
牛人,牛人!!多指点几下也好,提提看法,说说主意也行啊!!
多给点建议啊!!!
转载请注明:学时网 » spring源码分析、指导