2、spring注入及自動裝配

項羽齊發表於2018-03-26

     

1. 課程回顧 1-1

1.1. Spring 概述 1-1

1.2. Spring IOC 概述 1-2

1.3. Spring IOC 程式設計 1-2

2. Spring Bean依賴 2-2

2.1.1. 依賴注入基礎 2-2

2.1.2. 依賴注入進階 2-2

2.1.3. 依賴值的自動裝配 2-5

3. Spring 註解應用 3-6

3.1. Spring 註解概述 3-6

3.2. Spring 註解基本應用 3-6

3.2.1. 常用註解說明 3-6

3.2.2. 註解應用案例 3-7

3.2.3. 配置註解掃描 3-7

3.2.4. 編寫測試類獲取bean 3-8

3.3. Spring 註解應用增強 3-8

3.3.1. 作用域及生命週期 3-8

3.3.2. 延遲載入配置 3-8

3.3.3. 自動裝配配置 3-9

4. 總結 4-9

4.1. 重點和難點分析 4-9

4.2. 常見FAQ 4-9

 

 

  1. 課程回顧

 

    1.1. Spring 概述

      1. Spring 是什麼?(框架,半成品)
      2. Spring 能解決什麼問題(物件導向,面向切面,面向服務)
      3. Spring 框架核心?(IOC,AOP,MVC,…)

           個人認為:Spring 最強大是它的資源整合能力。

 

    1.2. Spring IOC 概述

        1. IOC是什麼?(控制反轉:由spring構建物件,管理物件依賴)
        2. IOC 應用優勢?(解耦,更好的管理物件,使用系統資源)
        3. IOC 的核心?(工廠,配置,依賴注入)
        4. IOC 程式設計步驟?(類,配置,獲取)

 

    1.3. Spring IOC 程式設計

      1. Spring Bean物件初始化,作用域,宣告週期,延遲載入

            1) Bean型別的編寫(修飾符,構造方法)

            2) Bean 的配置(applicationContext.xml)

            3) Bean 的作用域(singleton,prototype)

            4) Bean 的生命週期(生命週期方法的使用)

            5) Bean 的延遲載入(區域性lazy-init,全域性 default-lazy-init)

 

      1. Spring Bean依賴(依賴注入,自動裝配)

            1) 依賴注入(DI)的定義(通過spring為類中的屬性注入值)

            2) 依賴注入的實現(set注入,構造注入)

            3) 依賴注入中的集合值的注入(陣列,list,set,map)

            4) 依賴注入中的自動裝配(未講)

 

    2. Spring Bean依賴

        2.0.1. 依賴注入基礎

          重點了解set,構造注入的方式以及單個值如何實現注入。

        2.0.2. 依賴注入進階

          重點了解集合型別值的注入,例如listhashmap,properties

            案例1

            定義一個相對複雜的物件

              public class ComplexObject {

                private String[] names;

                private List<String> address;

                private Map<String,Integer> map;

                private Properties properties;

                //set,get

              }

            在配置檔案中配置此物件,併為屬性注入值

 

<?xml version="1.0" encoding="UTF-8"?>

