Spring學習筆記(2) – JAVA編程語言程序開發技術文章

1.   裝配各種類型的屬性
1.1簡單屬性
使用<bean>的value屬性或<property>子標簽<value>裝配。

<propertyname="name"value="張無忌"></property>

<!– property元素中的內容叫屬性值,會自動把value描述的值轉換成對應屬性的類型 –>

<propertyname="age"><value>68</value></property>

<!– 也可以使用value來裝配一些Spring支持的類型URL,Class –>

<propertyname="homePage"value="https://www.baidu.cn"></property>

1.2引用其它bean
使用ref屬性或標簽。

<propertyname="parent">

<!– ref引用其它的 bean,local表示引用本容器中的bean,parent表示引用父容器中某個bean,bean表示引入某個bean,先在當容器中找,然後再到父容器中找 –>

<refbean="p0"/></property>

1.3內部bean
直接使用<bean>定義一個新的bean,一般不用指定該bean的名稱。

<propertyname="parent">

<!– 值是內部Bean ,一般不需要指定bean的名稱 –>

<beanclass="cn.itcasg.gz.springioc.Person">

<constructor-argvalue="張三豐"></constructor-arg>

</bean>

</property>

1.4裝配集合
1)數組

          <!– 裝配數組 –>

          <propertyname="favs">

                <array><!–也可以使用<list>標簽 –>

                     <value>足球</value>

                     <value>藍球</value>

                     <value>音樂</value>

                </array>

           </property>

2)List

          <!– 裝配list –>

          <propertyname="schools">

                <list><!–也可以使用<array>標簽 –>

                     <value>北大</value>

                     <value>清華</value>

                </list>

           </property>

3)Set

          <!– 裝配set –>

          <propertyname="citys">

                <set>

                     <value>廣州</value>

                     <value>北京</value>

                     <value>西安</value>

                     <value>廣州</value><!–最終方set中的隻有三個元素–>

                </set>

           </property>

4)Map

          <!– 裝配Map –>

          <propertyname="scores">

                <map>

                     <entry key="語文"value="50"/>

                     <entry key="數學"value="30"/>

                     <entry key="歷史"value="20"/>

                     <!– key-ref,value-ref屬性用來引用其它bean –>

                     <entry key-ref="p1"value-ref="p2"/>

                </map>

           </property>

5)Properties

          <!– 裝配屬性類型 –>

          <propertyname="ims">

          <props>

          <prop key="qq">4858458</prop>

          <prop key="msn">caishiyou@sina.com</prop>

          </props>

          </property>

          <!– 在value中直接使用鍵值對來作為屬性內容 –>

          <propertyname="ims2">

                <value>

                     qq=58568565868

                     msn=kdkdkf@ddd.com

                </value>

            </property>

1.5裝配空值null
      <bean id="p2"class="cn.itcasg.gz.springioc.Person">

          <propertyname="name"value="小鏈子"/>

          <propertyname="age">

                <!– null標簽用來指定空值 –>

                <null/>

          </property>

      </bean>

1.6裝配復雜類型屬性
publicclass Person {

      private Stringname;

      private Integerage;

      private Personparent; //父親,復雜類型

      private Datebirthday; //生日,復雜類型

}

1) 使用value來註入復雜類型的對象

如果使用value來註入復雜類型的對象,要依賴的對象提供帶一個參數(並且類型為String)的構造函數,Spring就可以將其轉換成需要的類型。

 

      <bean id="p1"class="com.maple.spring.customtype.Person">

          <propertyname="name"value="張三"/>

          <propertyname="age"value="23"/>

          <!– parent為Person類型,如果使用value來註入復雜類型的對象隻要

依賴的對象提供帶一個參數(並且類型為String)的構造函數,Spring就可以將其轉換成需要的類型。 –>

          <propertyname="parent"value="李四/>

      </bean>

2)自定義類型編輯器

 

使用value來註入復雜類型對象時,是不完整的。這時就要自定義類型編輯器,自定義類型編輯器時繼承PropertyEditorSupport類,並重寫其setAsText方法。

publicclass MyDateEditorextendsPropertyEditorSupport {

//   PropertyEditorSupport的子類中有日期編輯器

      private SimpleDateFormatsdf = new SimpleDateFormat("yyyy-MM-dd");

      @Override

      publicvoid setAsText(Stringtext)throws IllegalArgumentException {

          System.out.println("執行編輯器的setAsText方法….");

          if(text !=null &&text.trim().length() > 0) {

                try {

                     Datedate = sdf.parse(text);//轉換

                     this.setValue(date);//將轉換後的值設置進去

                }catch (ParseException e) {

                     e.printStackTrace();

                     super.setAsText(text);

                }

          }   

      }

}

