Spring簡介
Spring是J2EE應用程序框架,是輕量級的IoC和AOP的容器框架,主要是針對javaBean的生命週期進行管理的輕量級容器,可以單獨使用,也可以和Struts框架,ibatis框架等第三方框架組合使用。
Spring總結起來優點如下:
- 低侵入式設計(用戶基本不需要與框架內部的代碼打交道),代碼的污染極低。
- 獨立於各種應用服務器,基於Spring框架的應用,可以真正實現Write Once,Run Anywhere的承諾。
- Spring的IoC容器降低了組件之間的耦合性,實現了軟件各層之間的解耦。
- Spring的AOP支持允許將一些通用任務如安全、事務、日誌等進行集中式管理,從而提供了更好的複用。
- Spring的ORM和DAO提供了與第三方持久層框架的良好整合,並簡化了底層的數據庫訪問。
- Spring的高度開放性,並不強制應用完全依賴於Spring,開發者可自由選用Spring框架的部分或全部。
Spring功能介紹
1)IoC(Inversion of Control)控制反轉,對象創建責任的反轉,在Spring中BeanFacotory是IoC容器的核心接口,負責實例化,定位,配置應用程序中的對象及建立這些對象間的依賴。XmlBeanFacotory實現BeanFactory接口,通過獲取xml配置文件數據,組成應用對象及對象間的依賴關係。
Spring中有三種注入方式,一種是set注入,一種是接口注入,另一種是構造方法注入。Spring 框架的 IOC 容器採用屬性注入(例如 setter 方法)和構造方法注入。
2)AOP面向切面編程
aop就是縱向的編程,業務1和業務2都需要一個共同的操作,與其往每個業務中都添加同樣的代碼,不如寫一遍代碼,讓兩個業務共同使用這段代碼。
Spring中面向切面編程的實現有兩種方式,一種是動態代理,一種是CGLIB,動態代理必須要提供接口,而CGLIB實現是有繼承。
依賴注入的方式和自動裝配
Spring 框架的 IOC 容器採用的依賴注入的方式:
- 屬性注入
- 構造器注入
自動裝配功能:
Spring能自動裝配Bean與Bean之間的依賴關係,即無須使用ref顯式指定依賴Bean,而是由Spring容器檢查XML配置文件內容,根據某種規則,爲調用者Bean注入被依賴的Bean。
Spring自動裝配可通過< beans/>元素的default-autowire屬性指定,該屬性對配置文件中所有的Bean起作用;也可通過對< bean/>元素的autowire屬性指定,該屬性只對該Bean起作用。
set注入和構造注入有時在做配置時比較麻煩。所以框架爲了提高開發效率,提供自動裝配功能,簡化配置。Spring框架式默認不支持自動裝配的,要想使用自動裝配需要修改spring配置文件中< bean >標籤的autowire屬性。
1、byName :**根據setter方法名進行自動裝配。**Spring容器查找容器中全部Bean,找出其id與setter方法名去掉set前綴,並小寫首字母后同名的Bean來完成注入。如果沒有找到匹配的Bean實例,則Spring不會進行任何注入。
2、byType:**根據setter方法的形參類型來自動裝配。**Spring容器查找容器中的全部Bean,如果正好有一個Bean類型與setter方法的形參類型匹配,就自動注入這個Bean;如果找到多個這樣的Bean,就拋出一個異常;如果沒有找到這樣的Bean,則什麼都不會發生,setter方法不會被調用。
3、constructor:與byType類似,區別是用於自動匹配構造器的參數。如果容器不能恰好找到一個與構造器參數類型匹配的Bean,則會拋出一個異常。
使用構造方法完成對象注入,其實也是根據構造方法的參數類型進行對象查找,相當於採用byType的方式。
4、autodetect
自動選擇:如果對象沒有無參數的構造方法,那麼自動選擇constructor的自動裝配方式進行構造注入。如果對象含有無參數的構造方法,那麼自動選擇byType的自動裝配方式進行setter注入。
5、no
不支持自動裝配功能,Bean依賴必須通過ref元素定義。
6、default
表示默認採用上一級標籤的自動裝配的取值。如果存在多個配置文件的話,那麼每一個配置文件的自動裝配方式都是獨立的。
<?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:util="http://www.springframework.org/schema/util"
xmlns:p="http://www.springframework.org/schema/p" 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/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config></context:annotation-config>
<!--屬性注入方式-->
<!--無參構造,name對應setName-->
<bean id="helloWorld" class="com.mook.HelloWOrld">
<property name="namee" value="Spring"></property>
</bean>
<!--構造器注入方式-->
<!--使用構造器注入屬性值可指定參數的位置和類型,以區分重載的構造函數;無name屬性-->
<bean id="car" class="com.mook.Car">
<constructor-arg value="BMW" index="0"></constructor-arg>
<constructor-arg index="1" type="java.lang.String">
<value><![CDATA[<Shanghai^>]]></value>
</constructor-arg>
<constructor-arg index="2" type="double">
<value>300000</value>
</constructor-arg>
</bean>
<bean id="car1" class="com.mook.Car" primary="true">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="Beijing" index="1"></constructor-arg>
<constructor-arg value="240" index="2" type="int"></constructor-arg>
</bean>
<bean id="person" class="com.mook.Person">
<property name="name" value="Tom"></property>
<property name="age" value="24"></property>
<!--可以使用property的ref屬性建立bean之間的引用關係-->
<!--<property name="car" ref="car1"></property>-->
<!--
<property name="car">
<ref bean="car1"></ref>
</property>
-->
<!--內部bean,不能被外部引用-->
<property name="car">
<!--不需要設置任何 id 或 name 屬性-->
<bean class="com.mook.Car">
<constructor-arg value="Ford" index="0"></constructor-arg>
<constructor-arg value="Beijing" index="1"></constructor-arg>
<constructor-arg value="200000" index="2" type="double"></constructor-arg>
</bean>
</property>
<property name="car.price" value="222222"></property>
</bean>
<bean id="person1" class="com.mook.Person">
<constructor-arg value="Mook"></constructor-arg>
<constructor-arg value="25"></constructor-arg>
<constructor-arg ref="car1"></constructor-arg>
<!--爲級聯屬性賦值.屬性需要先初始化後纔可以爲級聯屬性賦值,和Struts2不同.-->
<!--爲級聯屬性賦值,首先必須引用級聯屬性的類-->
<!--這裏的級聯屬性被賦值的前提是級聯屬性所屬的對象屬性已經被屬性注入或構造器注入賦值,否則會報異常。這一點與struts2不同,struts2這種情況會爲級聯屬性所屬對象自動生成實例,而spring不行。-->
<property name="Car.price" value="350000"></property>
<property name="Car.maxSpeed" value="190"></property>
<!--賦值null-->
<!--<constructor-arg><null/></constructor-arg>-->
</bean>
<!--配置集合屬性-->
<!--List-->
<bean id="person2" class="com.mook1.Person">
<property name="name" value="Mike"></property>
<property name="age" value="21"></property>
<property name="cars">
<list>
<ref bean="car"></ref>
<ref bean="car1"></ref>
<bean class="com.mook.Car">
<constructor-arg value="Ford" index="0"></constructor-arg>
<constructor-arg value="Beijing" index="1"></constructor-arg>
<constructor-arg value="200000" index="2" type="double"></constructor-arg>
</bean>
</list>
</property>
</bean>
<!--Map-->
<bean id="newPerson" class="com.mook1.NewPerson">
<property name="name" value="Rose"></property>
<property name="age" value="20"></property>
<property name="cars">
<map>
<entry key="AA" value-ref="car"></entry>
<entry key="BB" value-ref="car1"></entry>
</map>
</property>
</bean>
<!--配置Properties屬性值-->
<bean id="dataSource" class="com.mook1.DataSource">
<property name="properties">
<props>
<prop key="user">root</prop>
<prop key="password">123456</prop>
<prop key="jdbcUrl">jdbc:mysql://test</prop>
<prop key="driverClass">com.mysql.jdbc.Driver</prop>
</props>
</property>
</bean>
<!--配置獨立的 集合bean ,以供多個bean進行引用,需要導入util命名空間-->
<util:list id="cars">
<ref bean="car"></ref>
<ref bean="car1"></ref>
</util:list>
<bean id="person3" class="com.mook1.Person">
<property name="name" value="Kid"></property>
<property name="age" value="18"></property>
<property name="cars" ref="cars"></property>
</bean>
<!--通過p命名空間爲bean的屬性賦值,需要導入p命名空間,賦值更方便-->
<bean id="person4" class="com.mook1.Person" p:age="30" p:name="Queen" p:cars-ref="cars"></bean>
<!--byName-->
<!--根據bean的名字和當前bean的setter風格的屬性名進行自動裝配-->
<bean id="person5" class="com.autowire.Person" autowire="byName">
<property name="age" value="29"></property>
<property name="name" value="Tim"></property>
</bean>
<!--byType-->
<!--多個bean的類型與所需自動裝配的屬性的類型都匹配,就會拋出異常-->
<!--primary標示首選Bean;autowire-candidate取消自動裝配資格-->
<bean id="person6" class="com.autowire.Person" autowire="byType" p:name="Sim" p:age="28"></bean>
<!--constructor-->
<!--使用構造方法完成對象注入,其實也是根據構造方法的參數類型進行對象查找,相當於採用byType的方式。-->
<bean id="person7" class="com.autowire.Person" autowire="constructor">
<constructor-arg value="Mock" index="0"></constructor-arg>
<constructor-arg value="25" index="1"></constructor-arg>
</bean>
<bean id="person8" class="com.autowire.Person">
<property name="age" value="26"></property>
<property name="name" value="Rim"></property>
</bean>
</beans>
註解
註解Annotation,是一種類似註釋的機制,在代碼中添加註解可以在之後某時間使用這些信息。跟註釋不同的是,註釋是給我們看的,Java虛擬機不會編譯,註解也是不編譯的,但是我們可以通過反射機制去讀取註解中的信息。註解使用關鍵字@interface,繼承java.lang.annotition.Annotition。
Spring框架使用的是分層的註解:
持久層:@Repository
服務層:@Service
控制層:@Controller
@Autowired:
在 setter 方法中使用的 @Autowired 註釋,它會在方法中試圖執行 byType 自動連接。
在屬性中使用 @Autowired 註釋來除去 setter 方法。
在構造函數中使用 @Autowired。一個構造函數 @Autowired 說明當創建 bean 時,即使在 XML 文件中沒有使用 元素配置 bean ,構造函數也會被自動連接。
Autowired默認先按byType,如果發現找到多個bean,則,又按照byName方式比對,如果還有多個,則報出異常。爲了實現精確的自動裝配,Spring提供了@Qualifier註解,通過使用@Qualifier,允許根據Bean的id來執行自動裝配。
這三個層中的註解關鍵字都可以使用@Component來代替。
使用註解聲明對象,默認情況下生成的id名稱爲類名稱的首字母小寫。
IOC
使用Spring框架之後,調用者無需主動獲取被依賴對象,調用者只要被動接受Spring容器爲調用者的成員變量賦值即可,由此可見,使用Spring後,調用者獲取被依賴對象的方式由原來的主動獲取,變成了被動接受——所以Rod Johnson稱之爲控制反轉。
另外從Spring容器的角度來看,Spring容器負責將被依賴對象賦值給調用者的成員變量——相當於爲調用者注入它依賴的實例,因此Martine Fowler稱之爲依賴注入。
容器中Bean的作用域
當通過Spring容器創建一個Bean實例時,不僅可以完成Bean實例的實例化,還可以爲Bean指定特定的作用域。Spring支持如下五種作用域:
- singleton: 單例模式,在整個Spring IoC容器中,singleton作用域的Bean將只生成一個實例。
- prototype: 每次通過容器的getBean()方法獲取prototype作用域的Bean時,都將產生一個新的Bean實例。
- request: 對於一次HTTP請求,request作用域的Bean將只生成一個實例,這意味着,在同一次HTTP請求內,程序每次請求該Bean,得到的總是同一個實例。只有在Web應用中使用Spring時,該作用域才真正有效。
- 對於一次HTTP會話,session作用域的Bean將只生成一個實例,這意味着,在同一次HTTP會話內,程序每次請求該Bean,得到的總是同一個實例。只有在Web應用中使用Spring時,該作用域才真正有效。
- global session: 每個全局的HTTP Session對應一個Bean實例。在典型的情況下,僅在使用portlet context的時候有效,同樣只在Web應用中有效。
<?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">
<!--使用scope屬性來配置bean的作用域-->
<!--
默認:單例
容器初始時創建bean實例,在整個容器的生命週期內只創建這一個bean.
-->
<bean id="car" class="com.mook.Car" scope="singleton">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="Beijing" index="1"></constructor-arg>
<constructor-arg value="240" index="2" type="int"></constructor-arg>
</bean>
<!--
prototype:原型的
容器初始化時不創建bean實例,而在每次請求時都創建一個新的bean 實例,並返回.
-->
<bean id="car2" class="com.mook.Car" scope="prototype">
<constructor-arg value="Audi" index="0"></constructor-arg>
<constructor-arg value="Beijing" index="1"></constructor-arg>
<constructor-arg value="240" index="2" type="int"></constructor-arg>
</bean>
</beans>
如果不指定Bean的作用域,Spring默認使用singleton作用域。prototype作用域的Bean的創建、銷燬代價比較大。而singleton作用域的Bean實例一旦創建成果,就可以重複使用。因此,應該儘量避免將Bean設置成prototype作用域。
創建Bean實例的方式
1、使用構造器創建Bean實例
使用構造器來創建Bean實例是最常見的情況,如果不採用構造注入,Spring底層會調用Bean類的無參數構造器來創建實例,因此要求該Bean類提供無參數的構造器。
採用默認的構造器創建Bean實例,Spring對Bean實例的所有屬性執行默認初始化,即所有的基本類型的值初始化爲0或false;所有的引用類型的值初始化爲null。
2、使用靜態工廠方法創建Bean
使用靜態工廠方法創建Bean實例時,class屬性也必須指定,但此時class屬性並不是指定Bean實例的實現類,而是靜態工廠類,Spring通過該屬性知道由哪個工廠類來創建Bean實例。
除此之外,還需要使用factory-method屬性來指定靜態工廠方法,Spring將調用靜態工廠方法返回一個Bean實例,一旦獲得了指定Bean實例,Spring後面的處理步驟與採用普通方法創建Bean實例完全一樣。如果靜態工廠方法需要參數,則使用< constructor-arg…/>元素指定靜態工廠方法的參數。
3、調用實例工廠方法創建Bean
實例工廠方法與靜態工廠方法只有一個不同:調用靜態工廠方法只需使用工廠類即可,而調用實例工廠方法則需要工廠實例。使用實例工廠方法時,配置Bean實例的< bean…/>元素無須class屬性,配置實例工廠方法使用factory-bean指定工廠實例。
採用實例工廠方法創建Bean的< bean…/>元素時需要指定如下兩個屬性:
factory-bean: 該屬性的值爲工廠Bean的id。
factory-method: 該屬性指定實例工廠的工廠方法。
若調用實例工廠方法時需要傳入參數,則使用< constructor-arg…/>元素確定參數值。
<?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,注意不是配置靜態工廠方法實例,而是配置bean實例-->
<!--
class:指向靜態工廠方法的全類名
factory-method:指向靜態工廠方法的名字
constructor-arg:往靜態工廠方法傳入參數
-->
<bean id="car" class="com.factory.StaticCarFactory"
factory-method="getCar">
<constructor-arg value="audi"></constructor-arg>
</bean>
<!--配置工廠的實例-->
<bean id="instanceCarFactory" class="com.factory.InstanceCarFactory"></bean>
<!--通過實例工廠方法來配置bean-->
<bean id="car2" factory-bean="instanceCarFactory" factory-method="getCar">
<constructor-arg value="ford"></constructor-arg>
</bean>
</beans>
package com.factory;
import java.util.HashMap;
import java.util.Map;
/**
* Created by mook on 2016/12/1.
* 靜態工廠方法:直接調用某個類的靜態方法就可以返回Bean的實例
*/
public class StaticCarFactory {
private static Map<String, Car> cars = new HashMap<>();
public StaticCarFactory() {
System.out.println("StaticCarFactory's constructor...");
}
//靜態代碼塊
static {
cars.put("audi", new Car("audi", 300000));
cars.put("ford", new Car("ford", 400000));
}
//靜態工廠方法
public static Car getCar(String name) {
return cars.get(name);
}
}
package com.factory;
import java.util.HashMap;
import java.util.Map;
/**
* Created by mook on 2016/12/1.
* 實例工廠方法:實例工廠的方法.即先創建工廠本身,再調用工廠的實例方法來返回Bean的實例
*/
public class InstanceCarFactory {
private static Map<String, Car> cars = null;
public InstanceCarFactory() {
System.out.println("InstanceCarFactory's constructor...");
cars = new HashMap<>();
cars.put("audi", new Car("audi", 300000));
cars.put("ford", new Car("ford", 400000));
}
//非靜態方法
public Car getCar(String brand) {
return cars.get(brand);
}
}
FactoryBean的getObject()方法返回實例對象:單例模式
<?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">
<!--
FactoryBean的getObject()方法返回car實例
-->
<bean id="car" class="com.factorybean.CarFactoryBean">
<property name="brand" value="BMW"></property>
</bean>
</beans>
package com.factorybean;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
/**
* Created by mook on 2016/12/2.
* 實現FactoryBean<Car>接口
*/
public class CarFactoryBean implements FactoryBean<Car> {
private String brand;
public void setBrand(String brand) {
this.brand = brand;
}
@Override
public Car getObject() throws Exception {
return new Car(brand, 500000);
}
@Override
public Class<?> getObjectType() {
return Car.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
package com.factorybean;
/**
* Created by mook on 2016/11/30.
*/
public class Car {
private String brand;
private double price;
public Car() {
System.out.println("Car's constructor...");
}
public Car(String brand, double price) {
System.out.println("Car'ssss constructor...");
this.brand = brand;
this.price = price;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
'}';
}
}
AOP
AOP專門用於處理系統中分佈於各個模塊(不同方法)中的交叉關注點的問題,在JavaEE應用中,常常通過AOP來處理一些具有橫切性質的系統級服務,如事務管理、安全檢查、緩存、對象池管理等,AOP已經成爲一種非常常用的解決方案。
Spring bean的加載過程
參考文章 http://geeekr.com/read-spring-source-1-how-to-load-bean/
Spring bean的實例化
參考文章 http://geeekr.com/read-spring-source-two-beans-initialization/
Spring AOP原理
代理模式是常用的Java設計模式,他的特徵是代理類與委託類有同樣的接口,代理類主要負責爲委託類預處理消息、過濾消息、把消息轉發給委託類,以及事後處理消息等。代理類與委託類之間通常會存在關聯關係,一個代理類的對象與一個委託類的對象關聯,代理類的對象本身並不真正實現服務,而是通過調用委託類的對象的相關方法,來提供特定的服務。
下圖就是代理模式的UML類圖:
從圖中可以看出,代理接口(Subject)、代理類(ProxySubject)、委託類(RealSubject)形成一個“品”字結構。
根據代理對象的生成時間不同可以將代理分爲靜態代理和動態代理兩種。
1)靜態代理:由程序員創建或特定工具自動生成源代碼,再對其編譯。在程序運行前,代理類的.class文件就已經存在了。
2)動態代理:在程序運行時,運用反射機制動態創建而成。
靜態代理
由程序員創建或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就確定了。
接口:
public interface BookService {
void buyBook();
}
實現類:
public class BookServiceImpl implements BookService {
@Override
public void buyBook() {
System.out.println("買一本書...");
}
}
代理類:
public class BookServiceProxy implements BookService {
private BookService bookService;
public BookServiceProxy(BookService bookService){
this.bookService = bookService;
}
@Override
public void buyBook() {
prepareMoneyForBuyBook();
bookService.buyBook();
readBookAfterBuy();
}
private void prepareMoneyForBuyBook(){
System.out.println("爲買本準備好錢...");
}
private void readBookAfterBuy(){
System.out.println("終於可以看自己喜歡的書了...");
}
}
Client類:
public class StaticProxyTest {
public static void main(String[] args) {
BookService bookService = new BookServiceProxy(new BookServiceImpl());
bookService.buyBook();
}
}
優點:
業務類只需要關注業務邏輯本身,保證了業務類的重用性。這是代理的共有優點。
缺點:
1)代理對象的一個接口只服務於一種類型的對象,如果要代理的方法很多,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就無法勝任了。
2)如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法。增加了代碼維護的複雜度。
觀察代碼可以發現每一個代理類只能爲一個接口服務,這樣一來程序開發中必然會產生過多的代理,而且,所有的代理操作除了調用的方法不一樣之外,其他的操作都一樣,則此時肯定是重複代碼。
解決這一問題最好的做法是可以通過一個代理類完成全部的代理功能,那麼此時就必須使用動態代理完成。
動態代理
靜態代理是在編譯時就將接口、實現類、代理類一股腦兒全部手動完成,但如果我們需要很多的代理,每一個都這麼手動的去創建實屬浪費時間,而且會有大量的重複代碼,此時我們就可以採用動態代理,動態代理可以在程序運行期間根據需要動態的創建代理類及其實例,來完成具體的功能。
創建自己的調用處理器(InvocationHandler)
package com.mook.spring.aop.dynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 動態代理類
*
*/
public class DynamicProxy implements InvocationHandler {
/**
* 需要代理的目標類
*/
private Object target; //可以是實現很多接口的目標類
/**
* 寫法固定,aop專用:綁定委託對象並返回一個代理類
*
* @param target
* @return
*/
public Object bind(Object target) {
this.target = target; //這句可以用構造函數實現
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); //獲取目標對象的代理對象,該語句可以放到InvocationHandler外面調用
}
/**
* @param proxy:指代理對象。
* @param method:要調用的方法
* @param args:方法調用時所需要的參數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
// 切面之前執行
System.out.println("切面之前執行");
// 執行業務
result = method.invoke(target, args);
// 切面之後執行
System.out.println("切面之後執行");
return result;
}
}
測試類:
package com.mook.spring.aop.dynamicProxy;
/**
* 測試類
*/
public class Test {
public static void main(String[] args) {
// 綁定代理,這種方式會在所有的方法都加上切面方法
ITalk iTalk = (ITalk) new DynamicProxy().bind(new PeopleTalk("AOP", "18"));
iTalk.talk("業務說明");
}
}
動態代理類的字節碼是在程序運行期間由JVM根據反射等機制動態的生成,所以不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時確定。
Proxy靜態方法newProxyInstance:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
// 檢查h不爲空,否則拋異常
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* 獲得與指定類裝載器和一組接口相關的代理類類型對象
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 調用指定invocation handler,通過反射獲取構造函數對象並生成代理類實例並把MyInvocationHandler的實例傳給它的構造方法
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
類Proxy的getProxyClass方法調用ProxyGenerator的 generateProxyClass方法產生代理類的二進制數據:
public static byte[] generateProxyClass(final String name, Class[] interfaces)
用以下代碼可以獲取到JDK爲我們生成的代理類的字節碼並寫到硬盤中:
package dynamic.proxy;
import java.io.FileOutputStream;
import java.io.IOException;
import sun.misc.ProxyGenerator;
/**
* 代理類的生成工具
* @author zyb
* @since 2012-8-9
*/
public class ProxyGeneratorUtils {
/**
* 把代理類的字節碼寫到硬盤上
* @param path 保存路徑
*/
public static void writeProxyClassToHardDisk(String path) {
// 第一種方法,這種方式在剛纔分析ProxyGenerator時已經知道了
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
// 第二種方法
// 獲取代理類的字節碼
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", UserServiceImpl.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package dynamic.proxy;
import org.junit.Test;
/**
* 動態代理測試類
*/
public class ProxyTest {
@Test
public void testGenerateProxyClass() {
ProxyGeneratorUtils.writeProxyClassToHardDisk("F:/$Proxy11.class");
}
}
通過以上代碼,就可以在F盤上生成一個$Proxy.class文件了,現在用反編譯工具來看一下這個class文件裏面的內容。
public final class $Proxy11 extends Proxy implements UserService
import dynamic.proxy.UserService;
import java.lang.reflect.*;
public final class $Proxy11 extends Proxy
implements UserService
{
// 構造方法,參數就是剛纔傳過來的MyInvocationHandler類的實例
public $Proxy11(InvocationHandler invocationhandler)
{
super(invocationhandler);
}
public final boolean equals(Object obj)
{
try
{
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
/**
* 這個方法是關鍵部分
*/
public final void add()
{
try
{
// 實際上就是調用MyInvocationHandler的public Object invoke(Object proxy, Method method, Object[] args)方法,第二個問題就解決了
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode()
{
try
{
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString()
{
try
{
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable)
{
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m3;
private static Method m0;
private static Method m2;
// 在靜態代碼塊中獲取了4個方法:Object中的equals方法、UserService中的add方法、Object中的hashCode方法、Object中toString方法
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception)
{
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
JDK的動態代理依靠接口實現,如果有些類並沒有實現接口,則不能使用JDK代理,這就要使用cglib動態代理了。
參考:http://layznet.iteye.com/blog/1182924
http://blog.csdn.net/hejingyuan6/article/details/36203505
CGLIB代理
JDK的動態代理機制只能代理實現了接口的類,而不能實現接口的類就不能實現JDK的動態代理,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因爲採用的是繼承,所以不能對final修飾的類進行代理。
jdk和cglib動態代理實現的區別:
1、jdk動態代理生成的代理類和委託類實現了相同的接口;
2、cglib動態代理中生成的字節碼更加複雜,生成的代理類是委託類的子類,且不能處理被final關鍵字修飾的方法;
3、jdk採用反射機制調用委託類的方法,cglib採用類似索引的方式直接調用委託類方法;
實現類:沒有實現接口的
package com.mook.spring.aop.cglib;
/**
* 這個是沒有實現接口的實現類
*/
public class BookFacadeImpl1 {
public void addBook() {
System.out.println("增加圖書的普通方法...");
}
}
package com.mook.spring.aop.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 使用cglib動態代理
*/
public class BookFacadeCglib implements MethodInterceptor {
private Object target;
/**
* 創建代理對象
*
* @param target
* @return
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回調方法
enhancer.setCallback(this);
// 創建代理對象;通過字節碼技術動態創建子類實例
return enhancer.create();
}
@Override
// 回調方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("事物開始");
//通過代理類調用父類中的方法
proxy.invokeSuper(obj, args);
System.out.println("事物結束");
return null;
}
}
package com.mook.spring.aop.cglib;
/**
* Created by mook on 2017/7/18.
*/
public class TestCglib {
public static void main(String[] args) {
BookFacadeCglib cglib = new BookFacadeCglib();
BookFacadeImpl1 bookCglib = (BookFacadeImpl1)cglib.getInstance(new BookFacadeImpl1());
bookCglib.addBook();
}
}
代理對象的生成過程由Enhancer類實現,大概步驟如下:
1、生成代理類Class的二進制字節碼;
2、通過Class.forName加載二進制字節碼,生成Class對象;
3、通過反射機制獲取實例構造,並初始化代理類對象。
http://www.cnblogs.com/chinajava/p/5880887.html
Spring的事務管理
Spring中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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="car" class="com.cycle.Car"
init-method="init"
destroy-method="destroy">
<property name="brand" value="Audi"></property>
</bean>
<!--配置bean的後置處理器,對所有的bean進行處理-->
<bean class="com.cycle.MyBeanPostProcessor"></bean>
</beans>
package com.cycle;
/**
* Created by mook on 2016/12/1.
*/
public class Car {
public Car() {
System.out.println("Car's Constructor....");
}
private String brand;
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
System.out.println("setBrand....");
this.brand = brand;
}
public void init() {
System.out.println("init....");
setBrand("diAu");
}
public void destroy() {
System.out.println("destroy....");
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
'}';
}
}
package com.cycle;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
/**
* Created by mook on 2016/12/1.
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
System.out.println("postProcessBeforeInitialization: " + o + "," + s);
if ("car".equals(s)) {
//...
}
return o;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
System.out.println("postProcessAfterInitialization: " + o + "," + s);
Car car = new Car();
car.setBrand("Ford");
return car; }
}
package com.cycle;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by mook on 2016/12/1.
*/
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans-cycle.xml");
Car car = (Car) context.getBean("car");
System.out.println(car);
context.close(); //關閉IOC容器
}
}
運行結果:
八月 30, 2017 12:29:35 上午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@579bb367: startup date [Wed Aug 30 00:29:35 CST 2017]; root of context hierarchy
八月 30, 2017 12:29:35 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beans-cycle.xml]
Car's Constructor....
setBrand....
postProcessBeforeInitialization: Car{brand='Audi'},car
init....
setBrand....
postProcessAfterInitialization: Car{brand='diAu'},car
Car's Constructor....
setBrand....
Car{brand='Ford'}
destroy....
八月 30, 2017 12:29:35 上午 org.springframework.context.support.ClassPathXmlApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@579bb367: startup date [Wed Aug 30 00:29:35 CST 2017]; root of context hierarchy