1. 程式人生 > >Spring 相關知識複習

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 生命週期

  1. Spring 對 Bean 進行例項化:相當於程式中的new Xx()
  2. Spring 將值和 Bean 的引用注入進 Bean 對應的屬性中;
  3. 如果Bean實現了 BeanNameAware 介面,Spring 將 Bean 的 ID 傳遞給setBeanName()方法。實現BeanNameAware清主要是為了通過Bean的引用來獲得Bean的ID,一般業務中是很少有在Bean的ID的
  4. 如果Bean實現了BeanFactoryAware介面,Spring將呼叫setBeanDactory(BeanFactory bf)方法並把BeanFactory容器例項作為引數傳入。實現BeanFactoryAware 主要目的是為了獲取Spring容器,如Bean通過Spring容器釋出事件等
  5. 如果Bean實現了ApplicationContextAwaer介面,Spring容器將呼叫setApplicationContext(ApplicationContext ctx)方法,將bean所在的應用上下文的引用傳入進來。作用與BeanFactory類似都是為了獲取Spring容器,不同的是Spring容器在呼叫setApplicationContext方法時會把它自己作為setApplicationContext 的引數傳入,而Spring容器在呼叫setBeanDactory前需要程式設計師自己指定(注入)setBeanDactory裡的引數BeanFactory
  6. 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessBeforeInitialization(預初始化)方法

作用是在Bean例項建立成功後對進行增強處理,如對Bean進行修改,增加某個功能 7. 如果Bean實現了InitializingBean介面,Spring將呼叫它們的afterPropertiesSet方法,作用與在配置檔案中對Bean使用init-method宣告初始化的作用一樣,都是在Bean的全部屬性設定成功後執行的初始化方法。

  1. 如果Bean實現了BeanPostProcess介面,Spring將呼叫它們的postProcessAfterInitialization(後初始化)方法。作用與6的一樣,只不過6是在Bean初始化前執行的,而這個是在Bean初始化後執行的,時機不同
  2. 經過以上的工作後,Bean將一直駐留在應用上下文中給應用使用,直到應用上下文被銷燬
  3. 如果Bean實現了DispostbleBean介面,Spring將呼叫它的destory方法,作用與在配置檔案中對Bean使用destory-method屬性的作用一樣,都是在Bean例項銷燬前執行的方法。

參考來自 frank-lam的github: 文章連結