<beans 

    default-lazy-init="true"

    xmlns="http://www.springframework.org/schema/beans" 

    xmlns:p="http://www.springframework.org/schema/p"

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xmlns:context="http://www.springframework.org/schema/context"

    xmlns:tx="http://www.springframework.org/schema/tx"

    xmlns:aop="http://www.springframework.org/schema/aop"

    xmlns:mvc="http://www.springframework.org/schema/mvc"

    xmlns:util="http://www.springframework.org/schema/util"

    xmlns:jpa="http://www.springframework.org/schema/data/jpa"

    xsi:schemaLocation="  

       http://www.springframework.org/schema/beans   

       http://www.springframework.org/schema/beans/spring-beans-4.3.xsd  

       http://www.springframework.org/schema/mvc   

       http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd   

       http://www.springframework.org/schema/tx   

       http://www.springframework.org/schema/tx/spring-tx-4.3.xsd   

       http://www.springframework.org/schema/aop

       http://www.springframework.org/schema/aop/spring-aop-4.3.xsd

       http://www.springframework.org/schema/util

       http://www.springframework.org/schema/util/spring-util-4.3.xsd

       http://www.springframework.org/schema/data/jpa

       http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context-4.3.xsd"> 

          

      <bean id="cObj" class="beans.ComplexObject">

         <!-- 為String[]陣列屬性注入值 -->

         <property name="names">

             <list>

                <!-- #{}為spring中的一個表示式 -->

                <value>name-1</value>

                <value>name-2</value>

             </list>

         </property>

         <!-- 為list<String>集合注入值 -->

         <property name="address">

             <list>

               <value>北京</value>

               <value>深圳</value>

               <value>上海</value>

             </list>

         </property>

         <!-- 為map屬性注入值 -->

         <property name="map">

            <map>

               <entry key="k1" value="200"/>

               <entry key="k2" value="300"/>

            </map>

         </property>

         <!-- 為properties屬性注入值 -->

         <property name="properties">

            <props>

              <prop key="pk1">pv1</prop>

              <prop key="pk2">pv2</prop>

            </props>

         </property>      

      </bean>

</beans>

 

 

 

      案例2spring配置檔案中引入properties檔案中的資料

 

        step1:類路徑的根目錄定義properties檔案cfg.properties,其內容如下:

            driver=com.mysql.jdbc.Driver

            url=jdbc:mysql:///test

            username=root

            password=root

 

      step2:spring配置檔案中引入此配置檔案(藉助util標籤) 

        <util:properties   id="cfg"   location="classpath:cfg.properties"/>

 

      step3:在Bean物件中引入配置檔案中的值 

               <bean id="dataSource" class="beans.DataSource">

                      <property name="driver" value="#{cfg.driver}"/>

                      <property name="url"  value="#{cfg.url}"/>

                      <property name="username" value="#{cfg.username}"/>

                      <property name="password" value="#{cfg.password}"/>

               </bean>

 

          其中#{}spring中一個表示式,通過這個表示式可以獲取properties檔案中的值

          例如#{cfg.driver}為獲取idcfg對應的物件中keydriver的值。

 

    2.0.3. 依賴值的自動裝配 

      Spring中為bean物件中的屬性提供了自動裝配功能,此功能的開啟需要藉助bean標籤中的autowire屬性進行指定,此屬性的值預設有如下幾個:

 

 

NO 

自動配置(預設)

 

ByName

按名字自動裝配(重點掌握)

 

ByType

按型別自動狀態(重點掌握),但有多個型別時會出錯

 

Constructor

