個人學習參考所用,勿噴!
在使用 Tomcat服務器 + SpringFramework 進行JavaEE項目的開發部署的時候可以在Tomcat的配置文件中進行JDBC數據源的配置,具體步驟如下(這里省略了工程的建立步驟):
1) 添加如下代碼到tomcat的conf目錄下的server.xml中:
- <Context>
- <Resource name="jdbc/demoDB" auth="Container"
- type="javax.sql.DataSource"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/demo"
- username="root"
- password="123"
- maxActive="50"
- maxIdle="30"
- maxWait="10000" />
- </Context>
完成上述步驟數據源的連接池配置已經完成,但是為了提高項目的可移植性,最好將上述第二步的內容放入到工程的META-INF目錄的context.xml中(這個文件需要自行建立):
- <?xml version="1.0" encoding="UTF-8"?>
- <Context>
- <Resource name="jdbc/demoDB" auth="Container"
- type="javax.sql.DataSource"
- driverClassName="com.mysql.jdbc.Driver"
- url="jdbc:mysql://localhost:3306/demo"
- username="root"
- password="123"
- maxActive="50"
- maxIdle="30"
- maxWait="10000" />
- </Context>
2)在Spring的配置文件,如applicationContext.xml中配置配置如下內容:
- <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
- <beans>
- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="jndiName">
- <value>java:comp/env/jdbc/demoDB</value>
- </property>
- </bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource">
- <ref bean="dataSource" />
- </property>
- </bean>
-
-
- <bean id="sqlBaseDAO" class="demo.BaseDAOImpl">
- <property name="jdbcTemplate">
- <ref bean="jdbcTemplate" />
- </property>
- </bean>
- </beans>
3)建立數據庫基礎操作類 BaseDAOImpl
接口代碼:
- public interface BaseDAO {
-
- public List<Map<String, Object>> select(String sql);
-
- public void update(String how);
-
- public void insert(Object obj);
-
- public void insert(String sql);
-
- public void save(String sql);
-
- public void edit(String sql);
-
- public void execute(String sql, PreparedStatementCallback callback);
-
- public void delete(String sql);
-
- public void insertObjects(String[] sqls);
-
- public Connection getConnection() throws Exception;
-
- }
實現類代碼:
- public class BaseDAOImpl implements BaseDAO {
- private JdbcTemplate jdbcTemplate;
-
- public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
- this.jdbcTemplate = jdbcTemplate;
- }
-
- public void insert(Object obj) {
-
- }
-
- public void insert(String sql) {
- jdbcTemplate.execute(sql);
- }
-
- public void insertObjects(String[] sqls) {
- jdbcTemplate.batchUpdate(sqls);
- }
-
- public List<Map<String, Object>> select(String sql) {
- return jdbcTemplate.queryForList(sql);
- }
-
- public void update(String how) {
- jdbcTemplate.update(how);
-
- }
-
- public void delete(String sql) {
- if (sql == null) {
- return;
- }
- jdbcTemplate.execute(sql);
- }
-
- public void edit(String sql) {
- if (sql == null) {
- return;
- }
- jdbcTemplate.execute(sql);
- }
-
- public void execute(String sql, PreparedStatementCallback callback) {
- jdbcTemplate.execute(sql, callback);
- }
-
- public void save(String sql) {
- if (sql == null) {
- return;
- }
- jdbcTemplate.execute(sql);
- }
-
- public Connection getConnection() throws Exception {
- Connection conn = jdbcTemplate.getDataSource().getConnection();
- return conn;
- }
-
- }
這里存在一個疑問:
運行如下代碼:
- public static void main(String[] args) {
- org.springframework.jndi.JndiObjectFactoryBean jofb = new org.springframework.jndi.JndiObjectFactoryBean();
- javax.sql.DataSource ds = (javax.sql.DataSource)jofb;
- org.springframework.jdbc.core.JdbcTemplate jTemplate = new org.springframework.jdbc.core.JdbcTemplate();
- jTemplate.setDataSource(ds);
- }
會報告如下的錯誤:
- Exception in thread "main" java.lang.ClassCastException: org.springframework.jndi.JndiObjectFactoryBean cannot be cast to javax.sql.DataSource
從JndiObjectFactoryBean的源碼中也可以看到,JndiObjectFactoryBean的父類或所繼承的接口都沒有繼承javax.sql.DataSource接口,所以一下的配置中:
- <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="jndiName">
- <value>java:comp/env/jdbc/portalDataService</value>
- </property>
- </bean>
- <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource">
- <ref bean="dataSource" />
- </property>
- </bean>
對org.springframework.jdbc.core.JdbcTemplate的dataSource屬性的注入為何能夠成功?
帶著這樣的疑問去iteye中提問,沒有得到詳細的解答,但是iteye的提示功能似乎很不錯,在問題的下方給出了相關內容參考提示,進入到《從源代碼解讀spring之DataSource實現和FactoryBean模式》這個帖子中,看完以后大受啟發。一下是從這篇帖子摘抄出來的內容:
再看源碼后發現,JndiObjectFactoryBean實現了FactoryBean接口,下面是org.springframework.beans.factory.FactoryBean源代碼里一段注釋:
翻譯過來是說:所有實現FactoryBean接口的類都被當作工廠來使用,而不是簡單的直接當作bean來使用,FactoryBean實現類里定義了要生產的對象,并且由FactoryBean實現類來造該對象的實例,看到這里聰明的大概已經能猜出個八九不離十了吧,我們回過頭來看看 JndiObjectFactoryBean的實現細節 :
- private Object jndiObject;
-
-
-
-
- public void afterPropertiesSet() throws IllegalArgumentException, NamingException {
- super.afterPropertiesSet();
-
- if (this.proxyInterface != null) {
- if (this.defaultObject != null) {
- throw new IllegalArgumentException(
- "'defaultObject' is not supported in combination with 'proxyInterface'");
- }
-
- this.jndiObject = JndiObjectProxyFactory.createJndiObjectProxy(this);
- }
-
- else {
- if (!this.lookupOnStartup || !this.cache) {
- throw new IllegalArgumentException(
- "Cannot deactivate 'lookupOnStartup' or 'cache' without specifying a 'proxyInterface'");
- }
- if (this.defaultObject != null && getExpectedType() != null &&
- !getExpectedType().isInstance(this.defaultObject)) {
- throw new IllegalArgumentException("Default object [" + this.defaultObject +
- "] of type [" + this.defaultObject.getClass().getName() +
- "] is not of expected type [" + getExpectedType().getName() + "]");
- }
-
- this.jndiObject = lookupWithFallback();
- }
- }
-
-
-
-
- public Object getObject() {
- return this.jndiObject;
- }
-
- public Class getObjectType() {
- if (this.proxyInterface != null) {
- return this.proxyInterface;
- }
- else if (this.jndiObject != null) {
- return this.jndiObject.getClass();
- }
- else {
- return getExpectedType();
- }
- }
對于JndiObjectFactoryBean對象,spring IOC容器啟動時確實造了它的對象,只不過這時是工廠本身,spring會自動調用工廠里的afterPropertiesSet()方法去造真正需要的 bean,然后調用getObject()和getObjectType()方法返回已造好的對象和類型,再將其準確的注入依賴它的其他bean里面。
好吧,也許上面org.springframework.beans.factory.FactoryBean的注釋看起來像家長教育孩子該怎么怎么,那么Spring到底是怎么實現這種思想的呢?參考《Spring技術內幕》中2.5.3節對FactoryBean的實現的講解,結合Spring 的源碼可以看到:
常見的工廠Bean是怎樣實現的,這些FactoryBean為應用生成需要的對象,這些對象往往是經過特殊處理的,比如像 ProxyFactoryBean 這樣的特殊 Bean。FactoryBean 的生產特性是在getBean中起作用的,我們看到下面的調用:

