我们都知道在tomcat中,可以通过在context.xml中配置resource中用于配置tomcat数据源,如下所示即是一个配置例子。
1
2
3
4
5
6
|
<
Context
>
<
Resource
name
=
"jdbc/xx"
auth
=
"Container"
type
=
"javax.sql.DataSource"
password
=
"mymysql"
driverClassName
=
"com.mysql.jdbc.Driver"
username
=
"root"
url
=
"jdbc:mysql://127.0.0.1/xx"
/>
Context
>
|
配置了如上的数据源之后,在java代码中,就可以以如下代码进行访问:
1
2
|
InitialContext initialContext =
new
InitialContext();
DataSource dataSource = (DataSource) initialContext.lookup(
"java:comp/env/jdbc/xx"
);
|
那么,tomcat是如何将resource中的信息解析成上下文中,并可以通过jndi的方式进行访问呢。这就得从contextResource对象的创建说起。
在NamingContextListener中,namingContext被创建,同时相应的comp上下文和evn上下文被创建起来。然后通过解析context.xml,将最终的jdbc/xx节点绑定在相应的上下文中,并通过解析Resource节点,最终确定数据源对象的创建。
初始namingContext创建
首先我们进入到NamingContextListener对象中,在启动方法,即lifecycleEvent方法中,会初始化根上的namingContext,并将此对象绑定在容器中,并同时绑定在线程上。如下所示:
1
2
3
4
5
6
7
8
9
|
namingContext =
new
NamingContext(contextEnv, getName());
//创建根上下文
ContextBindings.bindContext(container, namingContext, container);
//将这个上下文绑定在绑定中
//因此当前容器为StandardContext,所以会将此上下文绑定在线程类加载器中,以方便后面进行获取
if
(container
instanceof
Context) {
ContextBindings.bindClassLoader
(container, container,
((Container) container).getLoader().getClassLoader());
}
|
创建子上下文
创建了根上下文后,就开始创建子上下文即comp和env了。这两个子上下文的创建封装在方法createNamingContext中,如下所示:
1
2
|
compCtx = namingContext.createSubcontext(
"comp"
);
//这里是组件上下文
envCtx = compCtx.createSubcontext(
"env"
);
//将环境上下文创建在组件上下文中
|
解析数据源
一旦创建了子上下文,就会被已经由digester解析出来的ContextResource对象加载到上下文中,这里就会碰到contextResource的进一步解析。因为,在之前仅是一个contextResource描述对象,这里要将此对象转换成一个objectFactory对象,即可以创建出数据源的一个工厂对象。这就会进入到addResource(ContextResource resource)方法。此方法的作用在于绑定子上下文,同时进行解析。我们先看如何进行信息绑定,如下代码所示:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
//这里将ContextResource里面的信息,重新组装在一个ResourceRef对象
//首先解析可以被确定的属性信息,如类型,验证方式等
Reference ref =
new
ResourceRef
(resource.getType(), resource.getDescription(),
resource.getScope(), resource.getAuth(),
resource.getSingleton());
//这里解析其他属性信息,这些信息以properties的方式放到ContextResource中,如driverClassName,username,password等。
Iterator
while
(params.hasNext()) {
String paramName = params.next();
String paramValue = (String) resource.getProperty(paramName);
StringRefAddr refAddr =
new
StringRefAddr(paramName, paramValue);
ref.add(refAddr);
}
//接下来创建子上下文,因为这里的resource Name为jdbc/xx,所以会依次创建jdbc上下文和xx上下文
createSubcontexts(envCtx, resource.getName());
envCtx.bind(resource.getName(), ref);
}
catch
(NamingException e) {
logger.error(sm.getString(
"naming.bindFailed"
, e));
}
|
在绑定了context之后,这里就会触发一次jndi资源的解析,即首先尝试解析数据源信息是否正确。即通过调用context.lookup进行预处理。预处理之后的值即为们想要的datasource,同时,因为ResourceRef为singleton的,所以会触发绑定值的更新。最终第二次再次lookup之后,就会直接取得datasource了。如以下代码所示:
1
2
3
4
5
|
if
(
"javax.sql.DataSource"
.equals(ref.getClassName())) {
ObjectName on = createObjectName(resource);
Object actualResource = envCtx.lookup(resource.getName());
......
}
|
这里的envCtx.lookup和我们所应用的context.lookup没有什么不同,它会查询出一个entry值,其中type表示所绑定的对象的类型,value即绑定的值。惟一不同的是这里本应该查询出刚才绑定的resourceRef对象,但是在tomcat内部,它会对resourceRef作二次处理。即会通过使用针对于resourceRef的FactoryClassName,再其他其的getObjectInstance方法进行处理,最终取得最终的值,并重新进行绑定。
整个流程可以理解为:
整个流程所对应的代码如所示:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
//1 查询出entryValue,对应类NamingContext的lookup(Name name, boolean resolveLinks)方法
NamingEntry entry = bindings.get(name.get(
0
));
//2 进行判断
if
(entry.type == NamingEntry.REFERENCE) {
try
{
Object obj = NamingManager.getObjectInstance
(entry.value, name,
this
, env);
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;
}
}
//3 进入到NamingContextManager内部
if
(ref !=
null
) {
String f = ref.getFactoryClassName();
//获取factoryClassName值
if
(f !=
null
) {
// if reference identifies a factory, use exclusively
factory = getObjectFactoryFromReference(ref, f);
//实际化此工厂,即类ResourceFactory
if
(factory !=
null
) {
return
factory.getObjectInstance(ref, name, nameCtx,
environment);
//调用其相应方法
}
}
//6 进入ResourceFactory内部,获取数据库连接池工厂
if
(obj
instanceof
ResourceRef) {
......
if
(ref.getClassName().equals(
"javax.sql.DataSource"
)) {
String javaxSqlDataSourceFactoryClassName =
System.getProperty(
"javax.sql.DataSource.Factory"
,
Constants.DBCP_DATASOURCE_FACTORY);
factory = (ObjectFactory)
Class.forName(javaxSqlDataSourceFactoryClassName)
.newInstance();
}
//7 调用数据库工厂相应访问
if
(factory !=
null
) {
//最终返回datasource对象
return
factory.getObjectInstance
(obj, name, nameCtx, environment);
}
|
在获取最终的datasource对象之后,NamingCotext会重新进行一次绑定,以避免重复获取。代码如下所示:
1
2
3
4
|
if
(singleton) {
//这里即指我们的resourceRef是否为单态的,默认值即为true
entry.type = NamingEntry.ENTRY;
entry.value = obj;
}
|
至此,整个解析过程结束。在后面的java代码中,我们通过java:comp/env/jdbc/xx这个即可访问到datasource,实际上就是访问的已经获取到的datasource值了。但通过java:xx这个命名对象值进行查询,又和上面的查询不一样(大部分相同,只是起点不一样)。关于此的差别在下一篇 tomcat如何获取jndi信息 进行处理。
转载请标明出处:i flym
本文地址:http://www.iflym.com/index.php/code/201208080004.html