byType類似,不同之處在於它應用於構造器引數。

 

       例如:

        定義並配置DataSource物件

          public class DataSource {

          }

         <bean id="dataSource" class="beans.DataSource"/>

          定義並配置JdbcTemplate物件

          public class JdbcTemplate {

            private DataSource dataSource;

            public JdbcTemplate() {}

            public JdbcTemplate(DataSource dataSource) {

               this.dataSource=dataSource;

              }

          public void setDataSource(DataSource dataSource) {

            this.dataSource = dataSource;

            }

 

   <bean id="jdbcTemplate" class="beans.JdbcTemplate" autowire="constructor"> </bean>

      自動裝配應用總結

 

        v byName 按屬性對應的set方法名從容器中查詢名字相同的bean,然後進行注入。假如出現了名字相同,但型別不同的Bean物件時,會注入失敗

        v byType 先從容器中查詢屬性型別相匹配的值然後找按類中對應的set方法(看引數型別)最後通過set方法為物件的屬性注入值。假如容器中出現多個型別相同的物件,就會注入失敗。

        v constructor 先從容器中查詢屬性型別相匹配的值然後找類中對應的構造方法(看引數型別)最後通過構造方法為物件的屬性注入值。假如容器中出現多個型別相同的物件,此時再比對引數名字,假如有同名的則直接注入,沒有同名的就會注入失敗。

    3. Spring 註解應用

      3.1. Spring 註解概述

          Spring中提供了兩種方式對Bean進行描述,一種是基於xml方式,一種是基於註解方式。基於註解方式主要是依託於註解對bean以及bean中的屬性進行描述

          然後spring底層通過反射獲取bean上定義的這些註解,通過註解描述初始化物件,管理物件的作用域以及物件與物件之間的依賴。

 

      3.2. Spring 註解基本應用 

        3.2.1. 常用註解說明

          元件應用註解

 

註解名

說明

 

@Component

通用註解

 

@Repository

持久層元件應用註解

 

@Service

業務層元件應用註解

 

@Controller

控制層元件應用註解

 

    3.2.2. 註解應用案例

       資料層物件

        @Repository 

      業務層物件

        @Service 

      控制層物件

        @Controller

 

   3.2.3. 配置註解掃描

       Spring中通過指定一個包路徑,由系統自動掃描該包及其子包所有元件類,當發現元件類定義前有特定的註解標記時,就將該元件納入到Spring容器。

 

      例如:用元件掃描,首先需要在XML配置中指定掃描父級package路徑,例如

         <context:component-scan base-package=”com.company.spring/>”

       在這個配置中,容器會自動掃描org.example包及其子包下所有元件,並例項化bean物件。

 

      3.2.4. 編寫測試類獲取bean

 

    3.3. Spring 註解應用增強

 

      3.3.1. 作用域及生命週期

@Scope("singleton")
@Repository
public class SysUserDao {
      /**@PostConstruct註解修飾方法在物件初始化時執行*/
      @PostConstruct
      public void init(){
        System.out.println("init");  
      }
      public void insertObject(){
          System.out.println("insertObject");
      }
      /**@PreDestroy物件銷燬時執行*/
      @PreDestroy
      public void destory(){
          System.out.println("destory");
      }
}

 

 

3.3.2. 延遲載入配置

@Lazy(false)
@Service
public class SysUserService {
}

 

    3.3.3. 自動裝配配置

      註解方式的自動裝配一般會藉助@Autowired@Resource實現,具體過程參考   

      註解自動裝配使用說明:

        應用位置

        1@Autowired/@Qualifier 一般用於修飾屬性和構造方法

        2@Resource 一般用於修飾屬性和set方法

 

      注入規則:

        1@Autowired 修飾屬性,構造方法,set方法時預設按照屬性型別或引數型別進行值的注入。假如容器中有多個型別相同的Bean,此時還會按名字進行匹配,沒有相匹配的則注入失敗。假如希望按名字進行匹配需要再此基礎加上@Qualifier註解。

 

        2@Resource 修飾屬性,set方法時預設按屬性名或set方法名進行匹配,假如Resource指定了名稱則按指定名稱進行匹配,假如沒有找到相匹配的名稱,則按型別進行值的注入。

 

  1. Spring 註解工廠應用剖析

     1.1. Bean工廠是用於做什麼的?

       所有的工廠都是用於建立物件的,物件建立一般會基於某中策略對物件進行儲存。

 

      例如Spring 中的Bean工廠:ApplicationContext

        1) ClassPathXmlApplicationContext

        2) AnnotationApplicationContext

        3) …….

 

  1.2. Bean工廠的實現的基本原理?

 

      Spring 中所有的Bean假如需要由Spring管理,此時需要以xml描述的方式或註解的描述方式告訴spring容器。Spring底層內建了對xml的解析,通過反射獲取註解描述,然後基於這些描述通過反射構建物件。

 

  1.3. Bean工廠理解的拔高?

      手寫基於註解方式的Spring Bean工廠

        1) 包的掃描(將包轉換為類所在的路徑,然後進行檔案的查詢)

        2) 構建類全名(包名+類名,例如java.util.Date

        3) 基於反射構建類的物件(獲取Class物件,構建構造方法物件,構建類的例項物件)

        4) 儲存類的物件(key是誰)

        5) 需要時從工廠中獲取bean物件即可

 

  1.4. Bean 工廠手寫應用實踐

       1.4.1. 註解定義實現

        定義一個用於修飾bean,描述bean的註解。

          @Retention(RetentionPolicy.RUNTIME)

          @Target(ElementType.TYPE)

          public @interface Component {     

            String value() default "";

          }

 

      定義一個用於描述配置的註解

         @Retention(RetentionPolicy.RUNTIME)

        @Target(ElementType.TYPE)

        public @interface ComponentScan {

            String value() default "";

        }

 

 

    1.4.2. 定義Bean元件

      定義一個IdGenerator類,然後使用@Component註解修飾

       @Component

      public class IdGenerator {}

 

    1.4.3. 定義Bean工廠

      這個Bean工廠要實現的功能是基於配置類上的@ComponentScan註解,對註解中定義包進行類的掃描,然後構建類的物件,再將物件儲存到map集合,需要時從map獲取。其程式碼如下:

