【JAVA】Spring依賴注入

為什么要用Spring的依賴注入,解耦,統一管理,面向接口編程,易于程序的擴展,有利于程序的維護

解耦合,比如A需要用到B,我們需要把B對象 set 到 A對象里,那C也需要用到B對象,此時也需要把B set 到C里,那么怎么獲取到B對象的引用了?
比如A用到B,C,D,B又用到E,C又用到F,D又用到G,這樣靠手動操作是不是很麻煩。

常的java開發中,程序員在某個類中需要依賴其它類的方法,則通常是new一個依賴類再調用類實例的方法,這種開發存在的問題是new的類實例不好統一管理,spring提出了依賴注入的思想,即依賴類不由程序員實例化,而是通過spring容器幫我們new指定實例并且將實例注入到需要該對象的類中。依賴注入的另一種說法是“控制反轉”,通俗的理解是:平常我們new一個實例,這個實例的控制權是我們程序員,而控制反轉是指new實例工作不由我們程序員來做而是交給spring容器來做。

面向接口編程,接口不變,改動實現類就可以,不需要改寫現有代碼,減少對系統的影響。
在使用面向接口的編程過程中,將具體邏輯與實現分開,減少了各個類之間的相互依賴,當各個類變化時,不需要對已經編寫的系統進行改動,添加新的實現類就可以了,不在擔心新改動的類對系統的其他模塊造成影響。

注入方式

構造函數注入,setter方法注入,注解注入

1.構造函數注入

在bean標簽的內部使用constructor-arg標簽就可以進行構造函數注入了。
constructor-arg標簽的屬性:
type:用于指定要注入的數據的數據類型,該數據類型也是構造函數中某個或某些參數的類型
index:用于指定要注入的數據給構造函數中指定索引位置的參數賦值,索引的位置從0開始
name:用于給指定構造函數中指定名稱的參數賦值
value:用于提供基本類型和String類型的數據
ref:用于指定其他的bean類型數據,就是在IOC容器中出現過的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("含參的構造方法被調用了");
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public AccountServiceImpl() {
        System.out.println("構造方法調用");
    }

    @Override
    public int addMoney(int money) {
        System.out.println("向賬戶中加錢:" + money);
        return 0;
    }

    @Override
    public void saveAccount(Account account) {
        System.out.println("saveAccount方法執行了");
    }
}

測試

@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標簽內部使用property標簽進行配置。
property標簽的屬性:
name:用于指定注入時所調用的set方法名稱
value:用于提供基本類型和String類型的數據
ref:用于指定其他的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">
        <!--注入基本類型、包裝類型、日期類型數據-->
       <property name="age" value="22"/>
        <property name="name" value="李四"/>
        <property name="birthday" ref="birthday"/>
    </bean>

    <bean id="birthday" class="java.util.Date"/>
</beans>

優勢:創建對象時沒有明確的限制,可以直接使用默認構造函數。
缺點:如果又某個成員必須有值,則獲取對象有可能是set方法沒有執行。

3.對集合類型數據進行注入
<?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.注解注入

假設此時只有一個AccountDao的實現類,并且這個類也加上了@Repository注解,那么我們這樣注入是可以成功的,但是如果容器中存在多個AccountDao的實現類,此時僅僅使用AccountDao是不能完成數據注入的,需要配合@Qualifier注解使用注入數據。

@Component
public class AccountService4Impl implements AccountService3 {

    //錯誤寫法,默認會去容器中查找名稱為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類型的數據時使用"#“號;使用@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;

    //讀取操作系統的名稱
    @Value("#{systemProperties['os.name']}")
    private String osname;

    //讀取數據庫配置文件中的值
    @Value("${password}")
    private String password;

    @Override
    public void addMoney(int money) {
        System.out.println("向賬戶中加錢....AccountService3Impl");
    }
}

通過 xml 和 annotation 獲取到 Bean 的描述信息后, 肯定需要將其統一存儲和管理起來。 在 Spring 框架代碼中, Bean 的描述信息的最終存儲形式即為 BeanDefinition。

Spring的核心是控制反轉(IoC)和面向切面(AOP)

方便解耦,簡化開發 (高內聚低耦合)
Spring就是一個大工廠(容器),可以將所有對象創建和依賴關系維護,交給Spring管理,spring工廠是用于生成bean

AOP編程的支持
Spring提供面向切面編程,可以方便的實現對程序進行權限攔截、運行監控等功能
聲明式事務的支持

方便集成各種優秀框架
Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持

降低JavaEE API的使用難度
Spring 對JavaEE開發中非常難用的一些API(JDBC、JavaMail、遠程調用等),都提供了封裝,使這些API應用難度大大降低

Spring容器就是一個實現了ApplicationContext接口的對象
Spring 容器是 Spring 框架的核心,是用來管理對象的。容器將創建對象,把它們連接在一起,配置它們,并管理他們的整個生命周期從創建到銷毀。

如何實例化一個Bean?

1.構造方法
2.通過靜態工廠方法
3.通過實例工廠方法

Spring通過解析我們的配置元數據,以及我們提供的類對象得到一個Beanfinition對象。通過這個對象可以實例化出一個java bean對象。