3)註冊自定義編輯器

      <!– 註冊類型編輯器,從BeanFactoryPostProcessor接口的實現類 –>

<beanclass="org.springframework.beans.factory.config.CustomEditorConfigurer">

          <!–customEditors是該類的一個Map類型屬性 –>

          <propertyname="customEditors">

                <map>

<!– key:要轉換的類型 value:指定使用哪個轉換器來轉換 –>

<entrykey="java.util.Date"value="com.maple.spring.customtype.MyDateEditor"/>

                </map>

          </property>

      </bean>

2.註解裝配解決方案1(XML+註解配合)
用法:xml用來定義Bean的信息;註解用來配置依賴信息。

使用到的標簽:

@Resource(Java標簽的資源註解)(推薦使用)

@Autowired(Spring自定的註解),註解可以加在setter方法上(setter註入)/可以加在Field上(Field註入)。

@Autowired--按類型註入。required=false用來指定該依賴的對象是否是必須的。(false非必須,true必須的,默認true)。

@Qualifier("personDao2")–用來指定要註入的Bean叫什麼名字,與@Autowired配合使用。

      @Autowired

      @Qualifier("personDao2")//要註入的Bean的名稱為personDao2,會到IoC容器中找

      publicvoid setDao(IPersonDaodao) {

          this.dao = dao;

      }

@Resource–按名字註入,找到名字相同,則直接註入。找不到名字相同,則找類型。可在使用name屬性來指定要註入的Bean的名稱如:@Resource(name=”personDao”)。

      //要註入的Bean的名稱為personDao,會到IoC容器中找

      @Resource(name="personDao")

      publicvoid setDao(IPersonDaodao) {

          this.dao = dao;

      }

 

beans.xml:

<?xmlversion="1.0"encoding="UTF-8"?>

<beansxmlns="https://www.springframework.org/schema/beans"

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

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

xsi:schemaLocation="https://www.springframework.org/schema/beans          https://www.springframework.org/schema/beans/spring-beans-3.0.xsd

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

https://www.springframework.org/schema/context/spring-context-3.0.xsd">

      <!– 開啟註解配置,必能省,註意加入context命名空間 –>

     <context:annotation-config/>

      <bean id="personDao"class="cn.itcasg.gz.springioc.PersonDaoImpl"/>

      <bean id="personDao2"class="cn.itcasg.gz.springioc.PersonDaoImpl"/>

      <bean id="dao"class="cn.itcasg.gz.springioc.Person"/>

      <bean id="personService"class="cn.itcasg.gz.springioc.PersonServiceImpl"/>

</beans>

3.註解裝配解決方案2(完全使用註解)
1)指定到IoC容器中的業務組件

在實現類上添加註解:

@Repository(持久層組件)

@Service(業務層的組件)

@Controller(控件層組件)

@Component(不屬於以上三層的組件)

 

放到容器中的Bean的名字默認是類名首字母變小寫,使用value屬性就可以指定具體的Bean名稱。如:@Service("personService")bean的名字為personService。

 

Bean的作用域:默認是單例singleton。其它作用域,則配合使用@Scope標簽來指定。

如:@Scope("prototype")

@Service("personService")//存在在容器中bean的名字為personService

@Scope("prototype")//Bean的作用域為propotype,默認是singleton

publicclass PersonServiceImpl2implements IPersonService {

….//省略

}

2)給組件指定依賴

使用@Autowire或@Resource標簽來指定。

      //要註入的Bean的名稱為personDao,會到IoC容器中找

      @Resource(name="personDao")

      publicvoid setDao(IPersonDaodao) {

          this.dao = dao;

      }

3)啟動Spring的註解掃描功能

在配置文件中使用<context:component-scan>指定讓Spring去掃描哪些包,從而找到要放到容器中的組件。

<!– 指定Spring去掃描哪些包

如果這些包及其子包下面的類有@Service等標簽,則把其放到容器管理

base-package中如果有多個包可使用逗號(,)或分號(;)隔開。要加入context命名空間–>

<context:component-scanbase-package="com.maple.annotation1,com.maple.annotation2"/>

4.容器擴展點及應用
Spring提供瞭一些接口,讓開發者通過這些接口可以擴充容器的功能。如常用的BeanFactoryPostProcessor和BeanPostProcessor口。

4.1自定義類型編輯器
參見1.6  2)和3)。

4.2 BeanFactoryPostProcessor接口(創建Bean前)
實現瞭該接口的子類要重寫其postProcessBeanFactory方法,該方法在配置信息加載完以後,Bean對象初始化之前調用,可以在創建Bean之前過濾信息。

1)寫一個類實現該接口,並重寫postProcessBeanFactory方法。