再來看FactoryBean特性的實現:
-
- protected Object getObjectForBeanInstance(
- Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
-
-
-
- if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
- throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
- }
-
-
-
-
- if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
- return beanInstance;
- }
-
- Object object = null;
- if (mbd == null) {
- object = getCachedObjectForFactoryBean(beanName);
- }
- if (object == null) {
-
- FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
-
- if (mbd == null && containsBeanDefinition(beanName)) {
- mbd = getMergedLocalBeanDefinition(beanName);
- }
- boolean synthetic = (mbd != null && mbd.isSynthetic());
-
- object = getObjectFromFactoryBean(factory, beanName, !synthetic);
- }
- return object;
- }
-
-
- protected Object getObjectFromFactoryBean(FactoryBean factory, String beanName, boolean shouldPostProcess) {
- if (factory.isSingleton() && containsSingleton(beanName)) {
- synchronized (getSingletonMutex()) {
- Object object = this.factoryBeanObjectCache.get(beanName);
- if (object == null) {
- object = doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
- this.factoryBeanObjectCache.put(beanName, (object != null ? object : NULL_OBJECT));
- }
- return (object != NULL_OBJECT ? object : null);
- }
- }
- else {
- return doGetObjectFromFactoryBean(factory, beanName, shouldPostProcess);
- }
- }
-
-
- private Object doGetObjectFromFactoryBean(
- final FactoryBean factory, final String beanName, final boolean shouldPostProcess)
- throws BeanCreationException {
-
- Object object;
-
- try {
- if (System.getSecurityManager() != null) {
- AccessControlContext acc = getAccessControlContext();
- try {
- object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
- public Object run() throws Exception {
- return factory.getObject();
- }
- }, acc);
- }
- catch (PrivilegedActionException pae) {
- throw pae.getException();
- }
- }
- else {
- object = factory.getObject();
- }
- }
- catch (FactoryBeanNotInitializedException ex) {
- throw new BeanCurrentlyInCreationException(beanName, ex.toString());
- }
- catch (Throwable ex) {
- throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex);
- }
-
-
-
- if (object == null && isSingletonCurrentlyInCreation(beanName)) {
- throw new BeanCurrentlyInCreationException(
- beanName, "FactoryBean which is currently in creation returned null from getObject");
- }
-
- if (object != null && shouldPostProcess) {
- try {
- object = postProcessObjectFromFactoryBean(object, beanName);
- }
- catch (Throwable ex) {
- throw new BeanCreationException(beanName, "Post-processing of the FactoryBean's object failed", ex);
- }
- }
-
- return object;
- }
這里返回的已經是作為工廠的 FactoryBean 生產的產品,并不是 FactoryBean 本身。這種FactoryBean的機制可以為我們提供一個很好的封裝機制,比如封裝Proxy、RMI、JNDI等。經過對FactoryBean實現過程的原理分析,相信讀者會對getObject方法有很深刻的印象。這個方法就是主要的FactoryBean 的接口,需要實現特定的工廠的生產過程,至于這個生產過程是怎樣和IoC容器整合的,就是我們在上面分析的內容。
那么返回的類型是怎么確定為javax.sql.DataSource類型的呢?回頭再看在context.xml中的數據源配置可以看到:
- type="javax.sql.DataSource"
這樣一句。然后在去細看JndiObjectFactoryBean類中的afterPropertiesSet方法的具體代碼所以一切都明了了。
綜上所述,這里主要還是要對Spring的FactoryBean模式的理解最為重要。