Spring 相關知識複習
Bean的簡單概念
Bean主要指可重複使用的元件,使用Bean中的功能需要通過其例項來呼叫。其例項的建立主要依賴Bean容器(container)。因此Bean的書寫需要遵循相關規範,以滿足容器對java類檔案的分析要求。因此,可以簡單理解Bean是按照特定規則書寫的java類。規則要求為:
- 這個Java類必須具有一個無參的建構函式
- 屬性必須私有化。
- 私有化的屬性必須通過public型別的方法暴露給其它程式,並且方法的命名也必須遵守一定的命名規範。
- 這個類應是可序列化的。(比如可以實現Serializable介面,用於實現bean的永續性)
Spring bean
Spring中的bean只需提供為其屬性設定值的setter方法即可,同時又spring管理其生命週期,並且通過配置檔案等設定spring自動例項化。
Spring 關鍵技術
1. IoC控制反轉
控制反轉(Inversion of Control)是為了滿足**依賴倒置(Dependency Injection)**的一種設計思想。將原本由高層依賴底層的依賴關係倒置過來。將底層注入到高層當中。反轉關係和注入過程通過控制反轉容器來實現。 傳統關係中物件主動通過new去建立所需物件。控制反轉則需先建立好物件之後由容器將所需物件注入。 所以控制反轉主要是將物件的建立和獲取分割到外部由容器提供。
2. 依賴注入
將類之間的依賴關係反轉過後,建立被呼叫者的過程不再由呼叫者完成,而是交給相應的容器來建立相應的容器,之後再注入給呼叫者。使用依賴注入可以實現程式碼的鬆耦合。 - 依賴注入的方式 1. 使用set方法注入:
<bean id="className" class="類的全限定名">
<!--set方法注入屬性
name屬性值:類中定義的屬性名稱
value屬性值:設定具體的值
-->
<property name="name" value="zs"></property>
</bean>
2. 使用有參構造方法注入:
public class Person { private String name; public void setName(String name) { this.name = name; } }
對於以上構造方法的注入方式為:
<bean id="user" class="cn.wang.ioc.User">
<!--構造方法注入屬性-->
<constructor-arg name="pname" value="Tony"></constructor-arg>
</bean>
3. 註解注入:
常用的註解有@Component:可以用於註冊所有bean;@Repository:主要用於註冊dao層的bean;@Controller:主要用於註冊控制層的bean;@Service:主要用於註冊服務層的bean;@Autowired
3.AOP(面向切面程式設計)
- AOP的基本概念
- Joinpoint(連線點):類裡面可以被增強的方法,這些方法稱為連線點
- Pointcut(切入點):所謂切入點是指我們要對哪些Joinpoint進行攔截的定義
- Advice(通知/增強):所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。通知分為前置通知,後置通知,異常通知,最終通知,環繞通知(方法之前和方法之後)
- Aspect(切面):把增強應用到具體方法上面,過程成為切面。
- Target(目標物件):代理的目標物件(要增強的類)
- Weaving(織入):是把增強應用到目標的過程,把advice應用到target的過程
- Proxy(代理):一個類被 AOP 織入增強後,就產生一個結果代理類
Spring中的AOP操作主要通過AspeJ來實現。使用xml和註解兩種方式配置。使用時需匯入相關jar包,同時spring的xml配置檔案中的schema需要引入aop約束
- 代理設計模式
Spring的通過動態代理設計模式織入增強程式碼。開發AOP程式。
- 1.靜態代理設計模式 第一步建立被代理類介面
/**
* 代理介面
*/
interface Interface{
public String doSomething();
public void somethingElse(String arg);
}
建立被代理類,繼承代理介面
/**
* 被代理類
*/
class RealObject implements Interface{
public String doSomething(){
System.out.println("[OUTPUT] do something");
return "res";
}
public void somethingElse(String arg){
System.out.println("[OUTPUT] something else "+arg);
}
}
建立代理類,同樣繼承代理介面
/**
* 代理類,對被代理類進行增強
*/
class SimpleProxy implements Interface{
private Interface interObj;
public SimpleProxy(RealObject obj){
this.interObj = obj;
}
public String doSomething(){
System.out.println("[OUTPUT] In Proxy Class");
interObj.doSomething();
return "proxy res";
}
public void somethingElse(String arg){
System.out.println("[OUTPUT] In Proxy Class");
interObj.somethingElse(arg);
}
//測試類
class SimpleProxyTest{
public static void excute(Interface interObj){
interObj.doSomething();
interObj.somethingElse("[param]");
}
public static void main(String[] args){
excute(new SimpleProxy(new RealObject()));
}
}
}
靜態代理模式,將被代理類通過代理類建構函式傳入代理類,並對其進行增強。
- 2.JDK動態代理 程式碼中可以看到,對需要被增強的方法,代理類都需編寫相應程式碼。代理類與被代理類關係還是相對緊密,所以引入了動態代理的模式。JDK動態代理主要依賴java.lang.reflect.Proxy類進行具體實現。首先同樣需要上例中的代理介面和被代理物件 其次,建立動態代理控制代碼DynamicProxyHandler並且需實現InvocationHandler介面並重寫其中的invoke()方法。
/**
* 動態代理控制代碼類
*/
class DynamicProxy implements InvocationHandler{
private Object interObj;
public DynamicProxy(Object obj){
this.interObj = obj;
}
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{
System.out.println("[OUTPUT] before invoke");
Object res = method.invoke(interObj, args);
System.out.println("[OUTPUT] after invoke");
return res;
}
}
這裡的invoke()方法即為對被代理類中的相應方法需要做的增強。其中的method.invoke()方法即通過反射以args為引數呼叫被代理類的相應方法。
/**
* 動態代理測試
*/
class SimpleDynamicProxy{
public static void main(String[] args) {
//System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Interface proxyClass = (Interface)Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{ Interface.class},
new DynamicProxy(new RealObject()));
System.out.println(proxyClass.doSomething());
proxyClass.somethingElse("in main");
}
}
通過Proxy中的靜態方法newProxyInstance()方法反射的建立代理類例項。由於newProxyInstance()方法返回值為Object型別所以需要轉成介面型別。之後就可以繼續呼叫。關於newProxyInstance的具體過程,會在另一篇文章裡分享,在這裡就不多展開描述了。
由以上可以,JDK動態代理需存在被代理類和其介面類,通過JDK中java.lang.reflect包中的內容來動態建立了代理類例項來實現的。我個人理解將其理解為,其實動態代理和靜態代理的實質是一樣的。只不過建立代理類的過程交由JDK通過反射的方式實現而不是我們自己編寫。
- 3.CGLib動態代理。 cglib動態代理的過程我沒有實際寫過,簡單分享一下看到的概念。cglib可以在執行期擴充套件Java類與實現Java介面,通俗說cglib可以在執行時動態生成位元組碼。其過程是:cglib繼承被代理的類,重寫方法,織入通知,動態生成位元組碼並執行,因為是繼承所以final類是沒有辦法動態代理的。因此,與JDK動態代理不同,cglib繼承被代理類,因此便不需要被代理類介面的存在。提供了又一種方便。
4.Spring bean的生命週期
Spring bean的生命週期相較於java bean複雜很多。下圖展示了spring bean從裝載到應用上下文到銷燬的一個生命週期
- Spring 對 Bean 進行例項化:相當於程式中的new Xx()
- Spring 將值和 Bean 的引用注入進 Bean 對應的屬性中;
- 如果Bean實現了 BeanNameAware 介面,Spring 將 Bean 的 ID 傳遞給setBeanName()方法。實現BeanNameAware清主要是為了通過Bean的引用來獲得Bean的ID,一般業務中是很少有在Bean的ID的
- 如果Bean實現了BeanFactoryAware介面,Spring將呼叫setBeanDactory(BeanFactory bf)方法並把BeanFactory容器例項作為引數傳入。實現BeanFactoryAware 主要目的是為了獲取Spring容器,如Bean通過Spring容器釋出事件等
- 如果Bean實現了ApplicationContextAwaer介面,Spring容器將呼叫setApplicationContext(ApplicationContext ctx)方法,將bean所在的應用上下文的引用傳入進來。作用與BeanFactory類似都是為了獲取Spring容器,不同的是Spring容器在呼叫setApplicationContext方法時會把它自己作為setApplicationContext 的引數傳入,而Spring容器在呼叫setBeanDactory前需要程式設計師自己指定(注入)setBeanDactory裡的引數BeanFactory
- 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessBeforeInitialization(預初始化)方法
作用是在Bean例項建立成功後對進行增強處理,如對Bean進行修改,增加某個功能 7. 如果Bean實現了InitializingBean介面,Spring將呼叫它們的afterPropertiesSet方法,作用與在配置檔案中對Bean使用init-method宣告初始化的作用一樣,都是在Bean的全部屬性設定成功後執行的初始化方法。
- 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessAfterInitialization(後初始化)方法。作用與6的一樣,只不過6是在Bean初始化前執行的,而這個是在Bean初始化後執行的,時機不同
- 經過以上的工作後,Bean將一直駐留在應用上下文中給應用使用,直到應用上下文被銷燬
- 如果Bean實現了DispostbleBean介面,Spring將呼叫它的destory方法,作用與在配置檔案中對Bean使用destory-method屬性的作用一樣,都是在Bean例項銷燬前執行的方法。
參考來自 frank-lam的github: 文章連結