publicclassMyBeanFactoryPostProcessorimplementsBeanFactoryPostProcessor {

@Override//該方法在配置信息加載完以後,Bean對象初始化之前調用

publicvoidpostProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)throws BeansException {

          //可以在創建Bean之前過濾信息

System.out.println("該方法在配置信息加載完以後,Bean對象初始化之前調用");

      }

}

2)在配置文件註冊該類。

      <!– 註冊自定義BeanFactoryPostProcessor子類,該類在所有的Bean創建之前都會執行其postProcessBeanFactory方法(隻執行一次) –>

<beanclass="com.maple.spring.customtype.MyBeanFactoryPostProcessor"/>

4.3 BeanPostProcessor接口(創建Bean之後)
1)寫一個類實現該接口,並重寫postProcessBeforeInitialization方法

和postProcessAfterInitialization方法。

publicclassMyBeanPostProcessorimplements BeanPostProcessor{

      @Override//該方法會在每個Bean創建好之後執行,執行完接著執行postProcessAfterInitialization方法

      public ObjectpostProcessBeforeInitialization(Object bean, String beanName)throws BeansException {

          System.out.println("處理Bean前…");

          return bean;//處理完後要返回原來的bean

      }

      @Override

      public ObjectpostProcessAfterInitialization(Object bean, String beanName)throws BeansException {

          System.out.println("處理Bean後…");

          return bean;//處理完後要返回原來的bean

      }

}

 

2) 在配置文件註冊該類。

<!– 註冊自定義BeanPostProcessor子類,該類在每個Bean創建好都會執行其

postProcessBeforeInitialization方法和postProcessAfterInitialization方法 (會執行多次) –>

<beanclass="com.maple.spring.customtype.MyBeanPostProcessor"/>

4.4使用Spring來加載配置文件
      <!– 加載properties類型的文件的內容 –>

<beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">

          <propertyname="location"value="db.properties"/>

      </bean>

      <!– 下面是簡化的方案 –>

      <context:property-placeholderlocation="db.properties"/>

      <!– 使用已有的擴展點來擴展一些功能 –>

      <!– 配置模擬數據源 –>

      <bean id="dataSource"class="cn.itcast.gz.springioc.MyDataSource">

          <propertyname="url"value="${db.url}"/>

          <propertyname="driverClass"value="${db.driverClass}"/>

          <propertyname="username"value="${db.username}"/>

          <propertyname="password"value="${db.password}"/>

      </bean>

說明:使用瞭context:property-placeholder後,Spring可以是根據${xxx}可以的內容到xxx.properties文件中找對應的值並設置到value上。

5. 工廠Bean(FactoryBean)
FactoyBean(接口)是一個專用於生產其它類型業務組件的Bean,用於生產其它產品的Bean。

註意:FactoyBean和BeanFactoy是兩個完全不同的接口。前者專門用來生產Bean,後者專門用來獲取Bean。

1) 自定義工廠Bean

自定義工廠Bean要實現FactoryBean接口。

publicclass PersonFactoryBeanimplementsFactoryBean<Person> {

      @Override

      public PersongetObject() throws Exception {

          System.out.println("getObject……….");

          //返回一個Person類型的Bean

          returnnew Person("張三");

      }

      @Override

      publicClass<Person> getObjectType() {

          //返回該Bean的類型

          System.out.println("getObjectType……….");

          return Person.class;

      }

      @Override

      publicboolean isSingleton() {

          //該Bean是否是單例的

          System.out.println("isSingleton……….");

          returnfalse;

      }

}

2) 在配置文件註冊該類。

      <!– 註冊自定義工廠Bean 根據id值pfb獲取的bean的類型不再是PersonFactoryBean類型,而是由PersonFactoryBean類中的getObject方法返回值的類型決定–>

<bean

id="pfb"class="com.maple.spring.customtype.PersonFactoryBean"/>

6. 簡化配置(p和c命名空間)
p和c命名空間是Spring3.0引入的,為瞭簡化書寫。

p:屬性 property

c:構造函數 constructor

c:parent-ref=”p5”引用其他bean。

      <!– 使用瞭p和c命名空間

      要在聲明schema時加入

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

      xmlns:c="https://www.springframework.org/schema/c"

      –>

      <bean id="p2"class="com.maple.spring.customtype.Person"

           p:name="李四" p:age="23"p:parent-ref="p3"/>

      <bean id="p3"class="com.maple.spring.customtype.Person"

           c:name="王五"/>

7. 代理模式(理解AOP原理的基礎)
7.1概念
代理模式的英文叫做Proxy或Surrogate,中文都可譯為”代理“,所謂代理,就是一個人或者一個機構代表另一個人或者另一個機構采取行動。在一些情況下,一個客戶不想或者不能夠直接引用一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用。

代理模式包含抽象主題,代理主題和真實主題。

