最近公眾號受邀獲取了留言和贈送模板的權限,小開心(歡迎去公眾號JackieZheng圍觀)。
我們大致的了解了Spring這個框架對於依賴注入的使用和詮釋可謂是淋漓盡致。因為有了Spring的這個IOC也好DI也好,我們把上街買菜的事情變成了菜主動送上門的活,這樣的“生活方式”大大的提高了我們對於Spring框架的用戶體驗。
今天主要說兩件事,想必凡是稍稍接觸過Spring框架開發的對於這些場景肯定都是眼熟透了——Spring如何使用多個外部屬性文件以及基於注解方式配置Bean。
1. Spring使用多個外部屬性文件
這個截圖並不稀奇,甚至完全看不出什么邏輯,下面分別貼出各個配置文件的內容
beans.xml
... <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> ...
jdbc.properites
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/shake?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=admin
沒錯,這就是你絕對見過的,在Spring中最常見對於數據源的配置
- 你完全可以把jdbc.driver的值寫在beans.xml文件中,但是對於大型項目,某一天你需要該其中的配置,那就必須從龐大而臃腫的beans.xml文件找到你要修改的位置,並且膽戰心驚的確認是不是還有遺漏的地方。
- 你完全可以把這些針對性的配置提取到一個外部屬性文件當中。寫成jdbc.properties的模樣,這樣修改起來,省時省心省力。
- 光有以上的配置還是無法工作的,因為spring的beans.xml文件並不知道該去哪里查找相應的變量,並為變量賦值。所以還需要在beans.xml中添加如下標簽<context:property-placeholder location="jdbc.properties"/>
測試代碼
寫上如下的測試方法,可以用來驗證上述配置是否正確
@Test public void testJDBCConfiguration() throws SQLException { ApplicationContext act=new ClassPathXmlApplicationContext("beans.xml"); DataSource dataSource = (DataSource) act.getBean("dataSource"); System.out.println(dataSource.getConnection()); }
顯然我們得到了理想的結果
那么問題來了,如果我們需要使用多個外部屬性文件,怎么做?
直接按照上面的套路再拷貝一份試試
beans.xml
... <context:property-placeholder location="test.properties"/> <bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${test.driver}"/> <property name="url" value="${test.url}"/> <property name="username" value="${test.username}"/> <property name="password" value="${test.password}"/> </bean> ...
test.properties
test.driver = com.mysql.jdbc.Driver test.url = jdbc:mysql://localhost:3306/shake test.username=root test.password=admin
測試方法
@Test public void testJDBCConfiguration() throws SQLException { ApplicationContext act=new ClassPathXmlApplicationContext("beans.xml"); DataSource dataSource = (DataSource) act.getBean("dataSource1"); System.out.println(dataSource.getConnection()); }
以下是報錯信息
org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'dataSource1' defined in class path resource [beans.xml]: Could not resolve placeholder 'test.driver' in string value "${test.driver}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'test.driver' in string value "${test.driver}" at org.springframework.beans.factory.config.PlaceholderConfigurerSupport.doProcessProperties(PlaceholderConfigurerSupport.java:211) at org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.processProperties(PropertyPlaceholderConfigurer.java:223) at org.springframework.beans.factory.config.PropertyResourceConfigurer.postProcessBeanFactory(PropertyResourceConfigurer.java:86) at ...org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83) at com.jackie.springmvc.TestCollections.testJDBCConfiguration(TestCollections.java:186) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.junit.runner.JUnitCore.run(JUnitCore.java:160) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'test.driver' in string value "${test.driver}" at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174) at ... at org.springframework.beans.factory.config.PlaceholderConfigurerSupport.doProcessProperties(PlaceholderConfigurerSupport.java:208) ... 35 more
報錯的主要原因來源於不識別test.driver這個變量。
究其原因是因為beans.xml對於<context:property-placeholder location="jdbc.properties"/>這樣的標簽,如果有多個這樣的定義,只會生效第一個,后面的都會忽略,這就造成了spring沒有辦法找到test.driver是在哪個文件中定義的。
解決方法
采用通配符的方式,只定義一次,但是可以匹配多個外部屬性文件
<context:property-placeholder location="classpath*:*.properties"/>
這樣就能夠正常運行上面的測試方法。
2.基於注解的方式配置Bean
與之經常同時出場的還有基於XML的方式配置Bean,我想大家都見過或了解autowired=byName和autowired=byType。這兩種都是基於XML方式對於Bean采用基於名字和基於類型進行匹配的。
但是這種方式有他的不足之處,所以在實際的項目中應用的不多。
- 在 Bean 配置文件里設置 autowire 屬性進行自動裝配將會裝配 Bean 的所有屬性. 然而, 若只希望裝配個別屬性時, autowire 屬性就不夠靈活了.
- autowire 屬性要么根據類型自動裝配, 要么根據名稱自動裝配, 不能兩者兼而有之.
- 一般情況下,在實際的項目中很少使用自動裝配功能,因為和自動裝配功能所帶來的好處比起來,明確清晰的配置文檔更有說服力一些
所以你看到以及用到比較多的應該是基於注解的方式配置Bean
beans.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <context:component-scan base-package="com.jackie.spring.annotation.generic"></context:component-scan> </beans>
該spring配置文件及其簡潔,我們甚至看不到聲明bean,只有Context:component-scang該標簽意為spring會掃描com.jackie.spring.annotation.generic包下面的所有相關類。相關類是指具有以下字樣的注解:
@Component: 基本注解, 標識了一個受 Spring 管理的組件
@Respository: 標識持久層組件
@Service: 標識服務層(業務層)組件
@Controller: 標識表現層組件
標注了如上注解的類都是受Spring管轄的。
同時我們還需要如下幾個類
BaseBao.java
public class BaseDao<T> { public void save(T entity){ System.out.println("Save:" + entity); } }
BaseService.java
public class BaseService<T> { private BaseDao<T> dao; public void addNew(T entity){ System.out.println("addNew by " + dao); dao.save(entity); } }
UserBao.java
@Repository public class UserDao extends BaseDao<User>{ }
UserService.java
@Service public class UserService extends BaseService<User>{ }
Main.java
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); UserService userService = (UserService) ctx.getBean("userService"); userService.addNew(new User()); } }
1. 這里BaseBao和UserBao以及BaseService和UserService有一個簡單的繼承關系。
2. UserBao類上加上了注解@Repository表示其為持久層的bean,UserService類上加上了注解@Service表示其為業務層的bean
3. 這時候執行main方法,會報錯
Exception in thread "main" addNew by null
java.lang.NullPointerException
at com.jackie.spring.annotation.generic.BaseService.addNew(BaseService.java:12)
at com.jackie.spring.annotation.generic.Main.main(Main.java:13)
原因很簡單,BaseService中不識別BaseBao這個bean,因為我們並沒有聲明過這個類,也沒有注入,這時候需要在該類前加上注解
@Autowired private BaseDao<T> dao;
加上@Autowired表示Spring裝配了該bean,從而就不會報空指針異常了。最終執行結果:
addNew by com.jackie.spring.annotation.generic.UserDao@32d2fa64 Save:com.jackie.spring.annotation.generic.User@1d8d30f7
4. Spring 還支持 @Resource 和 @Inject 注解,這兩個注解和 @Autowired 注解的功用類似
至此,我們熟悉了不能再熟悉的兩大場景
- Spring如何調用外部屬性文件
- Spring如何調用多個外部屬性文件
- Spring基於注解的方式注入bean的使用場景(反正我是一直在用,你們呢???)
如果您覺得閱讀本文對您有幫助,請點一下“推薦”按鈕,您的“推薦”將是我最大的寫作動力!如果您想持續關注我的文章,請掃描二維碼,關注JackieZheng的微信公眾號,我會將我的文章推送給您,並和您一起分享我日常閱讀過的優質文章。
友情贊助
如果你覺得博主的文章對你那么一點小幫助,恰巧你又有想打賞博主的小沖動,那么事不宜遲,趕緊掃一掃,小額地贊助下,攢個奶粉錢,也是讓博主有動力繼續努力,寫出更好的文章^^。
1. 支付寶 2. 微信