目录
提起 Spring,我们就想起 IOC(控制反转),实现 IOC 有两种技术:一是 DL(依赖查找 depency lookup),二是 DI(依赖注入 depency inject)。其实 Java 很早就有 DL 技术,本章让我们走近 DL 的鼻祖 JNDI。
JNDI(Java Naming and Directory Interface,Java 命名和目录接口)是一组在 Java 应用中访问命名和目录服务的API。简单来说,就是根据 obj 名称查找 obj,从而实现解耦。
JNDI 规范位于 javax.naming 包,属于 JavaEE 规范,实现厂商有 Jboss、Tomcat 等。下面以 Tomcat 为例,对 JNDI 进行讲解。
(1) maven 依赖
<!-- JNDI 规范实现 -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.19</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
(2)测试
// "java:comp/env/jdbc/mysql" 包含三层 Context,最后一层存储元素 mysql
@Test
public void testSelectorContext() throws Exception {
String jdbcPath = "jdbc/mysql";
createSelectorContext();
InitialContext ctx = new InitialContext();
// 1. 绑定依赖
createSubcontexts(ctx, "java:comp/env/" + jdbcPath);
ctx.bind("java:comp/env/" + jdbcPath, new MysqlDataSource());
// 2. 依赖查找
Object ds = ctx.lookup("java:comp/env/" + jdbcPath);
Assert.assertEquals(MysqlDataSource.class, ds.getClass());
}
// 创建 schema 为 “java:” 的命名空间
public void createSelectorContext() throws Exception {
System.setProperty(NamingContext.URL_PKG_PREFIXES, "org.apache.naming");
System.setProperty(NamingContext.INITIAL_CONTEXT_FACTORY,
javaURLContextFactory.class.getName());
NamingContext rootContext = createNamingContext();
ContextBindings.bindContext(this, rootContext, null);
ContextBindings.bindThread(this, null);
}
// 创建父容器 NamingContext
public NamingContext createNamingContext() throws Exception {
NamingContext rootContext = new NamingContext(null, "rootContext");
Context compCtx = rootContext.createSubcontext("comp");
Context envCtx = compCtx.createSubcontext("env");
return rootContext;
}
// 递归创建子容器 subContext
private void createSubcontexts(Context ctx, String name)
throws NamingException {
Context currentContext = ctx;
StringTokenizer tokenizer = new StringTokenizer(name, "/");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if ((!token.equals("")) && (tokenizer.hasMoreTokens())) {
try {
currentContext = currentContext.createSubcontext(token);
} catch (NamingException e) {
// Silent catch. Probably an object is already bound in the context.
currentContext = (Context) currentContext.lookup(token);
}
}
}
}
说明: 可以看到,使用时还是非常简单的。JNDI 提供了 InitialContext 作为入口,可以轻松的将 "java:comp/env/jdbc/mysql" 绑定(bind)到容器中,也可以很轻松的查找(lookup)。需要注意时,元素绑定到容器时,如果有多级(eg: jdbc/mysql),必须先创建子容器(jdbc)。
单一资源查找
Context 只支持根据名称查找。
DataSource ds = (DataSource) jdbcContext.lookup("mysql");
集合资源查找
不涉及。Context 只能根据 Name 查找,不能通过 Type 进行类型查找,也就是只能进行单一资源查找。
层级资源查找
DataSource ds = (DataSource) initialContext.lookup("java:comp/env/jdbc/mysql");
依赖查找时,会依次从父容器往子容器查找,直到找到元素为至。
Context 只支持根据名称查找,不支持根据类型查找。
作用类似 Spring 中的 Scope,Context 也有单例、多例。
NamingContext#lookup 获取对象时,会判断 Reference 的属性 singleton,如果为 true 则将 NamingEntry.type 属性设置为 ENTRY,并将 obj 缓存越来。
if(entry.value instanceof ResourceRef) {
boolean singleton = Boolean.parseBoolean(
(String) ((ResourceRef) entry.value).get("singleton").getContent());
if (singleton) {
entry.type = NamingEntry.ENTRY;
entry.value = obj;
}
}
作用类似 Spring 中的 BeanDefinition,Reference 定义了对象属性。典型实现 ResourceRef。
与 Reference 相关的一个接口是 Referenceable,提供了 getReference 用于获取 Reference 属性。
public interface Referenceable {
Reference getReference() throws NamingException;
}
对象实例可以实现 Reference 或 Referenceable 接口,如演示案例中的 MysqlDataSource 就实现了 Reference 接口。这样依赖查找时就可以通过 Reference 创建对象。
Spring 中也有一个 ObjectFactory,但个人觉得更类似 FactoryBean,用于根据 Reference 和 Context 创建对象。
JNDI 中的每个 Object 都对应 Reference,通过 ObjectFactory 创建对象,不像 Spring 是透明的,Spring 只有复杂对象的创建才会使用 FactoryBean(一般在整合三方框架时使用),这是因为 Spring 除了依赖查找外,还有依赖注入,面向 POJO 编程极大的简化了编程模型。典型实现 ResourceFactory。
ObjectFactory 创建的实例对象即可以是 Context,也可以是通过 Reference 创建具体的实例 obj。
public interface ObjectFactory {
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment) throws Exception;
}
JNDI 和 Spring 都实现了基本的依赖查找功能。
先回顾一下 JNDI 的基本用法:
// 获得对数据源的引用
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/mysql");
// 或先获取 subContext
Context envContext = (Context)ctx.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/mysql");
"java:comp/env/jdbc/mysql" 后,Context 容器中 JNDI 对象树结构如下:
InitialContext
:JNDI 提供的入口 Context,InitialContext 的主要功能是通过 getURLOrDefaultInitCtx() 方法获取真实的 Context,在 Tomcat 中一般就是 SelectorContext。InitialContext 是一个简单的代理模式,将所有的功能都委托给 SelectorContext。
SelectorContext
:用于处理 schema,在 Tomcat 中就是 "java"。通过配置属性 java.naming.factory.url.pkgs=org.apache.naming,在 getURLOrDefaultInitCtx() 初始化时通过 ${java.naming.factory.url.pkgs}.${schema}.${schema}URLContextFactory 来获取工厂类 ObjectFactory,再通过 javaURLContextFactory#getObjectInstance 创建 SelectorContext。
SelectorContext 通过 lookup(name) 时,会先处理 name,将前缀 "java:" 去除,如 "java:comp/env/jdbc/mysql" 解析为 "comp/env/jdbc/mysql"。绑定 bind 元素时同理。
NamingContext
:真正的容器实现者,子元素可以是子容器(context),也可以是元素(element)。依赖查找时如果元素是子容器则委托给子容器继续查找。元素绑定时,如果 name 有多级(eg: jdbc/mysql),则必须先创建子容器 jdbc,然后才能将元素绑定到容器中。
JNDI 名称 Name 也是有层级的,默认实现是 CompositeName(javadoc 有详细的使用说明)。CompositeName 将 ”comp/env/jdbc/mysql“ 解析成 {”comp“, ”env“, ”jdbc“, ”mysql“},每层对应自己的 Context。
NamingEntry
:子节点(jdbc)将 jdbc 包装成 NamingEntry 绑定到容器中。元素有以下几种情况:
说明: InitialContext、SelectorContext、NamingContext、NamingEntry 在上一小节对象树上已经介绍过了。
InitialContext 初始化时会调用 javaURLContextFactory 初始化 SelectorContext,进而初始化 NamingContext。无论是资源绑定,还是依赖查找,最终都会调用 NamingContext。
资源绑定:资源绑定时最终调用 NamingContext#bind方法。绑定时会将对象包装成 NamingEntry,需要注意的是,Context 中绑定的资源有多种类型,包括 CONTEXT、REFERENCE、LINK_REF、ENTRY 等。
依赖查找:依赖查找时最终调用 NamingContext#lookup 方法。如果是 CONTEXT 则继续向下查找,如果是 REFERENCE 则需要根据对象元信息构建对象,如果是 ENTRY 则直接返回。比如 ResourceRef 就需要 ResourceFactory 来初始化对象返回。
(1)配置加载
public InitialContext() throws NamingException {
init(null);
}
protected void init(Hashtable<?,?> environment) throws NamingException {
// 1. 加载配置信息
myProps = (Hashtable<Object,Object>)
ResourceManager.getInitialEnvironment(environment);
// 2. 加载默认Context,通过InitialContextFactory
if (myProps.get(Context.INITIAL_CONTEXT_FACTORY) != null) {
getDefaultInitCtx();
}
}
ResourceManager#getInitialEnvironment 方法会加载相关配置,配置有两种来源:一是 properties;二是系统变量 System.getProperties,它们的优先级如下:
System.getProperties() > ${classpath}/jndi.properties > ${java.home}/lib/jndi.properties
jndi.properties 配置如下:
java.naming.factory.url.pkgs=org.apache.naming
java.naming.factory.initial=org.apache.naming.java.javaURLContextFactory
java.naming.provider.url=127.0.0.1:1099
其中有两个环境变量非常重要:URL_PKG_PREFIXES 和 INITIAL_CONTEXT_FACTORY。前者是根据 schema 加载 Context,后者则是加载默认的 Context,也就是通过 InitialContextFactory 加载。
// ObjectFactory, 默认 org.apache.naming.java.javaURLContextFactory
String URL_PKG_PREFIXES = "java.naming.factory.url.pkgs";
private static final String defaultPkgPrefix = "com.sun.jndi.url";
// InitialContextFactory, 默认 org.apache.naming.java.javaURLContextFactory
String INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial";
(2)加载 Context
InitialContext 的所有操作都委托给了 getURLOrDefaultInitCtx() 初始化的 Context 完成,那它到底是如何加载到 SelectorContext 的呢?
protected Context getURLOrDefaultInitCtx(Name name) throws NamingException {
// 1. 设置了全局的InitialContextFactoryBuilder
if (NamingManager.hasInitialContextFactoryBuilder()) {
return getDefaultInitCtx();
}
// 2. 根据schema加载,eg: "java:comp/env/jdbc/mysql" 的schema为java
if (name.size() > 0) {
String first = name.get(0);
String scheme = getURLScheme(first);
if (scheme != null) {
Context ctx = NamingManager.getURLContext(scheme, myProps);
if (ctx != null) {
return ctx;
}
}
}
// 3. INITIAL_CONTEXT_FACTORY属性对应的InitialContextFactory加载
return getDefaultInitCtx();
}
总结: getURLOrDefaultInitCtx 要么是通过 schema 查找对应的 Context,要么是 INITIAL_CONTEXT_FACTORY属性对应的 InitialContextFactory 加载 Context。
(1)schema 加载
// Tomcat 中为 org.apache.naming.java.javaURLContextFactory
${java.naming.factory.url.pkgs}.${schema}.${schema}URLContextFactory
在 javax.naming.spi.NamingManager 中默认为 com.sun.jndi.url 包下,如 schema 为 rmi 对应 com.sun.jndi.url.rmi.rmiURLContextFactory。Tomcat 修改了对应的配置,默认为 org.apache.naming.java.javaURLContextFactory。
// javax.naming.spi.NamingManager#getURLObject
private static Object getURLObject(String scheme, Object urlInfo, Name name,
Context nameCtx, Hashtable<?,?> environment) throws NamingException {
ObjectFactory factory = (ObjectFactory)ResourceManager.getFactory(
Context.URL_PKG_PREFIXES, environment, nameCtx,
"." + scheme + "." + scheme + "URLContextFactory", defaultPkgPrefix);
...
return factory.getObjectInstance(urlInfo, name, nameCtx, environment);
}
(2)InitialContextFactory 加载
getDefaultInitCtx 最终会读取 env 中的 "java.naming.factory.initial" 属性,加载 InitialContextFactory 初始化 Context。
// javax.naming.spi.NamingManager#getInitialContext
public static Context getInitialContext(Hashtable<?,?> env) throws NamingException {
...
String className = (String)env.get(Context.INITIAL_CONTEXT_FACTORY);
InitialContextFactory factory = (InitialContextFactory) helper.loadClass(className).newInstance();
return factory.getInitialContext(env);
}
说明: 读取 "java.naming.factory.initial" 属性,加载 Context。
(3)javaURLContextFactory
public class javaURLContextFactory implements ObjectFactory, InitialContextFactory {
// SelectorContext
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment) throws NamingException {
if ((ContextBindings.isThreadBound()) || (ContextBindings.isClassLoaderBound())) {
return new SelectorContext((Hashtable<String,Object>)environment);
}
return null;
}
// 默认的Context
@Override
public Context getInitialContext(Hashtable<?,?> environment)
throws NamingException {
if (ContextBindings.isThreadBound() || (ContextBindings.isClassLoaderBound())) {
return new SelectorContext(environment, true);
}
// If the thread is not bound, return a shared writable context
if (initialContext == null) {
initialContext = new NamingContext(environment, MAIN);
}
return initialContext;
}
}
说明: 可以看到 Tomcat 最后加载了 SelectorContext,而 SelectorContext 最终委托 NamingContext 处理。
NamingContext#bind 资源绑定,肯定最终都是放到 bindings 的 Map 中,我们看一下是怎么处理的。
protected final HashMap<String,NamingEntry> bindings;
protected void bind(Name name, Object obj, boolean rebind) throws NamingException {
// 1. 去除name名称最前的空,如 "//x/y" -> {"", "", "x", "y"}
while ((!name.isEmpty()) && (name.get(0).length() == 0))
name = name.getSuffix(1);
NamingEntry entry = bindings.get(name.get(0));
// 2. 多层context,委托给subContext绑定
if (name.size() > 1) {
if (entry == null) {
throw new NameNotFoundException("namingContext.nameNotBound");
}
if (entry.type == NamingEntry.CONTEXT) {
if (rebind) {
((Context) entry.value).rebind(name.getSuffix(1), obj);
} else {
((Context) entry.value).bind(name.getSuffix(1), obj);
}
} else {
throw new NamingException("namingContext.contextExpected");
}
// 3. 绑定数据
} else {
// 3.1 如果entry已经绑定且不是rebind,则抛出异常。
if ((!rebind) && (entry != null)) {
throw new NameAlreadyBoundException("namingContext.alreadyBound");
} else {
// 3.2 CONTEXT/LinkRef/Reference/Referenceable/entry 分别处理
Object toBind = NamingManager.getStateToBind(obj, name, this, env);
if (toBind instanceof Context) {
entry = new NamingEntry(name.get(0), toBind, NamingEntry.CONTEXT);
} else if (toBind instanceof LinkRef) {
entry = new NamingEntry(name.get(0), toBind, NamingEntry.LINK_REF);
} else if (toBind instanceof Reference) {
entry = new NamingEntry(name.get(0), toBind, NamingEntry.REFERENCE);
} else if (toBind instanceof Referenceable) {
toBind = ((Referenceable) toBind).getReference();
entry = new NamingEntry(name.get(0), toBind, NamingEntry.REFERENCE);
} else {
entry = new NamingEntry(name.get(0), toBind, NamingEntry.ENTRY);
}
// 3.3 最终还是放到map中
bindings.put(name.get(0), entry);
}
}
}
说明: bind 将 name 和 obj 绑定到 bindings 中:
NamingContext#lookup资源绑定,最终肯定是根据 name 从 bindings 中查找 obj,最重要的是对不同的元素类型是怎么处理的。
protected Object lookup(Name name, boolean resolveLinks) throws NamingException {
...
NamingEntry entry = bindings.get(name.get(0));
...
if (name.size() > 1) {
// 1. Context类型,父子Context,委托 subContext查找
if (entry.type != NamingEntry.CONTEXT) {
throw new NamingException("namingContext.contextExpected");
}
return ((Context) entry.value).lookup(name.getSuffix(1));
} else {
// 2. LINK_REF类型,类似Spring中的别名
if ((resolveLinks) && (entry.type == NamingEntry.LINK_REF)) {
String link = ((LinkRef) entry.value).getLinkName();
if (link.startsWith(".")) {
// Link relative to this context
return lookup(link.substring(1));
} else {
return new InitialContext(env).lookup(link);
}
// 3. REFERENCE类型,类似Spring中的BeanDefinition
} else if (entry.type == NamingEntry.REFERENCE) {
Object obj = NamingManager.getObjectInstance(entry.value, name, this, env);
// 单例,由REFERENCE -> ENTRY,直接返回
if(entry.value instanceof ResourceRef) {
boolean singleton = Boolean.parseBoolean(
(String) ((ResourceRef) entry.value).get("singleton").getContent());
if (singleton) {
entry.type = NamingEntry.ENTRY;
entry.value = obj;
}
}
return obj;
// 4. ENTRY类型,直接返回。所以多次查找都是同一对象,相当于单例
} else {
return entry.value;
}
}
}
说明: lookup 处理了 Context/LINK_REF/REFERENCE/ENTRY 几种类型。
在依赖查找中,如果对象是 NamingEntry.REFERENCE 类型,则会调用 NamingManager#getObjectInstance 创建对象。有以下几个问题:
(1)什么时候绑定的对象是 REFERENCE 类型
对于第一个问题,当绑定的对象实现 Reference 或 Referenceable 时,对象为类型是 REFERENCE 类型,也就是说可以从绑定的对象中获取对象的元信息。
大家想一下这有什么问题,绑定对象 obj 和对象元信息 Reference 是继承关系,必须实现 Reference 接口,对业务代码侵入性太大。如 MysqlDataSource 就实现了 Reference 接口。在 Spring 中 bean 和 beanDefinition 是没有关系的,业务方不感知 beanDefinition,实现更优雅一些。
(2)Reference 属性元信息结构
public class Reference {
// 对象类型和工厂类型(ObjectFactory)
protected String className;
protected String classFactory = null;
// RefAddr 属性元信息,KV 结构 <type, content>
protected Vector<RefAddr> addrs = null;
...
}
Reference 中保存了 obj 对象的对象类型和工厂类型(ObjectFactory),以及属性信息。这样就可以通过 ObjectFactory 创建对象,当然每次创建的对象都不是同一个,也就是多例。
(3)NamingManager#getObjectInstance 是如何创建对象
public static Object getObjectInstance(Object refInfo, Name name, Context nameCtx,
Hashtable<?,?> environment) throws Exception {
ObjectFactory factory;
// 1. 全局ObjectFactory创建对象,一般没有设置
ObjectFactoryBuilder builder = getObjectFactoryBuilder();
if (builder != null) {
factory = builder.createObjectFactory(refInfo, environment);
return factory.getObjectInstance(refInfo, name, nameCtx, environment);
}
// 2. 获取对象元信息 Reference
Reference ref = null;
if (refInfo instanceof Reference) {
ref = (Reference) refInfo;
} else if (refInfo instanceof Referenceable) {
ref = ((Referenceable)(refInfo)).getReference();
}
Object answer;
// 2. 根据对象元信息 Reference 创建对象
if (ref != null) {
String f = ref.getFactoryClassName();
if (f != null) {
// 2.1 loadClass ref.factoryClassName,自定义ObjectFactory加载对象
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx, environment);
}
return refInfo;
} else {
// 2.2 ref属性:"URL": "rmi://host:port/xxx",根据URL中的schema加载对象
// ${java.naming.factory.url.pkgs}.${schema}.${schema}URLContextFactory
answer = processURLAddrs(ref, name, nameCtx, environment);
if (answer != null) {
return answer;
}
}
}
// 3. 使用全局定义的工厂ObjectFactory,加载对象,任意一个ObjectFactory加载对象成功即返回
// 属性 "java.naming.factory.object"
answer = createObjectFromFactories(refInfo, name, nameCtx, environment);
return (answer != null) ? answer : refInfo;
}
说明: 对象创建的关键是如何获取对象工厂 ObjectFactory。
设置对象元信息 Reference:分两种情况。
全局 ObjectFactory:如果变量 "java.naming.factory.object" 定义有全局 ObjectFactory,则根据 ObjectFactory 创建对象,只要有一个能创建成功,则返回。
(4)MysqlDataSource 示例说明
MysqlDataSource 实现了 Referenceable 接口,getReference 方法
public Reference getReference() throws NamingException {
String factoryName = MysqlDataSourceFactory.class.getName();
Reference ref = new Reference(this.getClass().getName(), factoryName, (String)null);
ref.add(new StringRefAddr(PropertyKey.USER.getKeyName(), this.getUser()));
ref.add(new StringRefAddr(PropertyKey.PASSWORD.getKeyName(), this.password));
ref.add(new StringRefAddr("serverName", this.getServerName()));
ref.add(new StringRefAddr("port", "" + this.getPort()));
ref.add(new StringRefAddr("databaseName", this.getDatabaseName()));
...
}
说明:Reference 定义了 factoryName 和必要的属性属性。MysqlDataSourceFactory 实现了 ObjectFactory 接口,通过 ObjectFactory 就可以创建 MysqlDataSource 对象。
文章推荐:
在 NamingContextListener#createNamingContext 创建 Context 时会调用 addResourceLink、addResource、addResourceEnvRef、addEnvironment、addEjb 等。这些方法会将从 xml 文件中加载的配置信息 ResourceBase 等包装成 Reference。
方法 | ResourceBase | Reference | ObjectFactory |
---|---|---|---|
addResource | ContextResource | ResourceRef | ResourceFactory |
addResourceLink | ContextResourceLink | ResourceLinkRef | ResourceLinkFactory |
addEnvironment | ContextEnvironment | obj | -- |
addService | ContextService | ServiceRef | ServiceRefFactory |
-- | -- | TransactionRef | TransactionFactory |
Reference 和 ObjectFactory 类图关系如下:
说明: Tomcat 资源绑定和依赖查找
以 addResource 为例,会将从 xml 文件中加载的配置信息 ContextResource 等包装成 ResourceRef 等,然后绑定到 Context 中。
public void addResource(ContextResource resource) {
// 1. resource包装成Reference
Reference ref = lookForLookupRef(resource);
if (ref == null) {
// ContextResource 包装成 ResourceRef
ref = new ResourceRef(resource.getType(), resource.getDescription(),
resource.getScope(), resource.getAuth(), resource.getSingleton());
// 添加属性信息拷贝到 ref 中
Iterator<String> params = resource.listProperties();
while (params.hasNext()) {
String paramName = params.next();
String paramValue = (String) resource.getProperty(paramName);
StringRefAddr refAddr = new StringRefAddr(paramName, paramValue);
ref.add(refAddr);
}
}
// 2. Reference绑定到Context中
createSubcontexts(envCtx, resource.getName());
envCtx.bind(resource.getName(), ref);
...
}
在 NamingManager#getObjectInstance 时,会根据 ref.getFactoryClassName() 获取 factoryClassName,对于 ResourceRef 而言,默认的工厂类是 ResourceFactory。
说明: 如果绑定的对象是 ResourceRef,则需要获取对应的工厂 ResourceFactory 来创建对象。 ResourceFactory 其实也是一个代理模式,查找真正的 ObjectFactory 工厂,创建对象。
// AbstractRef#getFactoryClassName
@Override
public final String getFactoryClassName() {
// 1. Reference中指定factoryName
String factory = super.getFactoryClassName();
if (factory != null) {
return factory;
} else {
// 2. 配置项"java.naming.factory.object"
factory = System.getProperty(Context.OBJECT_FACTORIES);
if (factory != null) {
return null;
// 3. 默认工厂,对于 ResourceRef 就是 ResourceFactory。子类实现
} else {
return getDefaultFactoryClassName();
}
}
}
说明: 对于 ResourceRef 类型,则委托给 ResourceFactory 来创建对象。
// FactoryBase#getObjectInstance
@Override
public final Object getObjectInstance(Object obj, Name name, Context nameCtx,
Hashtable<?,?> environment) throws Exception {
if (isReferenceTypeSupported(obj)) {
Reference ref = (Reference) obj;
// 1. 获取真正的ObjectFactory
ObjectFactory factory = null;
RefAddr factoryRefAddr = ref.get(Constants.FACTORY);
if (factoryRefAddr != null) {
String factoryClassName = factoryRefAddr.getContent().toString();
factoryClass = Class.forName(factoryClassName);
factory = (ObjectFactory) factoryClass.getConstructor().newInstance();
} else {
factory = getDefaultFactory(ref);
}
// 2. 通过ObjectFactory创建对象
return factory.getObjectInstance(obj, name, nameCtx, environment);
}
return null;
}
说明: ResourceFactory 通过 ref.get(Constants.FACTORY) 加载 ObjectFactory 来创建对象。
每天用心记录一点点。内容也许不重要,但习惯很重要!
原文:https://www.cnblogs.com/binarylei/p/12273010.html