第28章 Spring框架内的JNDI支持

第28章 Spring框架内的JNDI支持

本章内容

  • JNDI简单回顾

  • Spring框架内JNDI访问的基石——JndiTemplate

  • JNDI对象的依赖注入——JndiObjectFactoryBean

28.1 JNDI简单回顾

JNDI(Java Naming and Directory Interface,Java命名与目录接口),其主要目的是为了统一各种命名与目录服务(naming service/directory service)的访问接口,这与JDBC规范的提出目的有异曲同工之妙。整个JNDI的架构由API和SPI(Service Provider Interface)两部分组成,如图28-1所示。

image-20220714113209534

JNDI API主要公开给Java应用程序使用,它为Java应用程序访问各种命名与目录服务提供了统一的接口。不管Java应用程序将访问的命名和目录服务如何变换,只要Java应用程序是通过JNDIA PI进行命名和目录服务访问的,就可以“无视”各种具体命名和目录服务的差异和变更。

JNDI SPI主要公开给具体命名或者目录服务提供商(Vendor)使用,它为各种具体的命名和目录服务产品提供了一个扩展层。提供商如果愿意加入JNDI规范的行列,就可以根据JNDI SPI规范,给出针对自己命名或者目录服务产品的SPI实现,然后集成到JNDI架构中。除非你被委以重任,要为某种命名和目录服务产品提供JNDI的SPI实现,大部分情况下,我们都只是直接跟JNDI的API打交道。

注意:JNDI的架构设计拥有良好的通用性(从API角度)和扩展性(从SPI角度),这使得我们可以以统一的方式访问任何命名与目录服务,只要有相应的SPI实现就行。不过,或许你也注意到了,大多数时候,我们是直接使用Java应用服务器所提供的JINDI服务实现(各个应用服务器需要根据JavaEE规范提供相应的JNDI服务),但不要让这种特例蒙蔽了我们的双眼,JNDI实际上很强大的哦!

在Java EE平台上,JNDI更多的是为资源的访问和部署提供一个“隔离层”。如果我们通过JNDI进行相应资源的部署,并通过JNDI对这些绑定到JNDI服务的资源进行访问,那么,对资源进行访问的应用程序以及对具体资源的部署就都能够独立演化,即使系统管理员因为某些原因要对某些资源的部署做相应的调整,使用JNDI对这些资源进行访问的应用程序也不会因此受影响。

现有的许多JavaEE服务都依赖于JNDI来访问相应资源,比如JMS需要通过JNDI获取ConnectionFactory,EJB需要通过JNDI获取HOME接口的引用等,为了使大家能够更好的理解Spring框架对这些JavaEE服务的集成,出于铺垫的考虑,我们不得不把了解“Spring对JNDI的支持”作为我们本部分探索之旅的始发站,现在要发车啦!

注意:更多有关JNDI的信息可以参考Sun公司提供的"The JNDI Tutorial”(http://java.sun.com/products/jndi/tutorial/)或者网上的相关资源。

28.2 Spring框架内JNDI访问的基石——JndiTemplate

实际上,相对于JDBCAPI来说,JNDI API的使用看起来要简单得多。不过,与我们所要达到的要求还有一定的距离,下方代码清单中是通常情况下的JNDI API的使用示例。

public DataSource getDataSourceInTraditionalStyle() throws NamingException {
    
    
	Hashtable env = new Hashtable();
	env.put(Context.INITIAL_CONTEXT_FACTORY, "..");
	env.put(Context.PROVIDER_URL, "..");
	//其他可能的设置.....
	Context context = null;
	try {
    
    
		context = new InitialContext(env);
		return (DataSource)context.lookup("jdbc/yourDataSource");
    }
	finally {
    
    
		if(context != null) {
    
    
			try {
    
    
				context.close();
            } catch (NamingException e) {
    
    
				e.printStackTrace(); // 不要这样做
            }
        }
    }
}

实际上,在代码清单中,我们只需要一个DataSource。我想其中只有(DataSource)context.lookup("jdbc/yourDataSource")对我们是紧要的。要是每次通过JNDI来获取相应的资源引用,都书写这么一堆逻辑近乎不变的代码,肯定是怎么看怎么不爽啦!

你可以说这是一个传统,你也可以说这是Spring框架在API设计上的一致性,模板方法模式在这里又一次与我们相遇了。Spring框架提供了org.springframework.jndi.JndiTemplate这一Helper类以帮助我们简化一系列的JNDI操作,包括对象的查找、绑定或者解除绑定等。有了之前一系列模板方法模式使用上的“洗礼”之后,JndiTemplate看起来也容易理解得多。

JndiTemplate的核心方法定义如下方代码清单所示。

public Object execute(JndiCallback contextCallback) throws NamingException {
    
    
	Context ctx = createInitialContext();
	try {
    
    
		return contextCallback.doInContext(ctx);
    }
	finally {
    
    
		try {
    
    
			ctx.close();
        } catch (NamingException ex) {
    
    
			logger.debug("CouldnotcloseJNDIInitialContext", ex);
        }
    }
}

