我们都知道在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方法进行处理,最终取得最终的值,并重新进行绑定。
整个流程可以理解为:
- 查询出entryValue
- 判断entryValue类型,是否为REFERENCE类型
- 如果是则使用NamingManager.getObjectInstance重新获取
- 获取resourceRef所对象的FactoryClassName值
- 使用ResourceRef的FactoryClass实例化对象并调用其getObjectInstance方法
- 根据resourceRef所对应的className进行判断,如果是javax.sql.Datasource,则实例化所对应的tomcat-dbcp中的数据库连接池工厂
- 调用数据库连接池工厂再次调用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