7.2使用JDK創建代理類
使用JDK的Proxy類創建代理對象時,隻能創建實現瞭某個接口的類的代理對象,沒有實現接口的類是不能用JDK的Proxy類來創建代理對象的。

方式1:

/*

           * 創建實現瞭IHello接口的類的代理對象

           * 參數1:類加載器

           * 參數2:接口的字節碼

           * 參數3:回調函數(每次調用代理對象非final修飾方法時都會執行裡面的invoke方法)

           */

           IHello hello = (IHello) Proxy.newProxyInstance(TestProxy.class.getClassLoader(),new Class[]{IHello.class},newInvocationHandler() {

@Override

public Object invoke(Objectproxy, Method method, Object[] args)throws Throwable {

System.out.println("執行invoke方法—-" +method.getName());

                     returnnull;

                }

          });

           System.out.println(hello);//null

           System.out.println(hello.getClass());//class $Proxy4

            hello.sayHi();//無內容

方式2:

final IHello realObject=new HelloImpl();//真實對象

          //代理對象

          IHellohello = (IHello) Proxy.newProxyInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),newInvocationHandler() {

@Override

public Object invoke(Objectproxy, Method method, Object[] args)throws Throwable {

                     System.out.println("調用"+method.getName() +"前");

                     Objectret = method.invoke(realObject, args);//在真實對象上調用方法,參數不變照傳

                     System.out.println("調用"+method.getName() +"後");

                     return ret;//返回執行方法回的對象,為真實對象

                }

          });

          System.out.println(hello);

          hello.sayHi();//這裡是真實對象調用方法瞭,可以輸出"大傢好!"

      }

方式3:

publicclass TestProxy2implements InvocationHandler{

      private ObjectrealObject;

      /**

       * @param realObject

       * @return代理對象

       */

      public Object createProxyObjectByJdk(ObjectrealObject) {

          this.realObject = realObject;

          return Proxy.newProxyInstance(realObject.getClass().getClassLoader(),realObject.getClass().getInterfaces(),this);

      }

      @Override

      publicObject invoke(Objectproxy, Method method, Object[] args)throws Throwable {

          System.out.println("調用"+method.getName() +"前");

          Objectret = method.invoke(realObject, args);

          System.out.println("調用"+method.getName() +"後");

          returnret;

      }

}

7.3使用CGLIB創建代理類
使用JDK自帶的Proxy類不能創建沒有實現接口的類的代理對象,這時就要用到第三方類庫來創建瞭。如CGLIB。CGLIB是一個強大的,高性能,高質量的Code生成類庫。它可以在運行期擴展Java類與實現Java接口。(要加入jar包:cglib-nodep-xx.xx.jar)

使用CGLIB的Enhancer類來創建代理對象。

publicclass TestProxy {

      @Test//創建類的代理對象

      publicvoidtestCglibProxyClass() {

          //真實對象,沒有實現任何接口的類

          final HelloImpl2readObject =new HelloImpl2();

          Enhanceren = new Enhancer();

          //設置回調函數

          en.setCallback(newInvocationHandler() {

               

                @Override

public Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable {

returnmethod.invoke(readObject, args);//調用真實對象的方法並返回

                }

          });

          en.setSuperclass(HelloImpl2.class);//設置父類

          HelloImpl2hello2 = (HelloImpl2) en.create();//創建類的代理對象

          hello2.sayHi();

      }

      @Test//創建接口的代理對象

      publicvoidtestCglibProxyInterface() {

          final IHello readObject=new HelloImpl();

          Enhanceren = new Enhancer();

          //設置回調函數

          en.setCallback(newInvocationHandler() {

                @Override

                public Objectinvoke(Object proxy, Method method, Object[] args)throws Throwable {

returnmethod.invoke(readObject, args);//調用真實對象的方法並返回

                }

          });

          en.setSuperclass(IHello.class);//設置父類

          IHellohello = (IHello) en.create();//創建接口的代理對象

          hello.sayHi();

      }

}

 7.4代理總結

spring在運行期創建代理,不需要特殊的編譯器。

spring有兩種代理方式:

1.若目標對象實現瞭若幹接口,spring使用JDK的java.lang.reflect.Proxy類代理。

2.若目標對象沒有實現任何接口,spring使用CGLIB庫生成目標對象的子類。

使用該方式時需要註意:

1.對接口創建代理優於對類創建代理,因為會產生更加松耦合的系統。

對類代理是讓遺留系統或無法實現接口的第三方類庫同樣可以得到通知,這種方式應該是備用方案。

2.標記為final的方法不能夠被通知。spring是為目標類產生子類。任何需要被通知的方法都被復寫,將通知織入。final方法是不允許重寫的。

 

發佈留言