IoC:Inversion of Control,中文一般翻譯爲「控制反轉」,它還有一個別名叫作依賴注入(Dependency Injection)。但實際上依賴注入控制反轉的一種表達方式(還有一種叫依賴查找)。什麼是控制反轉呢,簡單來講就是原本上層建築依賴下層建築,下載經過依賴注入是下層建築依附於上層建築。具體表現就是經過注入的方式,爲高級類(接口)添加依賴,注入方式能夠爲構造方法、set方法和接口注入(用得少,侵入性高)。java
而Spring就一種是典型的IoC容器(用來管理bean),而且能夠幫助咱們管理注入,省去許多麻煩(感受有點像JVM幫咱們管理內存同樣)mysql
推薦看一下《Spring揭祕》這本書,講的很是不錯。面試
首先導入IoC相關依賴:
spring
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency>
而後是service和dao層的接口及其實現類:
sql
public interface UserDao { public void save(); } public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("保存用戶信息"); } } public interface UserService { public void register(); } public class UserServiceImpl implements UserService { @Override public void register() { System.out.println("註冊"); } }
再而後是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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/"> <!-- 定義bean id:惟一標識符 class:bean所對應的類 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean> </beans>
經過spring工廠獲取定義的JavaBean:session
//加載配置文件,獲取spring工廠,從容器中獲取dao和service的實現類 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //從容器中獲取service UserService accountService = (UserService) ac.getBean("userService"); System.out.println(accountService); //從容器中獲取dao UserDao userDao = (UserDao) ac.getBean("userDao"); System.out.println(userDao);
放一張被轉爛了的圖:app
能夠看到BeanFactory是工廠的頂層接口,也就是幫助咱們管理bean的,ApplicationContext是其子接口。固然,ApplicationContext除了具備BeanFactory的全部功能以外,還有國際化支持。統一資源加載策略、容器內時間發佈的特性。同時,二者對於bean的建立時機也不同,BeanFactory在須要的時候(調用getbean方法)時建立,ApplicationContext會在讀取配置以後當即建立。ide
上面給出了ApplicationContext的使用方法,BeanFactory則不太同樣(XmlBeanFactory在3.1以後已過期):函數
Resource resource=new ClassPathResource("applicationContext.xml"); BeanFactory factory = new DefaultListableBeanFactory(); BeanDefinitionReader bdr = new XmlBeanDefinitionReader((BeanDefinitionRegistry) factory); bdr.loadBeanDefinitions(resource);
ApplicationContext 接口的實現類非的三種實現類:
bean標籤的屬性:
id
:給對象在容器中提供的惟一標識,用於獲取對象class
:指定類的全限定類名。用於反射建立對象。默認狀況下調用無參構造函數。scope
:指定對象的做用範圍。singleton
:默認值,單例prototype
:多例request
:WEB 項目中,Spring 建立一個 Bean 的對象,將對象存入到 request 域中.session
:WEB 項目中,Spring 建立一個 Bean 的對象,將對象存入到 session 域中.global session
:WEB 項目中,應用在 Portlet 環境.若是沒有 Portlet 環境那麼 globalSession 至關於 session.init-method
:指定類中的初始化方法名稱(生命週期相關)。destroy-method
:指定類中銷燬方法名稱(生命週期相關)。bean的生命週期:
在ApplicationContext中:
週期 | 單例singleton | 多例prototype |
---|---|---|
對象出生 | 當應用加載,建立容器時,對象就被建立了。 | 當使用對象時,建立新的對象實例(getBean被調用) |
對象存在 | 只要容器在,對象一直活着 | 只要對象在使用中,就一直活着 |
對象死亡 | 當應用卸載,銷燬容器時,對象就被銷燬了 | 當對象長時間不用時,被java的垃圾回收器回收了 |
建立bean的三種方式:
<!-- 默認無參構造,通常用這種 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"></bean>
工廠方式:
//靜態工廠 public class BeansFacotory1 { public static Object getBeans(){ return new UserServiceImpl(); } } //示例工廠 public class BeansFacotory2 { public Object getBeans(){ return new UserServiceImpl(); } }
配置方式:
<!-- 靜態工廠方法建立對象 class:工廠類的全限定名 factory-method:工廠的靜態方法 --> <bean id="userService" class="com.bilibili.utils.BeansFacotory1" factory-method="getBeans"></bean> <!-- 實例工廠方法建立對象--> <!-- 首先配置工廠類的實例 --> <bean id="beansFactory2" class="com.bilibili.utils.BeansFacotory2"></bean> <!-- factory-bean:配置工廠類實例對象 factory-method:工廠類中用於建立對象的方法 --> <bean id="userService" factory-bean="beansFactory2" factory-method="getBeans"></bean>
小聲BB:工廠都有了還要你spring幹啥
面試官:爲何使用spring?
應聘者:由於方便?
面試官:什麼?
讓spring來管理bean的確方便😂
構造方法注入須要存在有參構造:
public class UserServiceImpl implements UserService { private String userName; private int age; private UserDao userDao; public UserServiceImpl(String userName, int age, UserDao userDao) { this.userName = userName; this.age = age; this.userDao = userDao; } }
在xml中使用constructor-arg
標籤進行注入:
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <!-- 以下3個屬性是用來指定給象中的哪一個具體屬性賦值 index: 經過下標來指定構造方法中的屬性 name: 經過參數名來指定構造方法中的屬性 type: 經過參數的類型(全限定名)來指定構造方法中的屬性 以下2個屬性是用來指定給對象中的屬性賦什麼值 value: 賦值基本類型的值 例如:string,int,double... ref : 被spring管理的其餘bean類型。必須是xml中配置的bean --> <constructor-arg name="userName" value="王者榮耀"/> <constructor-arg name="age" value="18" /> <constructor-arg name="userDao" ref="userDao" /> </bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
通常使用這種,比構造方法更靈活。
<bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <!-- property:set方法注入屬性 name:set方法的名字後面的內容,小寫開頭 例如:setUserName - userName 底層: userName - UserName - setUserName value:基本屬性類型的值 例如 String int... ref:被spring管理的bean類型的值 --> <property name="userName" value="嗚啦啦"/> <property name="age" value="20"/> <property name="userDao" ref="userDao"/> </bean> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean>
其實也是set注入,只不過能夠少些一些標籤,沒什麼用。(由於可讀性不強)
<!-- 須要在beans標籤中添加命名空間:xmlns:p="http://www.springframework.org/schema/p" --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userDao" class="com.bilibili.dao.impl.UserDaoImpl"></bean> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl" p:userName="嗚啦啦" p:age="18" p:userDao-ref="userDao"></bean> </beans>
先來UserServiceImpl實現類:
public class UserServiceImpl implements UserService { private String[] myArray; private List<String> myList; private Map<String,String> myMap; private Set<String> mySet; public String[] getMyArray() { return myArray; } public void setMyArray(String[] myArray) { this.myArray = myArray; } public List<String> getMyList() { return myList; } public void setMyList(List<String> myList) { this.myList = myList; } public Map<String, String> getMyMap() { return myMap; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public Set<String> getMySet() { return mySet; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } }
而後是xml使用特定標籤中注入:
<!-- 注入集合屬性: 使用set方法注入集合屬性: array:通常用來設置數組 list:通常用來設置list集合 map:通常用來設置map集合 --> <bean id="userService" class="com.bilibili.service.impl.UserServiceImpl"> <property name="myArray"> <array> <value>a</value> <value>b</value> <value>c</value> </array> </property> <property name="myList"> <list> <value>aa</value> <value>bb</value> <value>cc</value> </list> </property> <property name="myMap"> <map> <entry key="key1" value="value1"></entry> <entry key="key2" value="value2"></entry> </map> </property> <property name="mySet"> <set> <value>aaa</value> <value>bbb</value> <value>ccc</value> </set> </property> </bean>
bean除了使用xml進行注入,還可使用註解進行注入,只不過像JdbcTemplate
這種依賴中的類(暫時)就只能使用xml文件來配置注入(context標籤須要給beans根標籤添加命名空間):
<!-- 加載外部jdbc.properties配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 構造器方式注入數據源 --> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- 靜態方法配置dataSource --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" > <property name="driverClassName" value="${jdbc.driverClass}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean>
jdbc.properties文件
jdbc.driverClass=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring jdbc.username=root jdbc.password=root
使用註解方式進行注入時須要給beans標籤添加命名空間:
<?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.xsd"> <!-- ↑須要添加context命名空間 --> <!-- 配置註解方式掃描的包:在指定的包下進行掃描,若是發現類上面有註解,讓其裝配到容器中 --> <context:component-scan base-package="com.bilibili"/> </beans>
聲明bean的註解:
@Component("beanName")
:至關於xml配置的<bean><bean/>
標籤,註解的value屬性值至關於bean標籤的id屬性,若是不指定value屬性,默認bean的id就是類名,首字母小寫(Component:組件)@Controller
:通常用於表現層@Service
: 通常用於業務層@Respository
: 通常用於持久層注入相關注解:
@Autowired
:自動裝配,標註在須要注入的屬性上。當使用該註解注入屬性時,set方法能夠省略,當有多個相同類型的時候,bean的id必需要和屬性的名字一致,才能注入成功,不然報錯@Qualifier
:須要結合@Autowired註解一塊兒使用,在自動注入的基礎上,能夠給屬性注入指定id的bean@Resource
:直接注入指定id的bean@Value
註解用來給基本類型的屬性注入值。可使用${key}
從外部properties配置文件中引入值,須要注意properties配置文件須要在applicationContext.xml中引入做用範圍註解:
@Scope
:註解和<bean>
標籤的scope屬性的做用一致。值能夠爲prototype
和singleton
(默認)生命週期註解:
@PostConstruct
:聲明這個方法是初始化方法,對象被建立的時候調用一次。@PreDestroy
:聲明這個方式是銷燬方法,對象被銷燬的時候調用一次。xml方式和註解方式對比:
\ | xml | 註解 |
---|---|---|
bean定義 | <bean id="" class="" .../> |
@Component 衍生: @Controller @Service @Respository |
bean名稱 | 經過id 或name 屬性指定 |
經過上面三個註解的value屬性指定 |
bean注入 | property 或p命名空間 |
@Autowired 按類型注入@Qualifier 配合@Autowired指定@Resource 的name屬性,按名稱注入 |
bean做用範圍 生命週期 |
init-method destroy-method scope |
@PostConstruct @PreDestroy @Scope |
上面說到像JdbcTemplate
這種依賴中的類(暫時)就只能使用xml文件來配置注入,固然也可使用純註解進行配置。
主配置:
//聲明當前類是一個spring的配置類,用來替代xml配置文件 //獲取容器時須要使用AnnotationApplicationContext(@Configuration標註的類.class) @Configuration //用於配置容器初始化時須要掃描的包 //和xml配置中<context:component-scan base-package="com.bilibili"/>做用一致 @ComponentScan("com.bilibili") //導入其餘配置類 @Import(JdbcConfig.class) public class SpringConfig { //標註這個方法的返回值做爲一個bean而且交給spring容器管理,value屬性就是bean的id @Bean("jdbcTemplate") public JdbcTemplate getJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){ return new JdbcTemplate(dataSource); } }
外部配置:
//引入外部文件,和<context:property-placeholder location="classpath:jdbc.properties"/>做用同樣 @PropertySource("classpath:jdbc.properties") public class JdbcConfig { //使用value註解引用外部變量,這樣就不用寫死配置了。 @Value("${jdbc.url}") private String url; @Value("${jdbc.driverClass}") private String driverClass; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //標註這個方法的返回值做爲一個bean而且交給spring容器管理,value屬性就是bean的id @Bean("dataSource") public DataSource getDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setDriverClassName(driverClass); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } }
測試類:
public class SpringConfigTest { @Test public void getJdbcTemplate() { //使用AnnotationConfigApplicationContext實現類來獲取工廠 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); JdbcTemplate jdbcTemplate = (JdbcTemplate)ac.getBean("jdbcTemplate"); System.out.println("jdbcTemplate = " + jdbcTemplate); } }
在每一個單元測試類中,咱們都須要獲取Spring容器,而後獲取要測試的類:
@Before public void setUp() throws Exception { ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); accountService = (AccountService) ac.getBean("accountServiceImpl"); }
那麼能不能直接在測試類中注入要測試的bean呢?
固然是能夠。
首先添加依賴:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!-- spring5及以上版本要求junit的版本必須是4.12及以上。 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.6.RELEASE</version> </dependency>
而後給測試類配置註解:
//配置spring的單元測試運行器,自動建立spring容器 @RunWith(SpringJUnit4ClassRunner.class) //配置容器建立時依賴的配置 //xml文件方式,直接給value賦值(注意前綴classpath:) @ContextConfiguration("classpath:applicationContext.xml") //純註解方式,給classes屬性賦值 @ContextConfiguration(classes = SpringConfig.class) public class AccountServiceImplTest { //依賴注入 @Resource(name = "accountService") private AccountService accountService; }
而後就能夠愉快地在測試類中使用注入的依賴了。依賴少的時候好像並無方便多少😅