现在的位置: 首页 > 综合 > 正文

tomcat如何解析resource数据源

2019年04月23日 ⁄ 综合 ⁄ 共 6046字 ⁄ 字号 评论关闭

我们都知道在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<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);
        }
//接下来创建子上下文,因为这里的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方法进行处理,最终取得最终的值,并重新进行绑定。
整个流程可以理解为:

  1. 查询出entryValue
  2. 判断entryValue类型,是否为REFERENCE类型
  3. 如果是则使用NamingManager.getObjectInstance重新获取
  4. 获取resourceRef所对象的FactoryClassName值
  5. 使用ResourceRef的FactoryClass实例化对象并调用其getObjectInstance方法
  6. 根据resourceRef所对应的className进行判断,如果是javax.sql.Datasource,则实例化所对应的tomcat-dbcp中的数据库连接池工厂
  7. 调用数据库连接池工厂再次调用getObjectInstance方法,获取最终的datasource

整个流程所对应的代码如所示:

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

抱歉!评论已关闭.