為什么要用Spring的依賴注入,解耦,統(tǒng)一管理,面向接口編程,易于程序的擴展,有利于程序的維護
解耦合,比如A需要用到B,我們需要把B對象 set 到 A對象里,那C也需要用到B對象,此時也需要把B set 到C里,那么怎么獲取到B對象的引用了?
比如A用到B,C,D,B又用到E,C又用到F,D又用到G,這樣靠手動操作是不是很麻煩。
常的java開發(fā)中,程序員在某個類中需要依賴其它類的方法,則通常是new一個依賴類再調(diào)用類實例的方法,這種開發(fā)存在的問題是new的類實例不好統(tǒng)一管理,spring提出了依賴注入的思想,即依賴類不由程序員實例化,而是通過spring容器幫我們new指定實例并且將實例注入到需要該對象的類中。依賴注入的另一種說法是“控制反轉(zhuǎn)”,通俗的理解是:平常我們new一個實例,這個實例的控制權(quán)是我們程序員,而控制反轉(zhuǎn)是指new實例工作不由我們程序員來做而是交給spring容器來做。
面向接口編程,接口不變,改動實現(xiàn)類就可以,不需要改寫現(xiàn)有代碼,減少對系統(tǒng)的影響。
在使用面向接口的編程過程中,將具體邏輯與實現(xiàn)分開,減少了各個類之間的相互依賴,當(dāng)各個類變化時,不需要對已經(jīng)編寫的系統(tǒng)進行改動,添加新的實現(xiàn)類就可以了,不在擔(dān)心新改動的類對系統(tǒng)的其他模塊造成影響。
注入方式
構(gòu)造函數(shù)注入,setter方法注入,注解注入
1.構(gòu)造函數(shù)注入
在bean標(biāo)簽的內(nèi)部使用constructor-arg標(biāo)簽就可以進行構(gòu)造函數(shù)注入了。
constructor-arg標(biāo)簽的屬性:
type:用于指定要注入的數(shù)據(jù)的數(shù)據(jù)類型,該數(shù)據(jù)類型也是構(gòu)造函數(shù)中某個或某些參數(shù)的類型
index:用于指定要注入的數(shù)據(jù)給構(gòu)造函數(shù)中指定索引位置的參數(shù)賦值,索引的位置從0開始
name:用于給指定構(gòu)造函數(shù)中指定名稱的參數(shù)賦值
value:用于提供基本類型和String類型的數(shù)據(jù)
ref:用于指定其他的bean類型數(shù)據(jù),就是在IOC容器中出現(xiàn)過的bean對象
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="com.sks.service.imp.AccountServiceImpl">
<constructor-arg type="java.lang.String" value="張三"/>
<constructor-arg index="1" value="20"/>
<constructor-arg name="birthday" ref="birthday"/>
</bean>
<bean id="birthday" class="java.util.Date"/>
</beans>
AccountServiceImpl 類
public class AccountServiceImpl implements AccountService {
private String name;
private Integer age;
private Date birthday;
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String, String> myMap;
private Properties myProps;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public AccountServiceImpl(String name, Integer age, Date birthday) {
System.out.println("含參的構(gòu)造方法被調(diào)用了");
this.name = name;
this.age = age;
this.birthday = birthday;
}
public AccountServiceImpl() {
System.out.println("構(gòu)造方法調(diào)用");
}
@Override
public int addMoney(int money) {
System.out.println("向賬戶中加錢:" + money);
return 0;
}
@Override
public void saveAccount(Account account) {
System.out.println("saveAccount方法執(zhí)行了");
}
}
測試
@Test
public void test8() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");;
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
System.out.println(accountService.toString());
}
2.setter方法注入
在bean標(biāo)簽內(nèi)部使用property標(biāo)簽進行配置。
property標(biāo)簽的屬性:
name:用于指定注入時所調(diào)用的set方法名稱
value:用于提供基本類型和String類型的數(shù)據(jù)
ref:用于指定其他的bean類型數(shù)據(jù)
這里面我們注入了基本類型、包裝類型、日期類型數(shù)據(jù)。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService" class="com.sks.service.imp.AccountServiceImpl">
<!--注入基本類型、包裝類型、日期類型數(shù)據(jù)-->
<property name="age" value="22"/>
<property name="name" value="李四"/>
<property name="birthday" ref="birthday"/>
</bean>
<bean id="birthday" class="java.util.Date"/>
</beans>
優(yōu)勢:創(chuàng)建對象時沒有明確的限制,可以直接使用默認(rèn)構(gòu)造函數(shù)。
缺點:如果又某個成員必須有值,則獲取對象有可能是set方法沒有執(zhí)行。
3.對集合類型數(shù)據(jù)進行注入
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountService2" class="com.sks.service.imp.AccountService2Impl">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
</list>
</property>
<property name="mySet">
<set>
<value>set1</value>
<value>set2</value>
<value>set3</value>
</set>
</property>
<property name="myProps">
<props>
<prop key="name">柯森</prop>
<prop key="age">23</prop>
</props>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
<entry key="key3">
<value>value3</value>
</entry>
</map>
</property>
</bean>
</beans>
4.注解注入
假設(shè)此時只有一個AccountDao的實現(xiàn)類,并且這個類也加上了@Repository注解,那么我們這樣注入是可以成功的,但是如果容器中存在多個AccountDao的實現(xiàn)類,此時僅僅使用AccountDao是不能完成數(shù)據(jù)注入的,需要配合@Qualifier注解使用注入數(shù)據(jù)。
@Component
public class AccountService4Impl implements AccountService3 {
//錯誤寫法,默認(rèn)會去容器中查找名稱為accountDao的bean
//@Autowired
//private AccountDao accountDao;
//正確寫法
//@Autowired
//private AccountDao accountDao1
//正確寫法
@Autowired
@Qualifier("accountDao1")
private AccountDao accountDao;
@Override
public void addMoney(int money) {
System.out.println("向賬戶中加錢....AccountService3Impl");
}
}
@Value注解的基本使用
在使用@Value注入基本類型和String類型的數(shù)據(jù)時使用"#“號;使用@Value讀取配置文件的值時需要使用”$"符號,同時使用@PropertySource注解指定配置文件的位置。
@Component
@PropertySource("classpath:db.properties")
public class AccountService4Impl implements AccountService3 {
@Autowired
@Qualifier("accountDao1")
private AccountDao accountDao;
//使用SPEL表達式只注入值
@Value("#{19 - 9}")
private int age;
@Value("zhangsan")
private String name;
//讀取操作系統(tǒng)的名稱
@Value("#{systemProperties['os.name']}")
private String osname;
//讀取數(shù)據(jù)庫配置文件中的值
@Value("${password}")
private String password;
@Override
public void addMoney(int money) {
System.out.println("向賬戶中加錢....AccountService3Impl");
}
}
通過 xml 和 annotation 獲取到 Bean 的描述信息后, 肯定需要將其統(tǒng)一存儲和管理起來。 在 Spring 框架代碼中, Bean 的描述信息的最終存儲形式即為 BeanDefinition。
Spring的核心是控制反轉(zhuǎn)(IoC)和面向切面(AOP)
方便解耦,簡化開發(fā) (高內(nèi)聚低耦合)
Spring就是一個大工廠(容器),可以將所有對象創(chuàng)建和依賴關(guān)系維護,交給Spring管理,spring工廠是用于生成bean
AOP編程的支持
Spring提供面向切面編程,可以方便的實現(xiàn)對程序進行權(quán)限攔截、運行監(jiān)控等功能
聲明式事務(wù)的支持
方便集成各種優(yōu)秀框架
Spring不排斥各種優(yōu)秀的開源框架,其內(nèi)部提供了對各種優(yōu)秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持
降低JavaEE API的使用難度
Spring 對JavaEE開發(fā)中非常難用的一些API(JDBC、JavaMail、遠(yuǎn)程調(diào)用等),都提供了封裝,使這些API應(yīng)用難度大大降低
Spring容器就是一個實現(xiàn)了ApplicationContext接口的對象
Spring 容器是 Spring 框架的核心,是用來管理對象的。容器將創(chuàng)建對象,把它們連接在一起,配置它們,并管理他們的整個生命周期從創(chuàng)建到銷毀。
如何實例化一個Bean?
1.構(gòu)造方法
2.通過靜態(tài)工廠方法
3.通過實例工廠方法
Spring通過解析我們的配置元數(shù)據(jù),以及我們提供的類對象得到一個Beanfinition對象。通過這個對象可以實例化出一個java bean對象。
依賴注入
依賴注入主要分為兩種方式
1.構(gòu)造函數(shù)注入
2.Setter方法注入
1.IOC控制反轉(zhuǎn)
將我們創(chuàng)建對象的控制權(quán)反轉(zhuǎn)交給spring去創(chuàng)建
- 之前創(chuàng)建對象是這么寫的
UserService userService = new UserServiceImpl();
userService.addUser();
- 現(xiàn)在都由spring來創(chuàng)建,后面開發(fā)也都通過掃描配置文件里注入的bean,對象直接從spring容器獲取不需要自己創(chuàng)建
在xml里配置bean
<bean id="userServiceId" class="com.itheima.a_ioc.UserServiceImpl"></bean>
1 獲得容器
String xmlPath = "com/itheima/a_ioc/beans.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
2獲得內(nèi)容 -- 不需要自己new,都是從spring容器通過id獲得
UserService userService = (UserService) applicationContext.getBean("userServiceId");
userService.addUser();
2.DI依賴注入
service里是這么寫的,通過setBookDao方法進行注入bookDao
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void addBook(){
this.bookDao.save();
}
上面可以像下面這樣用spring配置文件實現(xiàn),使用<property> 用于進行屬性注入
<!-- 創(chuàng)建service -->
<bean id="bookServiceId" class="com.itheima.b_di.BookServiceImpl" >
<property name="bookDao" ref="bookDaoId"></property>
</bean>
<!-- 創(chuàng)建dao實例 -->
<bean id="bookDaoId" class="com.itheima.b_di.BookDaoImpl"></bean>
3.核心Api
BeanFactory :這是一個工廠,用于生成任意bean,采取延遲加載,第一次getBean時才會初始化Bean,懶加載
ApplicationContext:是BeanFactory的子接口,功能更強大。(國際化處理、事件傳遞、Bean自動裝配、各種不同應(yīng)用層的Context實現(xiàn))。當(dāng)配置文件被加載,就進行對象實例化,餓漢式,直接會實例bean
ClassPathXmlApplicationContext 用于加載classpath(類路徑、src)下的xml
加載xml運行時位置 --> /WEB-INF/classes/...xml
FileSystemXmlApplicationContext 用于加載指定盤符下的xml
加載xml運行時位置 --> /WEB-INF/...xml
通過java web ServletContext.getRealPath() 獲得具體盤符
4.裝配bean
- 默認(rèn)構(gòu)造
<bean id="" class=""> - 靜態(tài)工廠
常用與spring整合其他框架(工具)
靜態(tài)工廠:用于生成實例對象,所有的方法必須是static
<bean id="" class="工廠全限定類名" factory-method="靜態(tài)方法">
例如:bean.xml配置文件
<bean id="userServiceId" class="com.itheima.c_inject.b_static_factory.MyBeanFactory" factory-method="createService"></bean>
public class MyBeanFactory {
public UserService createService(){
return new UserServiceImpl();
}
}
String xmlPath = "com/itheima/c_inject/b_static_factory/beans.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = applicationContext.getBean("userServiceId" ,UserService.class);
userService.addUser();
通過userServiceId獲得factory并調(diào)用createService方法
- 實例工廠
必須先有工廠實例對象,通過實例對象創(chuàng)建對象。提供所有的方法都是“非靜態(tài)”的。
5.作用域
singleton 單例,默認(rèn)值。
prototype 多例,每執(zhí)行一次getBean將獲得一個實例。例如:struts整合spring,配置action多例。
6.生命周期
- 初始化和銷毀
<bean id="" class="" init-method="初始化方法名稱" destroy-method="銷毀的方法名稱">
目標(biāo)類
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("e_lifecycle add user");
}
public void myInit(){
System.out.println("初始化");
}
public void myDestroy(){
System.out.println("銷毀");
}
}
spring配置
<!--
init-method 用于配置初始化方法,準(zhǔn)備數(shù)據(jù)等
destroy-method 用于配置銷毀方法,清理資源等
-->
<bean id="userServiceId" class="com.itheima.e_lifecycle.UserServiceImpl"
init-method="myInit" destroy-method="myDestroy" ></bean>
<!-- 將后處理的實現(xiàn)類注冊給spring -->
<bean class="com.itheima.e_lifecycle.MyBeanPostProcessor"></bean>
測試
@Test
public void demo02() throws Exception{
//spring 工廠
String xmlPath = "com/itheima/e_lifecycle/beans.xml";
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserService userService = (UserService) applicationContext.getBean("userServiceId");
userService.addUser();
//要求:1.容器必須close,銷毀方法執(zhí)行; 2.必須是單例的
//applicationContext.getClass().getMethod("close").invoke(applicationContext);
// * 此方法接口中沒有定義,實現(xiàn)類提供
applicationContext.close();
}
BeanPostProcessor 后處理Bean
spring 提供一種機制,只要實現(xiàn)此接口BeanPostProcessor,并將實現(xiàn)類提供給spring容器,spring容器將自動執(zhí)行,在初始化方法前執(zhí)行before(),在初始化方法后執(zhí)行after() 。 配置<bean class="">
spring提供工廠勾子,用于修改實例對象,可以生成代理對象,是AOP底層。
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("前方法 : " + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
System.out.println("后方法 : " + beanName);
// bean 目標(biāo)對象
// 生成 jdk 代理
return Proxy.newProxyInstance(
MyBeanPostProcessor.class.getClassLoader(),
bean.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------開啟事務(wù)");
//執(zhí)行目標(biāo)方法
Object obj = method.invoke(bean, args);
System.out.println("------提交事務(wù)");
return obj;
}});
}
}
將后處理的實現(xiàn)類注冊給spring
<bean class="com.itheima.e_lifecycle.MyBeanPostProcessor"></bean>
問題1:后處理bean作用某一個目標(biāo)類,還是所有目標(biāo)類?
所有
問題2:如何只作用一個?
通過“參數(shù)2”beanName進行控制
Bean的生命周期,生命周期詳情
- instantiate bean對象實例化
- populate properties 封裝屬性
- 如果Bean實現(xiàn)BeanNameAware 執(zhí)行 setBeanName
- 如果Bean實現(xiàn)BeanFactoryAware 或者 ApplicationContextAware 設(shè)置工廠 setBeanFactory 或者上下文對象 setApplicationContext
- 如果存在類實現(xiàn) BeanPostProcessor(后處理Bean) ,執(zhí)行postProcessBeforeInitialization
- 如果Bean實現(xiàn)InitializingBean 執(zhí)行 afterPropertiesSet
- 調(diào)用<bean init-method="init"> 指定初始化方法 init
- 如果存在類實現(xiàn) BeanPostProcessor(處理Bean) ,執(zhí)行postProcessAfterInitialization
- 執(zhí)行業(yè)務(wù)處理
- 如果Bean實現(xiàn) DisposableBean 執(zhí)行 destroy
- 調(diào)用<bean destroy-method="customerDestroy"> 指定銷毀方法 customerDestroy