JndiCallback回调接口负责提供具体的JNDI访问逻辑,而底层的JNDI基础设施管理则由当前模板方法来负责。在JNDI访问逻辑需要进一步定制的时候,我们才会考虑execute()方法加JndiCallback的使用形式。对于常见的JNDI访问操作,JndiTemplate在execute()核心模板方法上构建的各种便利性模板方法将是首选。下面的代码片段演示了使用JndiTemplate进行JNDI资源访问与传统API使用模式上的差别:

public DataSource getDataSourceViaJndiTemplate() throws NamingException {
    
    
	Properties env = new Properties();
	env.setProperty(Context.INITIAL_CONTEXT_FACTORY, "..");
	env.setProperty(Context.PROVIDER_URI, "..");
	// 其他可能的设置.....
	JndiTemplate jndiTemplate = new JndiTemplate(env);
	return (DataSource)jndiTemplate.1ookup("jdbc/yourDataSource");
}

当然,实际情况下我们不会把初始化JNDI的环境设置数据硬编码到代码中,将JndiTemplate添加到Spring的IoC容器然后注入给依赖的对象或许会更好,如下所示:

<bean id="jndiTemplate" class="org.springframework.jndi.JndiTemplate">
	<property name="environment">
		<props>
			<prop key="java.naming.factory.initial">..</prop>
			<prop key="java.naming.provider.url">..</prop>
		</props>
	</property>
</bean>

现在,谁需要JNDI访问支持,将JndiTemplate注入给它就行。而且,我想现在访问JNDI的代码就真的只有一行啦。

28.3 JNDI对象的依赖注入——JndiObjectFactoryBean

尽管JndiTemplate在一定程度上简化了JNDI的访问操作,但并不意味着我们就可以不分场合地滥用JndiTemplate。那些依赖于绑定到JNDI服务某项资源的对象,实际上并不关心该项资源到底是来自JNDI服务还是其他位置。所以,如果我们在这些对象内直接使用JndiTemplate甚至是JNDI的Context为其获取相应依赖的话,势必造成当前业务对象与Spring API和JNDI API的过紧耦合。为了避免这种问题,我们应该将JNDI资源访问相关逻辑剥离出当前对象,由其他辅助对象来实现这些逻辑,并将辅助对象获取到的被依赖对象注入到需要它的对象中。Spring框架提供的org.springframework.jndi.JndiObjectFactoryBean将帮助我们完成类似的使命。

JndiObjectFactoryBean是Spring的FactoryBean实现类,负责根据请求的jndi名称,查找并返回绑定到JNDI服务的相应资源引用。帮助JndiObjectFactoryBean完成所有JNDI访问操作的,自然就是JndiTemplate。鉴于FactoryBean的特殊性质,在我们将JndiobjectFactoryBean作为依赖注入给相应对象之后,该对象获取的依赖对象将是JndiObjectFactoryBean通过JNDI所获取的资源引用,而不是JndiObjectFactoryBean本身。所以,在我们声明如下依赖DataSource的对象定义之后:

public class DataSourceRequired {
    
    
    private DataSource dataSource;
    ...
    // getter和setter等方法
}

定义如下的bean定义将保证该对象获取到来自JNDI的DataSource引用:

<bean id="dataSource" class="org.springframework.jndi.JndiobjectFactoryBean" p:jndiName="jdbc/yourDataSource">
</bean>

<bean id="target" class="..DataSourceRequired" p:dataSource-ref="dataSource"/>

当然,Spring2.0发布之后,以上配置内容在引入了jee命名空间的XSD配置文件中可以进一步简化:

<jee:jndi-1ookup id="dataSource" jndi-name="jdbc/yourDataSource"/>

<bean id="target" class="..DataSourceRequired" p:dataSource-ref="dataSource"/>

不管最终我们将使用何种配置形式,我们的目的都是相同的,那就是通过JndiobjectFactoryBean的存在,让对象与其所依赖的对象与JNDI不发生任何的耦合。

默认情况下,JndiObjectFactoryBean在初始化的时候就会查找指定名称的JNDI对象引用,然后缓存之。对于那些不经常变更的JNDI资源来说,这种默认行为可以避免性能上的损失。不过,如果绑定到JNDI的资源经常变动,或者我们希望开发过程中能够实时反映资源的变更情况的话,就可以通过设置JndiObjectFactoryBean的相应属性来定制其行为。表28-1是相应属性与其所控制行为之间的一个简单说明。

image-20220714120231274

从表28-1中我们可以知道,当将lookuponStartupcache设置为false的时候,需要指定proxyInterfaces属性以使得JndiObjectTargetSource可以构建相应的代理对象。这个JndiObjectTargetSource实际上就是AOP的TargetSource实现,它的默认行为与JndiObjectFactoryBean类似,也是默认情况下在实例化的时候执行查找,然后缓存查找结果。不过同样可以通过lookupOnStartupcache属性来变更它的这种默认行为。所以,基本上JndiObjectFactoryBean和JndiObjectTargetSource可以满足相同的查找需求,在使用的时候根据情况选择其中一个就可以了。

28.4 小结

为了能够为之后JavaEE平台上各种企业级应用服务的介绍做铺垫,我们首先在本章对JNDI进行"开刀”,首先一起简单回顾了JNDI相关内容,然后详细介绍了Spring框架为JNDI提供的集成服务。接下来,我们将进入JMS的领地,看一下Spring框架都为JMS做了哪些。

猜你喜欢

转载自blog.csdn.net/qq_34626094/article/details/125944633