依賴注入

依賴注入主要分為兩種方式
1.構造函數注入
2.Setter方法注入

1.IOC控制反轉

將我們創建對象的控制權反轉交給spring去創建

  • 之前創建對象是這么寫的
UserService userService = new UserServiceImpl();
userService.addUser();
  • 現在都由spring來創建,后面開發也都通過掃描配置文件里注入的bean,對象直接從spring容器獲取不需要自己創建
在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獲得內容 -- 不需要自己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配置文件實現,使用<property> 用于進行屬性注入

<!-- 創建service -->
<bean id="bookServiceId" class="com.itheima.b_di.BookServiceImpl" >
   <property name="bookDao" ref="bookDaoId"></property>
</bean>
   
<!-- 創建dao實例 -->
<bean id="bookDaoId" class="com.itheima.b_di.BookDaoImpl"></bean>

3.核心Api

BeanFactory :這是一個工廠,用于生成任意bean,采取延遲加載,第一次getBean時才會初始化Bean,懶加載
ApplicationContext:是BeanFactory的子接口,功能更強大。(國際化處理、事件傳遞、Bean自動裝配、各種不同應用層的Context實現)。當配置文件被加載,就進行對象實例化,餓漢式,直接會實例bean

ClassPathXmlApplicationContext 用于加載classpath(類路徑、src)下的xml
加載xml運行時位置 --> /WEB-INF/classes/...xml

FileSystemXmlApplicationContext 用于加載指定盤符下的xml
加載xml運行時位置 --> /WEB-INF/...xml
通過java web ServletContext.getRealPath() 獲得具體盤符

4.裝配bean

  • 默認構造
    <bean id="" class="">
  • 靜態工廠
    常用與spring整合其他框架(工具)
    靜態工廠:用于生成實例對象,所有的方法必須是static
    <bean id="" class="工廠全限定類名" factory-method="靜態方法">

例如: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并調用createService方法
  • 實例工廠
    必須先有工廠實例對象,通過實例對象創建對象。提供所有的方法都是“非靜態”的。

5.作用域

image.png

singleton 單例,默認值。
prototype 多例,每執行一次getBean將獲得一個實例。例如:struts整合spring,配置action多例。

6.生命周期

  • 初始化和銷毀
    <bean id="" class="" init-method="初始化方法名稱" destroy-method="銷毀的方法名稱">
    目標類
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 用于配置初始化方法,準備數據等
    destroy-method 用于配置銷毀方法,清理資源等
-->
<bean id="userServiceId" class="com.itheima.e_lifecycle.UserServiceImpl" 
init-method="myInit" destroy-method="myDestroy" ></bean>
    
<!-- 將后處理的實現類注冊給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,銷毀方法執行; 2.必須是單例的
       //applicationContext.getClass().getMethod("close").invoke(applicationContext);
    // * 此方法接口中沒有定義,實現類提供
    applicationContext.close(); 
}

BeanPostProcessor 后處理Bean

spring 提供一種機制,只要實現此接口BeanPostProcessor,并將實現類提供給spring容器,spring容器將自動執行,在初始化方法前執行before(),在初始化方法后執行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 目標對象
        // 生成 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("------開啟事務");
                            
                            //執行目標方法
                            Object obj = method.invoke(bean, args);
                            
                            System.out.println("------提交事務");
                            return obj;
                        }});
    }
}

將后處理的實現類注冊給spring
<bean class="com.itheima.e_lifecycle.MyBeanPostProcessor"></bean>

問題1:后處理bean作用某一個目標類,還是所有目標類?
所有
問題2:如何只作用一個?
通過“參數2”beanName進行控制

Bean的生命周期,生命周期詳情

  1. instantiate bean對象實例化
  2. populate properties 封裝屬性
  3. 如果Bean實現BeanNameAware 執行 setBeanName
  4. 如果Bean實現BeanFactoryAware 或者 ApplicationContextAware 設置工廠 setBeanFactory 或者上下文對象 setApplicationContext
  5. 如果存在類實現 BeanPostProcessor(后處理Bean) ,執行postProcessBeforeInitialization
  6. 如果Bean實現InitializingBean 執行 afterPropertiesSet
  7. 調用<bean init-method="init"> 指定初始化方法 init
  8. 如果存在類實現 BeanPostProcessor(處理Bean) ,執行postProcessAfterInitialization
  9. 執行業務處理
  10. 如果Bean實現 DisposableBean 執行 destroy
  11. 調用<bean destroy-method="customerDestroy"> 指定銷毀方法 customerDestroy

參考:
http://www.lxweimin.com/p/0e629af94415

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,974評論 6 342
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,779評論 18 399
  • 北京人藝原創小劇場話劇《催眠》自2016年8月在實驗劇場首演以來,以頗具懸疑感的情節與極富現實意義的指向性贏得了不...
    易鹿閱讀 819評論 0 0
  • 著\MX先生 第二章 (上) 鬼上身? 林小曉只見眼前一片漆黑,她不知被誰點了睡穴,只能昏...
    MX先生閱讀 357評論 0 1