寫在前面: 最近學習Spring告一段落了,這文章可以簡單的告訴你怎麼使用Spring的IOC控制反轉,以及IOC註解開發。 作者是一個學生,沒有能力寫得太深,需要的可以去看看大佬們的手撕Spring源碼。
本文將告訴你:
IOC控制反轉是什麼?
如何使用Spring的IOC控制反轉?
XML創建bean的三種形式
IOC控制反轉的註解開發
如果對你有幫助可以點贊支持一下^ _ ^
公衆號:小白編碼
目錄
什麼是Spring?
Spring是分層的Java SE/EE應用 full-stack輕量級開源框架,以IoC(Inverse Of Control:反轉控制)和AOP(Aspect Oriented Programming:面向切面編程)爲內核,提供了展現層Spring MVC和持久層Spring JDBC以及業務層事務管理等衆多的企業級應用技術,還能整合開源世界衆多著名的第三方框架和類庫,逐漸成爲使用最多的Java EE企業應用開源框架。
Spring能做什麼?
- 方便解耦,簡化開發
通過Spring提供的IoC容器,可以將對象間的依賴關係交由Spring進行控制,避免硬編碼所造成的過度程序耦合。用戶也不必再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可以更專注於上層的應用。 - AOP編程的支持
通過Spring的AOP功能,方便進行面向切面的編程,許多不容易用傳統OOP實現的功能可以通過AOP輕鬆應付。 - 聲明式事務的支持
可以將我們從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活的進行事務的管理,提高開發效率和質量。 - 方便程序的測試
可以用非容器依賴的編程方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。 - 方便集成各種優秀框架
Spring可以降低各種框架的使用難度,提供了對各種優秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持。 - 降低JavaEE API的使用難度
Spring對JavaEE API(如JDBC、JavaMail、遠程調用等)進行了薄薄的封裝層,使這些API的使用難度大爲降低。 - Java源碼是經典學習範例
Spring的源代碼設計精妙、結構清晰、匠心獨用,處處體現着大師對Java設計模式靈活運用以及對Java技術的高深造詣。它的源代碼無意是Java技術的最佳實踐的範例。
Spring的體系結構
IOC控制反轉是什麼?
我們都知道Service層需要Dao層的支持,使用Dao獲取數據處理業務:(這裏需要AccountDao的實例)
上圖使用了工廠獲取實例。 目的就是爲了解耦。
我獲取AccountDao的實例的時候有兩種方法:
- 第一種:通過new AccountDaoImpl() 實例的方式
- 第二種:通過工廠模式,獲取AccountDao的實例
那麼問題來了?到底什麼是控制反轉?
通過下圖可知:獲取實例的方式轉變爲從工廠裏獲取 ,而不是直接new一個實例,它將獲取實例的控制權 ,交給了BeanFactory來做,而不是自己直接面對實例資源。讓工廠來面對實例資源。這種控制權發生了轉變。就叫控制反轉。
那麼如何使用Spring的IOC控制反轉?
開發前準備:
1.導入Maven依賴座標:或者官方下載: http://repo.springsource.org/libs-release-local/org/springframework/spring
<dependency>
<!--SpringIOC依賴-->
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
依賴的結構圖:
2.準備好Dao層,以及Service層:
Dao層:(接口自己寫)
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("保存一個賬戶");
}
}
Service層:(接口自己寫)
public class AccountService implements IAccountService {
private IAccountDao iAccountDao = new AccountDaoImpl();//這裏我還是先使用new的方式
@Override
public void saveAccount() {
iAccountDao.saveAccount();
}
}
Xml配置IOC容器:
<?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/spring-beans.xsd">
<!-- bean標籤:用於配置讓spring創建對象,並且存入ioc容器之中 id屬性:對象的唯一標識。
class屬性:指定要創建對象的全限定類名 -->
<!--配置AccountService的實現類,放入容器-->
<bean id="accountService" class="cn.codewhite.service.Impl.AccountService"></bean>
<!--配置AccountDao的實現類,放入容器-->
<bean id="accountDao" class="cn.codewhite.dao.Impl.AccountDaoImpl"></bean>
</beans>
測試程序:
public class Clinet {
public static void main(String[] args) {
//1.獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// IAccountService iAccountService = new AccountService();
//在使用SpringIOC控制反轉前,我們是通過new 的方式獲取Service實例
// 現在配置了IOC控制反轉,new實例交給了Spring管理,根據配置的id獲取容器裏配置的實現類
IAccountService accountService = (IAccountService) ac.getBean("accountService");
// IAccountDao accountDao = new AccountDaoImpl();原先是通過new方式獲取Dao實例
//通過字節碼文件,獲得運行時類
IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(accountService);
System.out.println(accountDao);
//測試保存賬戶
accountService.saveAccount();
}
}
運行結果:
結論:通過這個簡單的案例,我們就知道了,可以配置將實現類,配置到Spring的IOC容器中,在需要獲取Dao或者Service等其他實現類的時候,從IOC容器中獲取。而不是直接new的方式獲取實例。實現了控制反轉
XML創建bean的三種形式:
創建Bean,也就是配置bean之後(放入IOC容器),通過getBean()來獲取實例。
第一種方式:
使用默認構造函數創建。
在spring的配置文件中使用bean標籤,配以id和class屬性之後,且沒有其他屬性和標籤時。
採用的就是默認構造函數創建bean對象,此時如果類中沒有默認構造函數(空參構造器),則對象無法創建。
<bean id="accountService" class="cn.codewhite.service.Impl.AccountServiceImpl"></bean>
AccountService實現類:
public class AccountServiceImpl implements IAccountService {
//必須是空參構造器,否則第一種方式無法創建對象
public AccountServiceImpl() {
}
@Override
public void saveAccount() {
System.out.println("service中的saveAccount()執行了");
}
}
配置完之後,就可以通過根據配置的id獲取實例。
//1.獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根據id獲取Bean對象,獲取實例
IAccountService accountService1 = (IAccountService) ac.getBean("accountService");
第二種方式:
使用普通工廠中的方法創建對象(使用某個類中的方法創建對象,並存入spring容器)
<!--配置工廠到容器-->
<bean id="instanceFactory" class="cn.codewhite.factory.InstanceFactory"></bean>
<!--配置accountservice實例,是從工廠的getAccountService裏獲取的實例-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
工廠類: (AccountServce實現類看上邊)
/**
* 模擬一個工廠類(該類可能是存在於jar包中的,我們無法通過修改源碼的方式來提供默認構造函數)
*/
public class InstanceFactory {
//獲取AccountService的實例
public IAccountService getAccountService() {
return new AccountServiceImpl();
}
}
測試: 此時的實例service的實例是從配置過的IOC裏的工廠裏獲取的實例。
public class Clinet {
public static void main(String[] args) {
//1.獲取核心容器對象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根據id獲取Bean對象
IAccountService accountService = (IAccountService) ac.getBean("accountService");
accountService.saveAccount();
}
}
結果:
service中的saveAccount()執行了
第三種方式:
使用工廠中的靜態方法創建對象(使用某個類中的靜態方法創建對象,並存入spring容器)
xml:(配置工廠裏的靜態方法)
<bean id="accountService" class="cn.codewhite.factory.StaticFactory" factory-method="getAccountService"></bean>
工廠類:
public class StaticFactory {
//靜態方法
public static IAccountService getAccountService() {
//返回AccontService實例
return new AccountServiceImpl();
}
}
測試如第二種方式的結果。
bean標籤
作用:用於配置對象讓spring來創建的。
默認情況下它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。
屬性:
-
id:給對象在容器中提供一個唯一標識。用於獲取對象。
-
class:指定類的全限定類名。用於反射創建對象。默認情況下調用無參構造函數。
-
scope:指定對象的作用範圍。常用: singleton :默認值,單例的. prototype :多例的.
- request :WEB項目中,Spring創建一個Bean的對象,將對象存入到request域中. 作用於web應用的請求範圍
- session :WEB項目中,Spring創建一個Bean的對象,將對象存入到session域中.作用於web應用的會話範圍
- global session :WEB項目中,應用在Portlet環境.如果沒有Portlet環境那麼globalSession相當於session. 作用於集羣環境的會話範圍(全局會話範圍),當不是集羣環境時,它就是session
-
init-method:指定類中的初始化方法名稱。
-
destroy-method:指定類中銷燬方法名稱。
bean依賴注入
**依賴注入:Dependency Injection。(DI)**它是spring框架核心ioc的具體實現。 簡單說就是:(爲指定的類裏面的屬性賦值實例。相當於賦值,)
依賴關係的管理:我們的程序在編寫時,通過控制反轉,把對象的創建交給了spring,但是代碼中不可能出現沒有依賴的情況。ioc解耦只是降低他們的依賴關係,但不會消除。
IOC的作用: 降低程序間的耦合(依賴關係)
例如:我們的業務層仍會調用持久層的方法。 那這種業務層和持久層的依賴關係,在使用spring之後,就讓spring來維護了。 簡單的說,就是坐等**框架把持久層對象傳入業務層,而不用我們自己去獲取。**在當前類需要用到其他類的對象,我們只需要在配置文件中說明
構造函數注入:
使用的標籤:constructor-arg
標籤出現的位置:bean標籤的內部
標籤中的屬性:
type:用於指定要注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型
index:用於指定要注入的數據給構造函數中指定索引位置的參數賦值。索引的位置是從0開始
name: ★用於指定給構造函數中指定名稱的參數賦值 常用的
以上三個用於指定給構造函數中哪個參數賦值
value: 用於提供基本類型和String類型的數據
ref: 用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象
xml配置:
<!--將Service放入IOC容器,以及配置Service裏的屬性依賴-->
<bean id="accountService" class="cn.codewhite.service.Impl.AccountServiceImpl">
<!--配置Service裏的String name屬性:賦值爲:小白-->
<constructor-arg name="name" value="小白"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<!--這種類型需要引入,所以使用ref引入-->
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<bean id="now" class="java.util.Date"></bean>
Service:
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
}
set方法注入(Bean中必須設有Set方法) ★ (更常用)
涉及的標籤:property
出現的位置:bean標籤的內部
標籤的屬性:
name: 用於指定注入時所調用的set方法名稱
value: 用於提供基本類型和String類型的數據
ref: 用於指定其他的bean類型數據。它指的就是在spring的Ioc核心容器中出現過的bean對象
優勢: 創建對象時沒有明確的限制,可以直接使用默認構造函數
弊端: 如果有某個成員必須有值,則獲取對象是有可能set方法沒有執行。
xml:
<!--將Service放入IOC容器,以及配置Service裏的屬性依賴-->
<bean id="accountService2" class="cn.codewhite.service.Impl.AccountServiceImpl2">
<!--配置Service裏的String name屬性:通過set方法賦值爲:小白-->
<property name="name" value="小白"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
Service: (需要提供set方法)
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
複雜類型的注入/集合類型的注入:
用於給List結構集合注入的標籤: list
、array
、set
用於個Map結構集合注入的標籤: map
、 props
結構相同,標籤可以互換
Bean.xml:
<!--將Service放入IOC容器,以及配置Service裏的屬性依賴-->
<bean id="accountService3" class="cn.codewhite.service.Impl.AccountServiceImpl3">
<!--以下是List結構-->
<property name="myStrs">
<array>
<value>aaa</value>
</array>
</property>
<property name="myList">
<list>
<value>AA</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
</set>
</property>
<property name="myMap">
<!--以下是Map結構-->
<map>
<entry key="keyA" value="AAA"></entry>
<entry key="keyB" >
<value>BBB</value>
</entry>
<entry key="keyC" value="CCC"></entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="PropA">AAA</prop>
</props>
</property>
</bean>
Servcie: (set方法我這裏沒寫!需要自己補充,否則無法注入)
public class AccountServiceImpl3 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myProps;
}
IOC控制反轉的註解開發
環境搭建:
Dao層:
AccountDaoImpl:
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("accountDao1中:保存一個賬戶");
}
}
AccountDaoImpl2:
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
@Override
public void saveAccount() {
System.out.println("accountDao2中:保存一個賬戶");
}
}
Service:
@Service("accountService")
@Scope("singleton")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name = "accountDao2")
private IAccountDao iAccountDao;
@PostConstruct
public void init() {
System.out.println("init創建。。。。");
}
@PreDestroy
public void destroy() {
System.out.println("destroy銷燬。。。。");
}
@Override
public void saveAccount() {
iAccountDao.saveAccount();
}
}
綁定使用註解的包:(非常重要)
<?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">
<!--配置cn.codewhite包下所有的註解都能被掃描到-->
<context:component-scan base-package="cn.codewhite"></context:component-scan>
</beans>
用於創建對象的註解
相當於:<bean id="" class="">
@Component
作用: 把資源讓spring來管理。相當於在xml中配置一個bean。
屬性: value:指定bean的id。 如果不指定value屬性,默認bean的id是當前類的類名。首字母小寫。
@Controller @Service @Repository
他們三個註解都是針對一個的衍生註解,他們的作用及屬性都是一模一樣的 .他們三個是spring框架爲我們提供明確的三層使用的註解,使我們的三層對象更加清晰
@Controller:一般用於表現層的註解。
@Service:一般用於業務層的註解。
@Repository:一般用於持久層的註解。
細節:如果註解中有且只有一個屬性要賦值時,且名稱是value,value在賦值是可以不寫。
用於注入數據(依賴注入)的註解
相當於:
<property name="" ref="">
<property name="" value="">
@Autowired
作用: 自動按照類型注入。只要容器中有唯一的一個bean對象類型和要注入的變量類型匹配,就可以注入成功
如果ioc容器中沒有任何bean的類型和要注入的變量類型匹配,則報錯。
如果Ioc容器中有多個類型匹配時: 出現位置:可以是變量上,也可以是方法上
細節:在使用註解注入時,set方法就不是必須的了,可以省略。
@Qualifier
作用:在按照類中注入的基礎之上再按照名稱注入。它在給類成員注入時不能單獨使用。必須和@Autowire一起使用;但是給方法參數注入時,可以獨立使用。
屬性:
value:用於指定注入bean的id。
AccountDao1:
調用Service結果:
給方法傳入:
@Resource
作用: 直接按照Bean的id注入。可以獨立使用。它也只能注入其他bean類型。
屬性: name:指定bean的id。
調用Service測試結果:
以上三個注入都只能注入其他bean類型的數據,而基本類型和String類型無法使用上q述註解實現。
另外,集合類型的注入只能通過XML來實現。
@Value
作用:用於注入基本類型和String類型的數據 ,
屬性:value:用於指定數據的值。它可以使用spring中SpEL(也就是spring的el表達式)
SpEL的寫法:${表達式}
注入配置文件中的key:(需要使用@PropertySource(“classpath:jdbcConfig.properties”))引用
配置文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123
用於改變作用範圍的註解
相當於:<bean id="" class="" scope="">
@Scope
作用: 指定bean的作用範圍。
屬性: value:指定範圍的值。
取值:(singleton或prototype 常用)request,session,globalsession
測試是否單例:
和生命週期相關的註解:(瞭解)
相當於:
<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct
作用: 用於指定初始化方法。
@PostConstruct
作用: 用於指定銷燬方法。
測試結果:
寫在後邊:
寫得不好,請見諒,如果需要PDF版的可以找我。