public class AnnotationAppContext{
    private Map<String,Object> beanMap=
    new HashMap<String,Object>();
    public AnnotationAppContext(Class<?> c)throws Exception{
        //1.獲取class(例如AppConfig)上的@ComponentScan註解
        ComponentScan cs=c.getAnnotation(ComponentScan.class);
        //2.獲取註解中定義的包名
        String pkg=cs.value();
        //替換包名中的“.”,改成目錄結構
        String dir=pkg.replaceAll("\\.", "/");
        //3.基於包獲取包對應的類路徑
        URL url=
        getClass().getClassLoader()
        .getResource(dir);
        System.out.println("url="+url);
        //4.獲取類路徑下所有的class檔案(例如IdGenerator)
        File pkgDir=new File(url.getPath());
        //4.1獲取目錄下所有class檔案
        File[] fs=pkgDir.listFiles();//class
        //4.2遍歷所有檔案,構建類全名
        for(File f:fs){
            String fname=
            f.getName().substring(0,f.getName().lastIndexOf("."));
            String clsName=pkg+"."+fname;
            Class<?> cls=Class.forName(clsName);
            //5.基於class檔案上註解(@Component)描述構建類物件
            if(!cls.isAnnotationPresent(Component.class))
            continue;
            Object obj=newInstance(cls);
            //6.將類物件儲存到map集合
            //6.1獲取key的值
            Component cp=cls.getAnnotation(Component.class);
            String key=cp.value();
            if("".equals(key)){
                key=String.valueOf(
                fname.charAt(0)).toLowerCase()+
                fname.substring(1);
            }
            //6.2儲存到map
            beanMap.put(key, obj);
        }
    }
    private Object newInstance(Class<?> cls)
    throws Exception{
        Constructor<?> c=
        cls.getDeclaredConstructor();
        c.setAccessible(true);
        return c.newInstance();
    }
    public Object getBean(String beanName){
        return beanMap.get(beanName);
    }
    @SuppressWarnings("unchecked")
    public <T>T getBean(String beanName,
            Class<T> cls){
        return (T) beanMap.get(beanName);
    }
    public static void main(String[] args)
    throws Exception{
        AnnotationAppContext ctx=
        new AnnotationAppContext(AppConfig.class);
        
        IdGenerator obj1=(IdGenerator)
        ctx.getBean("idGenerator");
        IdGenerator obj2=
        ctx.getBean("idGenerator",IdGenerator.class);
        
        System.out.println(obj1);
        System.out.println(obj2==obj1);
    }
}

 

2. 總結

2.1. 重點和難點分析

  1. Spring 註解的強化理解
  2. Spring 註解方式實現自動裝配
  3. Spring Bean工廠手寫方式的實現。

 

2.2. 常見FAQ

  1. URL是什麼
  2. URL的值什麼情況下為空
  3. 註解是什麼,如何自己定義
  4. 工廠應用中泛型方法如何實現

 

2.3. 作業

  1. 總結spring註解應用
  2. 嘗試手寫spring bean 工廠

 

相關文章