tomcat如何解析resource数据源

我们都知道在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

猜你喜欢

转载自blog.csdn.net/baidu2030/article/details/22676607