tomcat如何解析resource数据源

我们都知道在tomcat中,可以通过在context.xml中配置resource中用于配置tomcat数据源,如下所示即是一个配置例子。

<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代码中,就可以以如下代码进行访问:

         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,并将此对象绑定在容器中,并同时绑定在线程上。如下所示:

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中,如下所示:

compCtx = namingContext.createSubcontext("comp");//这里是组件上下文
envCtx = compCtx.createSubcontext("env");  //将环境上下文创建在组件上下文中

解析数据源

一旦创建了子上下文,就会被已经由digester解析出来的ContextResource对象加载到上下文中,这里就会碰到contextResource的进一步解析。因为,在之前仅是一个contextResource描述对象,这里要将此对象转换成一个objectFactory对象,即可以创建出数据源的一个工厂对象。这就会进入到addResource(ContextResource resource)方法。此方法的作用在于绑定子上下文,同时进行解析。我们先看如何进行信息绑定,如下代码所示:

//这里将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了。如以下代码所示:        

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

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

//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会重新进行一次绑定,以避免重复获取。代码如下所示:

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
本文地址:https://www.iflym.com/index.php/code/201208080004.html

相关文章:

作者: flym

I am flym,the master of the site:)

发表评论

邮箱地址不会被公开。 必填项已用*标注