ioc
什么是ioc?
1.IOC是Inversion of Control的縮寫,翻譯為控制反轉。ioc是容器, 控制反轉,目的:降低耦合度,高內(nèi)聚,低耦合。把對象創(chuàng)建和對象之間的調(diào)用過程,交給Spring進行管理。
2.IOC也叫依賴注入(DI)
所謂依賴注入,就是由IOC容器在運行期間,動態(tài)地將某種依賴關系注入到對象之中。所以,依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關系注入的方式,實現(xiàn)對象之間的解耦。
3.IOC的優(yōu)缺點
第一、軟件系統(tǒng)中由于引入了第三方IOC容器,生成對象的步驟變得有些復雜,本來是兩者之間的事情,又憑空多出一道手續(xù),所以,我們在剛開始使用IOC框架的時候,會感覺系統(tǒng)變得不太直觀。所以,引入了一個全新的框架,就會增加團隊成員學習和認識的培訓成本,并且在以后的運行維護中,還得讓新加入者具備同樣的知識體系。
第二、由于IOC容器生成對象是通過反射方式,在運行效率上有一定的損耗。如果你要追求運行效率的話,就必須對此進行權衡。
第三、具體到IOC框架產(chǎn)品(比如:Spring)來講,需要進行大量的配制工作,比較繁瑣,對于一些小的項目而言,客觀上也可能加大一些工作成本。
第四、IOC框架產(chǎn)品本身的成熟度需要進行評估,如果引入一個不成熟的IOC框架產(chǎn)品,那么會影響到整個項目,所以這也是一個隱性的風險。
反射
1.什么是反射
JAVA反射機制是在運行狀態(tài)中,對于任意一個類,都能夠知道這個類的所有屬性和方法,對于任意一個對象,都能夠調(diào)用它的任意一個方法和屬性,這種動態(tài)獲取的信息以及動態(tài)調(diào)用對象的方法的功能稱為java語言的反射機制。
說一下反射
package com.reflect;
public class Animal {
public String name = "Dog";
private int age = 30;
//默認無參構造函數(shù)
public Animal() {
System.out.println("Animal");
}
//帶參數(shù)的構造函數(shù)
public Animal(String name, int age) {
System.out.println(name + "," + age);
}
//公開 方法 返回類型和參數(shù)均有
public String sayName(String name) {
return "Hello," + name;
}
}
package com.reflect;
import java.lang.reflect.Constructor;
public class ReflectTest {
public static void main(String[] args) throws Exception {
//1、加載類,指定類的完全限定名:包名+類名
Class c1 = Class.forName("com.reflect.Animal");
System.out.println(c1); //打印c1,發(fā)現(xiàn)值和字節(jié)碼中的類的名稱一樣
//2、解刨(反射)類c1的公開構造函數(shù),且參數(shù)為null
Constructor ctor1 = c1.getConstructor();
//3、構造函數(shù)的用途,就是創(chuàng)建類的對象(實例)的
//除了私有構造函數(shù)外(單列模式,禁止通過構造函數(shù)創(chuàng)建類的實例,保證一個類只有一個實例)
//ctor1.newInstance()默認生成一個Object對象,我們需要轉化成我們要的Animal類對象
Animal a1 = (Animal) ctor1.newInstance();
//4、證明一下a1確實是Animal的實例,我們通過訪問類中的變量來證明
System.out.println(a1.name);
}
}
獲得類中的變量(字段)和方法,兩種方式,一個是getXXX 獲得本類中的所有公有的字段,并獲得指定對象的字段值,一個是getDeclaredXXX,獲得類中的所有的字段 包括public、private和protected,不包括父類中申明的字段,demo
package com.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectTest {
public static void main(String[] args) throws Exception {
System.out.println("A(無參構造函數(shù))--加載類、反射類的構造函數(shù)、利用構造函數(shù)new一個Animal實例instance--");
//1、加載類,指定類的完全限定名:包名+類名
Class c1 = Class.forName("com.reflect.Animal");
System.out.println(c1); //打印c1,發(fā)現(xiàn)值和字節(jié)碼中的類的名稱一樣
//2、解刨(反射)類c1的公開構造函數(shù),且參數(shù)為null
Constructor ctor1 = c1.getConstructor();
//3、構造函數(shù)的用途,就是創(chuàng)建類的對象(實例)的
//除了私有構造函數(shù)外(單列模式,禁止通過構造函數(shù)創(chuàng)建類的實例,保證一個類只有一個實例)
//ctor1.newInstance()默認生成一個Object對象,我們需要轉化成我們要的Animal類對象
Animal a1 = (Animal) ctor1.newInstance();
//4、證明一下a1確實是Animal的實例,我們通過訪問類中的變量來證明
System.out.println(a1.name);
System.out.println("A(有參構造函數(shù))--加載類、反射類的構造函數(shù)、利用構造函數(shù)new一個Animal實例instance--");
//2.b、 解刨(反射)類c1的公開構造函數(shù),參數(shù)為string和int
Constructor ctor2 = c1.getConstructor(String.class, int.class);
Animal a2 = (Animal) ctor2.newInstance("cat", 20);
System.out.println("B--獲得本類中的所有的字段----------------------------");
//5、獲得類中的所有的字段 包括public、private和protected,不包括父類中申明的字段
Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("C--獲得本類中的所有公有的字段,并獲得指定對象的字段值-----");
//6、獲得類中的所有的公有字段
fields = c1.getFields();
for (Field field : fields) {
System.out.println(field + ", 字段值 = " + field.get(a1));
//注意:私有變量值,無法通過field.get(a1)進行獲取值
//通過反射類中的字段name,修改name的值(注意,原值在類中name="Dog")
//如果,字段名稱等于"name",且字段類型為String,我們就修改字段的值,也就是類中變量name的值
if (field.getName() == "name" && field.getType().equals(String.class)) {
String name_new = (String) field.get(a1); //記得轉換一下類型
name_new = "哈士奇"; //重新給name賦值
field.set(a1, name_new); //設置當前實例a1的name值,使修改后的值生效
}
}
System.out.println("利用反射出的字段,修改字段值,修改后的name = "+a1.name);
System.out.println("D--獲取本類中的所有的方法--------------------");
//7、獲取本類中所有的方法 包括public、private和protected,不包括父類中申明的方法
Method[] methods = c1.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m);//我們在類Animal中只定義了一個public方法,sayName
}
System.out.println("E--獲取本類中的所有的公有方法,包括父類中和實現(xiàn)接口中的所有public方法-----------");
//8、獲取類中所有公有方法,包括父類中的和實現(xiàn)接口中的所有public 方法
methods = c1.getMethods();
for (Method m : methods) {
System.out.println(m);//我們在類Animal中只定義了一個public方法,sayName
}
System.out.println("F--根據(jù)方法名稱和參數(shù)類型獲取指定方法,并喚起方法:指定所屬對象a1,并給對應參數(shù)賦值-----------");
//9、喚起Method方法(執(zhí)行) getMethod:第一個參數(shù)是方法名,后面跟方法參數(shù)的類
Method sayName = c1.getMethod("sayName", String.class);
System.out.println(sayName.invoke(a1, "riemann"));
}
}
輸出結果:
A(無參構造函數(shù))--加載類、反射類的構造函數(shù)、利用構造函數(shù)new一個Animal實例instance--
class com.reflect.Animal
Animal
Dog
A(有參構造函數(shù))--加載類、反射類的構造函數(shù)、利用構造函數(shù)new一個Animal實例instance--
cat,20
B--獲得本類中的所有的字段----------------------------
public java.lang.String com.reflect.Animal.name
private int com.reflect.Animal.age
C--獲得本類中的所有公有的字段,并獲得指定對象的字段值-----
public java.lang.String com.reflect.Animal.name, 字段值 = Dog
利用反射出的字段,修改字段值,修改后的name = 哈士奇
D--獲取本類中的所有的方法--------------------
public java.lang.String com.reflect.Animal.sayName(java.lang.String)
E--獲取本類中的所有的公有方法,包括父類中和實現(xiàn)接口中的所有public方法-----------
public java.lang.String com.reflect.Animal.sayName(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
F--根據(jù)方法名稱和參數(shù)類型獲取指定方法,并喚起方法:指定所屬對象a1,并給對應參數(shù)賦值-----------
Hello,riemann
比如,在Spring中,我們經(jīng)??吹剑?/p>

針對上述的配置,我們Spring是怎么幫助我們實例化對象,并放到容器中去了呢? 沒錯,就是通過反射?。。?!
我們看下,下面的偽代碼實現(xiàn)過程:
//解析<bean .../>元素的id屬性得到該字符串值為"sqlSessionFactory"
String idStr = "sqlSessionFactory";
//解析<bean .../>元素的class屬性得到該字符串值為"org.mybatis.spring.SqlSessionFactoryBean"
String classStr = "org.mybatis.spring.SqlSessionFactoryBean";
//利用反射知識,通過classStr獲取Class類對象
Class cls = Class.forName(classStr);
//實例化對象
Object obj = cls.newInstance();
//container表示Spring容器
container.put(idStr, obj);
//當一個類里面需要用另一類的對象時,我們繼續(xù)下面的操作
//解析<property .../>元素的name屬性得到該字符串值為“dataSource”
String nameStr = "dataSource";
//解析<property .../>元素的ref屬性得到該字符串值為“dataSource”
String refStr = "dataSource";
//生成將要調(diào)用setter方法名
String setterName = "set" + nameStr.substring(0, 1).toUpperCase()
+ nameStr.substring(1);
//獲取spring容器中名為refStr的Bean,該Bean將會作為傳入?yún)?shù)
Object paramBean = container.get(refStr);
//獲取setter方法的Method類,此處的cls是剛才反射代碼得到的Class對象
Method setter = cls.getMethod(setterName, paramBean.getClass());
//調(diào)用invoke()方法,此處的obj是剛才反射代碼得到的Object對象
setter.invoke(obj, paramBean);
是不是很熟悉,雖然是偽代碼,但是和我們本篇講的反射機制的使用是相同的,現(xiàn)在知道我們的反射機制用在哪了吧,沒錯就是我們經(jīng)常提到的Java web框架中,里面就用到了反射機制,只要在代碼或配置文件中看到類的完全限定名(包名+類名),其底層原理基本上使用的就是Java的反射機制。
public class Person implements China{
private String name;
private int age ;
private char sex ;
public Person() {
super ();
}
public Person(String name, int age, char sex) {
super ();
this .name = name;
this .age = age;
this .sex = sex;
}
public String getName() {
return name ;
}
public void setName(String name) {
this .name = name;
}
public int getAge() {
return age ;
}
public void setAge(int age) {
this .age = age;
}
public char getSex() {
return sex ;
}
public void setSex(char sex) {
this .sex = sex;
}
public void eat()
{
System. out .println("吃了" );
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", sex=" + sex + "]" ;
}
@Override
public void sayChina() {
// TODO Auto-generated method stub
System. out .println("作者:" + AUTHOR + "國籍:"+ NATIONAL );
}
@Override
public String sayHello(String name, int age, char sex) {
// TODO Auto-generated method stub
return "姓名:" + name + "年齡:"+ age + "性別:" + sex;
}
}
public class ClassDemo {
public static void main(String[] args) {
Person p1 = new Person("小明" ,20,'男' );
Person p2 = new Person("小紅" ,23,'女' );
//創(chuàng)建Class對象的方式一:(對象.getClass()),獲取person類中的字節(jié)碼文件
Class class1 = p1.getClass();
System. out.println(p1.getClass().getName());
Class class2 = p2.getClass();
System. out.println(class1 == class2 );
System. out.println("==============================" );
//創(chuàng)建Class對象的方式二:(類.class:需要輸入一個明確的類,任意一個類型都有一個靜態(tài)的class屬性)
Class class3 = Person.class;
System. out.println(class1 == class2);
System. out.println("==============================" );
//創(chuàng)建Class對象的方式三:(forName():傳入時只需要以字符串的方式傳入即可)
//通過Class類的一個forName(String className)靜態(tài)方法返回一個Class對象,className必須是全路徑名稱;
//Class.forName()有異常:ClassNotFoundException
Class class4 = null;
try {
class4 = Class.forName("cn.itcast.Person");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System. out.println(class4 == class3);
}
}
在開發(fā)中一般使用第三種方法,因為第三種接收的是一個字符串路徑,將來可以通過配置文件獲取,通用性好;
spring中的ioc草圖


簡單描述一下流程
1.準備容器對象
2.加載配置文件,解析成對應的BeanDefinition
3.執(zhí)行BeanFactoryPostProcessor
4.準備工作(注冊BeanPostProcessor,準備廣播器,監(jiān)聽器等工作)
5.直接執(zhí)行實例化及初始化相關工作
6.DiposableBean接口 調(diào)用DiposibleBean.destory()
7.調(diào)用<bean>的destroy-method屬性指定的初始化方法
復雜點描述
IOC 容器的初始化分為三個過程實現(xiàn):
Resource資源定位,這個Resouce指的是BeanDefinition的資源定位,這個過程就是容器找數(shù)據(jù)的過程。
BeanDefinition的載入過程,這個載入過程是把用戶定義好的Bean表示成Ioc容器內(nèi)部的數(shù)據(jù)結構,而這個容器內(nèi)部的數(shù)據(jù)結構就是BeanDefition。
向IOC容器注冊這些BeanDefinition的過程,這個過程就是將前面的BeanDefition保存到HashMap中的過程。
Resource定位
我們一般使用外部資源來描述Bean對象,所以IOC容器第一步就是需要定位Resource外部資源。Resource的定位其實就是BeanDefinition的資源定位,它是由ResourceLoader通過統(tǒng)一的Resource接口來完成的,這個Resource對各種形式的BeanDefinition的使用都提供了統(tǒng)一接口。
載入
第二個過程就是BeanDefinition的載入,BeanDefinitionReader讀取,解析Resource定位的資源,也就是將用戶定義好的Bean表示成IOC容器的內(nèi)部數(shù)據(jù)結構也就是BeanDefinition,在IOC容器內(nèi)部維護著一個BeanDefinition Map的數(shù)據(jù)結構,通過這樣的數(shù)據(jù)結構,IOC容器能夠對Bean進行更好的管理。
在配置文件中每一個都對應著一個BeanDefinition對象。
注冊
第三個過程則是注冊,即向IOC容器注冊這些BeanDefinition,這個過程是通過BeanDefinitionRegistery接口來實現(xiàn)的。
在IOC容器內(nèi)部其實是將第二個過程解析得到的BeanDefinition注入到一個HashMap容器中,IOC容器就是通過這個HashMap來維護這些BeanDefinition的。
上面提到的過程一般是不包括Bean的依賴注入的實現(xiàn),Bean的載入和依賴注入是兩個獨立的過程,依賴注入是發(fā)生在應用第一次調(diào)用getBean向容器所要Bean時。
當然我們可以通過設置預處理,即對某個Bean設置lazyinit屬性,那么這個Bean的依賴注入就會在容器初始化的時候完成。
經(jīng)過這 (Resource定位,載入,注冊)三個步驟,IOC容器的初始化過程就已經(jīng)完成了。
Bean生命周期
1.實例化
當容器是beanFactory時,第一次請求時會需要注入依賴時開始進行實例化,當為ApplicationContext容器,容器啟動結束后,就開始實例化所有bean。
2.實例化結束后開始設置屬性,即依賴注入。
3.根據(jù)是否實現(xiàn)了Aware接口,將相關的xxxAware實例注入bean。此時bean已經(jīng)被構造完成。
4.如果實現(xiàn)BeanPostProcessor 接口,會將bean傳遞進接口的方法中,進行前置處理和后置處理。這個前置和后置是針對InitialzationBean接口而言的。
5.InitialzationBean接口方法會對bean增加邏輯,但不能對bean本身進行操作。這種方式會有侵入性,所以Spring提供了init-method屬性,用來指定要執(zhí)行的方法。本質(zhì)還是執(zhí)行了InitialzationBean的方法。
6.最后,檢查DisposableBean 和 destroy-method,原理和InitialzationBean接口是一樣的。
總結一下
Spring框架中,一旦把一個Bean納入Spring IOC容器之中,這個Bean的生命周期就會交由容器進行管理,一般擔當管理角色的是BeanFactory或者ApplicationContext,認識一下Bean的生命周期活動,對更好的利用它有很大的幫助:
下面以BeanFactory為例,說明一個Bean的生命周期活動。
Bean的建立, 由BeanFactory讀取Bean定義文件,并生成各個實例;
Setter注入,執(zhí)行Bean的屬性依賴注入;
BeanNameAware的setBeanName(), 如果實現(xiàn)該接口,則執(zhí)行其setBeanName方法;
BeanFactoryAware的setBeanFactory(),如果實現(xiàn)該接口,則執(zhí)行其setBeanFactory方法;
BeanPostProcessor的processBeforeInitialization(),如果有關聯(lián)的processor,則在Bean初始化之前都會執(zhí)行這個實例的processBeforeInitialization()方法;
InitializingBean的afterPropertiesSet(),如果實現(xiàn)了該接口,則執(zhí)行其afterPropertiesSet()方法;
Bean定義文件中定義init-method;
BeanPostProcessors的processAfterInitialization(),如果有關聯(lián)的processor,則在Bean初始化之前都會執(zhí)行這個實例的processAfterInitialization()方法;
DisposableBean的destroy(),在容器關閉時,如果Bean類實現(xiàn)了該接口,則執(zhí)行它的destroy()方法;
Bean定義文件中定義destroy-method,在容器關閉時,可以在Bean定義文件中使用“destory-method”定義的方法;
如果使用ApplicationContext來維護一個Bean的生命周期,則基本上與上邊的流程相同,只不過在執(zhí)行BeanNameAware的setBeanName()后,若有Bean類實現(xiàn)了org.springframework.context.ApplicationContextAware接口,則執(zhí)行其setApplicationContext()方法,然后再進行BeanPostProcessors的processBeforeInitialization() 實際上,ApplicationContext除了向BeanFactory那樣維護容器外,還提供了更加豐富的框架功能,如Bean的消息,事件處理機制等
spring循環(huán)依賴注入
Spring循環(huán)依賴的處理
在Spring中有兩種循環(huán)依賴的場景
1.構造器循環(huán)依賴,對于構造器循環(huán)依賴Spring是無法解決的,會直接拋出循環(huán)依賴異常
2.filed屬性循環(huán)依賴,在處理屬性循環(huán)以來的時候,其實只是解決了單利模式的循環(huán)的依賴問題,對于prototype的bean,也會直接拋出異常。
對于singleton的bean,并不是等完全創(chuàng)建好后再加入緩存中,而是在創(chuàng)建過程中通過ObjectFactory提前將自己暴露出來。
比如說beanA,在初始化A的過程中會提前暴露自己(ObjectFactory)到緩存中。加載過程中發(fā)現(xiàn)A依賴B,這時就會去初始化B,初始化B的過程中發(fā)現(xiàn)B依賴A,就從緩存中通過ObjectFactory.getObject獲取A,等B拿到A,B初始化完成,將B放到緩存中,緊接著A也初始化完成。這時候A和B都放到緩存中,并將ObjectFactory刪除。


BeanFactory和FactoryBean
BeanFactory
1.BeanFactory是ioc容器的必備數(shù)據(jù)結構之一,2.BeanFactory中維護了BeanDefinition中的一個BeanDefiniton map
3.在容器初始化的時候會把資源加載到BeanDefinition中,
4.根據(jù)BeanDefinition的描述對Bean進行和管理,這過程包括處理對象的依賴和Bean生命周期
FactoryBean
1.FactoryBean是一個接口,實現(xiàn)這個接口的Bean就代表了一個工廠Bean,
2.和普通的bean不同,從容器中通過BeanName獲取工廠Bean,其實就是FactoryBean的getObject()方法返回的對象,如果需要獲取FactoryBean對象本身,就是需要在name前加上&
總結:
FactoryBean比BeanFactory在生產(chǎn)上更加靈活,還可以修飾對象,還可以修飾對象,帶有一些裝飾模式思想在里面。比如使用spring-mybatis時,mybatis底層就是把mapper定義為一個FactoryBean,在getObject中通過動態(tài)代理生成一個代理對象放在BeanFactory容器中。
BeanFactory:在啟動的時候不會去實例化Bean,中有從容器中拿Bean的時候才會去實例化;
ApplicationContext:在啟動的時候就把所有的Bean全部實例化了。它還可以為Bean配置lazy-init=true來讓Bean延遲實例化;
AOP
所謂AOP,即Aspect orientied program,就是面向方面(切面)的編程。
特點
讓關注點代碼與業(yè)務代碼分離,可以動態(tài)地添加和刪除在切面上的邏輯而不影響原來的執(zhí)行代碼。
AOP核心概念
1、橫切關注點
對哪些方法進行攔截,攔截后怎么處理,這些關注點稱之為橫切關注點
2、切面(aspect)
類是對物體特征的抽象,切面就是對橫切關注點的抽象,面向切面編程,就是指 對很多功能都有的重復的代碼抽取,再在運行的時候往業(yè)務方法上動態(tài)植入“切面類代碼”。
3、連接點(joinpoint)
被攔截到的點,因為Spring只支持方法類型的連接點,所以在Spring中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構造器
4、切入點(pointcut)
切入點在AOP中的通知和切入點表達式關聯(lián),指定攔截哪些類的哪些方法, 給指定的類在運行的時候動態(tài)的植入切面類代碼。
5、通知(advice)
所謂通知指的就是指攔截到連接點之后要執(zhí)行的代碼,通知分為前置、后置、異常、最終、環(huán)繞通知五類
6、目標對象
被一個或者多個切面所通知的對象。
7、織入(weave)
將切面應用到目標對象并導致代理對象創(chuàng)建的過程
8、引入(introduction)
在不修改代碼的前提下,引入可以在運行期為類動態(tài)地添加一些方法或字段
9、AOP代理(AOP Proxy)
在Spring AOP中有兩種代理方式,JDK動態(tài)代理和CGLIB代理
使用注解

@Aspect指定一個類為切面類;
@Pointcut(“execution(* cn.itcast.e_aop_anno..(..))”) 指定切入點表達式;
@Before(“pointCut_()”)前置通知: 目標方法之前執(zhí)行;
@After(“pointCut_()”)后置通知:目標方法之后執(zhí)行(始終執(zhí)行);
@AfterReturning(“pointCut_()”)返回后通知: 執(zhí)行方法結束前執(zhí)行(異常不執(zhí)行);
@AfterThrowing(“pointCut_()”)異常通知: 出現(xiàn)異常時候執(zhí)行;
@Around(“pointCut_()”)環(huán)繞通知:環(huán)繞目標方法執(zhí)行;
AOP的實現(xiàn)原理
Spring AOP使用的動態(tài)代理,所謂的動態(tài)代理就是說AOP框架不會去修改字節(jié)碼,而是在內(nèi)存中臨時為方法生成一個AOP對象,這個AOP對象包含了目標對象的全部方法,并且在特定的切點做了增強處理,并回調(diào)原對象的方法。
Spring AOP中的動態(tài)代理主要有兩種方式,JDK動態(tài)代理和CGLIB動態(tài)代理。JDK動態(tài)代理通過反射來接收被代理的類,并且要求被代理的類必須實現(xiàn)一個接口。JDK動態(tài)代理的核心是InvocationHandler接口和Proxy類。
如果目標類沒有實現(xiàn)接口,那么Spring AOP會選擇使用CGLIB來動態(tài)代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,可以在運行時動態(tài)的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態(tài)代理,因此如果某個類被標記為final,那么它是無法使用CGLIB做動態(tài)代理的。
Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關系也由IOC容器負責管理。因此,AOP代理可以直接使用容器中的其它bean實例作為目標,這種關系可由IOC容器的依賴注入提供。
Spring創(chuàng)建代理的規(guī)則為:
默認使用Java動態(tài)代理來創(chuàng)建AOP代理,這樣就可以為任何接口實例創(chuàng)建代理了
當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強制使用CGLIB
AOP編程其實是很簡單的事情,程序員只需要參與三個部分:
定義普通業(yè)務組件
定義切入點,一個切入點可能橫切多個業(yè)務組件
定義增強處理,增強處理就是在AOP框架為普通業(yè)務組件織入的處理動作
所以進行AOP編程的關鍵就是定義切入點和定義增強處理,一旦定義了合適的切入點和增強處理,AOP框架將自動生成AOP代理
即:代理對象的方法=增強處理+被代理對象的方法。
Spring 是如何管理事務的,事務管理機制
編程式事務管理:Spring推薦使用TransactionTemplate,實際開發(fā)中使用聲明式事務較多。
聲明式事務管理:將我們從復雜的事務處理中解脫出來,獲取連接,關閉連接、事務提交、回滾、異常處理等這些操作都不用我們處理了,Spring都會幫我們處理。
聲明式事務管理使用了AOP面向切面編程實現(xiàn)的,本質(zhì)就是在目標方法執(zhí)行前后進行攔截。在目標方法執(zhí)行前加入或創(chuàng)建一個事務,在執(zhí)行方法執(zhí)行后,根據(jù)實際情況選擇提交或是回滾事務。
如何管理的
Spring事務管理主要包括3個接口,Spring的事務主要是由它們
(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三個共同完成的。
1. PlatformTransactionManager:事務管理器–主要用于平臺相關事務的管理
主要有三個方法:
commit 事務提交;
rollback 事務回滾;
getTransaction 獲取事務狀態(tài)。
2. TransactionDefinition:事務定義信息–用來定義事務相關的屬性,給事務管理器PlatformTransactionManager使用
這個接口有下面四個主要方法:
getIsolationLevel:獲取隔離級別;
getPropagationBehavior:獲取傳播行為;
getTimeout:獲取超時時間;
isReadOnly:是否只讀(保存、更新、刪除時屬性變?yōu)閒alse–可讀寫,查詢時為true–只讀)
事務管理器能夠根據(jù)這個返回值進行優(yōu)化,這些事務的配置信息,都可以通過配置文件進行配置。
3. TransactionStatus:事務具體運行狀態(tài)–事務管理過程中,每個時間點事務的狀態(tài)信息。
例如它的幾個方法:
hasSavepoint():返回這個事務內(nèi)部是否包含一個保存點,
isCompleted():返回該事務是否已完成,也就是說,是否已經(jīng)提交或回滾
isNewTransaction():判斷當前事務是否是一個新事務
聲明式事務的優(yōu)缺點:
優(yōu)點:不需要在業(yè)務邏輯代碼中編寫事務相關代碼,只需要在配置文件配置或使用注解(@Transaction),這種方式?jīng)]有侵入性。
缺點:聲明式事務的最細粒度作用于方法上,如果像代碼塊也有事務需求,只能變通下,將代碼塊變?yōu)榉椒ā?/p>
事務的隔離級別
從理論上來說, 事務應該彼此完全隔離, 以避免并發(fā)事務所導致的問題,然而, 那樣會對性能產(chǎn)生極大的影響, 因為事務必須按順序運行, 在實際開發(fā)中, 為了提升性能, 事務會以較低的隔離級別運行, 事務的隔離級別可以通過隔離事務屬性指定。
ISOLATION_DEFAULT: 這是一個PlatfromTransactionManager默認的隔離級別,使用數(shù)據(jù)庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應
ISOLATION_READ_UNCOMMITTED: 這是事務最低的隔離級別,它充許令外一個事務可以看到這個事務未提交的數(shù)據(jù)。這種隔離級別會產(chǎn)生臟讀,不可重復讀和幻像讀。
ISOLATION_READ_COMMITTED: 保證一個事務修改的數(shù)據(jù)提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數(shù)據(jù)
ISOLATION_REPEATABLE_READ: 這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現(xiàn)幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數(shù)據(jù)外,還保證了避免下面的情況產(chǎn)生(不可重復讀)。
ISOLATION_SERIALIZABLE: 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執(zhí)行。除了防止臟讀,不可重復讀外,還避免了幻像讀。
事務的隔離級別要得到底層數(shù)據(jù)庫引擎的支持, 而不是應用程序或者框架的支持.
Oracle 支持的 2 種事務隔離級別:READ_COMMITED , SERIALIZABLE
Mysql 支持 4 中事務隔離級別 默認是REPEATABLE READ隔離級別
事務回滾屬性
默認情況下只有未檢查異常(RuntimeException和Error類型的異常)會導致事務回滾. 而受檢查異常不會.
事務的回滾規(guī)則可以通過@Transactional 注解的 rollbackFor 和 noRollbackFor 屬性來定義,這兩個屬性被聲明為 Class[] 類型的, 因此可以為這兩個屬性指定多個異常類。
rollbackFor: 遇到時必須進行回滾
noRollbackFor: 一組異常類,遇到時必須不回滾
不推薦使用的手動回滾事務的方法:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Spring 的不同事務傳播行為有哪些,干什么用的?
當事務方法被另一個事務方法調(diào)用時, 必須指定事務應該如何傳播. 例如: 方法可能繼續(xù)在現(xiàn)有事務中運行, 也可能開啟一個新事務, 并在自己的事務中運行. 事務的傳播行為可以由傳播屬性指定.
Spring 定義了7 種類傳播行為.(一二兩種最常用)
==PROPAGATION_REQUIRED==: 如果存在一個事務,則支持當前事務,如果沒有事務則開啟。
==PROPAGATION_REQUIRES_NEW==: 總是開啟一個新的事務,如果一個事務已經(jīng)存在,則將這個存在的事務掛起。
PROPAGATION_SUPPORTS: 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執(zhí)行
PROPAGATION_MANDATORY: 如果已經(jīng)存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。
PROPAGATION_NOT_SUPPORTED: 總是非事務地執(zhí)行,并掛起任何存在的事務。
PROPAGATION_NEVER: 總是非事務地執(zhí)行,如果存在一個活動事務,則拋出異常
PROPAGATION_NESTED:如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行
Spring 中用到了那些設計模式?
Spring框架中使用到了大量的設計模式,下面列舉了比較有代表性的:
代理模式—在AOP和remoting中被用的比較多。
單例模式—在spring配置文件中定義的bean默認為單例模式。
模板方法—用來解決代碼重復的問題。比如. RestTemplate, JmsTemplate, JpaTemplate。
工廠模式—BeanFactory用來創(chuàng)建對象的實例。
適配器–spring aop
裝飾器–spring data hashmapper
觀察者– spring 時間驅動模型
回調(diào)–Spring ResourceLoaderAware回調(diào)接口
工廠模式(Factory Method)
Spring容器就是實例化和管理Bean的工廠
工廠模式隱藏了創(chuàng)建類的細節(jié),返回值必定是接口或者抽象類,而不是具體的某個對象,工廠類根據(jù)條件生成不同的子類實例。當?shù)玫阶宇惖膶嵗螅涂梢哉{(diào)用基類中的方法,不必考慮返回的是哪一個子類的實例。
這個很明顯,在各種BeanFactory以及ApplicationContext創(chuàng)建中都用到了;
Spring通過配置文件,就可以管理所有的bean,而這些bean就是Spring工廠能產(chǎn)生的實例,因此,首先我們在Spring配置文件中對兩個實例進行配置。
單態(tài)模式【單例模式】(Singleton)
Spring默認將所有的Bean設置成 單例模式,即對所有的相同id的Bean的請求,都將返回同一個共享的Bean實例。這樣就可以大大降低Java創(chuàng)建對象和銷毀時的系統(tǒng)開銷。
使用Spring將Bean設置稱為單例行為,則無需自己完成單例模式。
可以通過singleton=“truefalse” 或者 scope=“?”來指定
適配器(Adapter)
在Spring的Aop中,使用的Advice(通知)來增強被代理類的功能。Spring實現(xiàn)這一AOP功能的原理就使用代理模式(1、JDK動態(tài)代理。2、CGLib字節(jié)碼生成技術代理。)對類進行方法級別的切面增強,即,生成被代理類的代理類, 并在代理類的方法前,設置攔截器,通過執(zhí)行攔截器重的內(nèi)容增強了代理方法的功能,實現(xiàn)的面向切面編程。
代理(Proxy)
Spring實現(xiàn)了一種能夠通過額外的方法調(diào)用完成任務的設計模式 - 代理設計模式,比如JdkDynamicAopProxy和Cglib2AopProxy。
代理設計模式的一個很好的例子是org.springframework.aop.framework.ProxyFactoryBean。該工廠根據(jù)Spring bean構建AOP代理。該類實現(xiàn)了定義getObject()方法的FactoryBean接口。此方法用于將需求Bean的實例返回給bean factory。在這種情況下,它不是返回的實例,而是AOP代理。在執(zhí)行代理對象的方法之前,可以通過調(diào)用補充方法來進一步“修飾”代理對象(其實所謂的靜態(tài)代理不過是在裝飾模式上加了個要不要你來干動作行為而已,而不是裝飾模式什么也不做就加了件衣服,其他還得由你來全權完成)。
觀察者(Observer)
定義對象間的一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。spring中Observer模式常用的地方是listener的實現(xiàn)。如ApplicationListener。
Spring MVC 的工作原理?
https://www.cnblogs.com/xiaoxi/p/6164383.html
SpringMVC流程
用戶發(fā)送請求至前端控制器DispatcherServlet。
DispatcherServlet收到請求調(diào)用HandlerMapping處理器映射器。
處理器映射器找到具體的處理器(可以根據(jù)xml配置、注解進行查找),生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。
DispatcherServlet調(diào)用HandlerAdapter處理器適配器。
HandlerAdapter經(jīng)過適配調(diào)用具體的處理器(Controller,也叫后端控制器)。
Controller執(zhí)行完成返回ModelAndView。
HandlerAdapter將controller執(zhí)行結果ModelAndView返回給DispatcherServlet。
DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器。
ViewReslover解析后返回具體View。
DispatcherServlet根據(jù)View進行渲染視圖(即將模型數(shù)據(jù)填充至視圖中)。
DispatcherServlet響應用戶。
第一步: 用戶發(fā)起請求到前端控制器(DispatcherServlet)
第二步:前端控制器請求處理器映射器(HandlerMappering)去查找處理器(Handle):通過xml配置或者注解進行查找
第三步:找到以后處理器映射器(HandlerMappering)像前端控制器返回執(zhí)行鏈(HandlerExecutionChain)
第四步:前端控制器(DispatcherServlet)調(diào)用處理器適配器(HandlerAdapter)去執(zhí)行處理器(Handler)
第五步:處理器適配器去執(zhí)行Handler
第六步:Handler執(zhí)行完給處理器適配器返回ModelAndView
第七步:處理器適配器向前端控制器返回ModelAndView
第八步:前端控制器請求視圖解析器(ViewResolver)去進行視圖解析
第九步:視圖解析器像前端控制器返回View
第十步:前端控制器對視圖進行渲染
第十一步:前端控制器向用戶響應結果

看到這些步驟我相信大家很感覺非常的亂,這是正常的,但是這里主要是要大家理解springMVC中的幾個組件:
前端控制器(DispatcherServlet):接收請求,響應結果,相當于電腦的CPU。
處理器映射器(HandlerMapping):根據(jù)URL去查找處理器
處理器(Handler):(需要程序員去寫代碼處理邏輯的)
處理器適配器(HandlerAdapter):會把處理器包裝成適配器,這樣就可以支持多種類型的處理器,類比筆記本的適配器(適配器模式的應用)
視圖解析器(ViewResovler):進行視圖解析,多返回的字符串,進行處理,可以解析成對應的頁面
Spring如何解決循環(huán)依賴?
http://www.importnew.com/17580.html
一、構造器循環(huán)依賴:表示通過構造器注入構成的循環(huán)依賴,此依賴是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴。
如在創(chuàng)建CircleA類時,構造器需要CircleB類,那將去創(chuàng)建CircleB,在創(chuàng)建CircleB類時又發(fā)現(xiàn)需要CircleC類,則又去創(chuàng)建CircleC,最終在創(chuàng)建CircleC時發(fā)現(xiàn)又需要CircleA;從而形成一個環(huán),沒辦法創(chuàng)建。
Spring容器將每一個正在創(chuàng)建的Bean 標識符放在一個“當前創(chuàng)建Bean池”中,Bean標識符在創(chuàng)建過程中將一直保持在這個池中,因此如果在創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在“當前創(chuàng)建Bean池”里時將拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴;而對于創(chuàng)建完畢的Bean將從“當前創(chuàng)建Bean池”中清除掉
1)首先讓我們看一下配置文件(chapter3/circleInjectByConstructor.xml):
2)寫段測試代碼(cn.javass.spring.chapter3.CircleTest)測試一下吧:
@Test(expected=BeanCurrentlyInCreationException.class)publicvoidtestCircleByConstructor()throwsThrowable{try{newClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");}catch(Exceptione){//因為要在創(chuàng)建circle3時拋出;Throwablee1=e.getCause().getCause().getCause();throwe1;}}
讓我們分析一下吧:
1、Spring容器創(chuàng)建“circleA” Bean,首先去“當前創(chuàng)建Bean池”查找是否當前Bean正在創(chuàng)建,如果沒發(fā)現(xiàn),則繼續(xù)準備其需要的構造器參數(shù)“circleB”,并將“circleA” 標識符放到“當前創(chuàng)建Bean池”;
2、Spring容器創(chuàng)建“circleB” Bean,首先去“當前創(chuàng)建Bean池”查找是否當前Bean正在創(chuàng)建,如果沒發(fā)現(xiàn),則繼續(xù)準備其需要的構造器參數(shù)“circleC”,并將“circleB” 標識符放到“當前創(chuàng)建Bean池”;
3、Spring容器創(chuàng)建“circleC” Bean,首先去“當前創(chuàng)建Bean池”查找是否當前Bean正在創(chuàng)建,如果沒發(fā)現(xiàn),則繼續(xù)準備其需要的構造器參數(shù)“circleA”,并將“circleC” 標識符放到“當前創(chuàng)建Bean池”;
4、到此為止Spring容器要去創(chuàng)建“circleA”Bean,發(fā)現(xiàn)該Bean 標識符在“當前創(chuàng)建Bean池”中,因為表示循環(huán)依賴,拋出BeanCurrentlyInCreationException。
二、setter循環(huán)依賴:表示通過setter注入方式構成的循環(huán)依賴。
對于setter注入造成的依賴是通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的Bean來完成的,而且只能解決單例作用域的Bean循環(huán)依賴。
addSingletonFactory(beanName,newObjectFactory(){publicObjectgetObject()throwsBeansException{returngetEarlyBeanReference(beanName,mbd,bean);}});
具體步驟如下:
1、Spring容器創(chuàng)建單例“circleA” Bean,首先根據(jù)無參構造器創(chuàng)建Bean,并暴露一個“ObjectFactory ”用于返回一個提前暴露一個創(chuàng)建中的Bean,并將“circleA” 標識符放到“當前創(chuàng)建Bean池”;然后進行setter注入“circleB”;
2、Spring容器創(chuàng)建單例“circleB” Bean,首先根據(jù)無參構造器創(chuàng)建Bean,并暴露一個“ObjectFactory”用于返回一個提前暴露一個創(chuàng)建中的Bean,并將“circleB” 標識符放到“當前創(chuàng)建Bean池”,然后進行setter注入“circleC”;
3、Spring容器創(chuàng)建單例“circleC” Bean,首先根據(jù)無參構造器創(chuàng)建Bean,并暴露一個“ObjectFactory ”用于返回一個提前暴露一個創(chuàng)建中的Bean,并將“circleC” 標識符放到“當前創(chuàng)建Bean池”,然后進行setter注入“circleA”;進行注入“circleA”時由于提前暴露了“ObjectFactory”工廠從而使用它返回提前暴露一個創(chuàng)建中的Bean;
4、最后在依賴注入“circleB”和“circleA”,完成setter注入。
Spring 如何保證 Controller 并發(fā)的安全?
Spring 多線程請求過來調(diào)用的Controller對象都是一個,而不是一個請求過來就創(chuàng)建一個Controller對象。
并發(fā)的安全? 原因就在于Controller對象是單例的,那么如果不小心在類中定義了類變量,那么這個類變量是被所有請求共享的,這可能會造成多個請求修改該變量的值,出現(xiàn)與預期結果不符合的異常
那有沒有辦法讓Controller不以單例而以每次請求都重新創(chuàng)建的形式存在呢?
答案是當然可以,只需要在類上添加注解@Scope(“prototype”)即可,這樣每次請求調(diào)用的類都是重新生成的(每次生成會影響效率)
雖然這樣可以解決問題,但增加了時間成本,總讓人不爽,還有其他方法么?答案是肯定的!
使用ThreadLocal來保存類變量,將類變量保存在線程的變量域中,讓不同的請求隔離開來。