SpringIOC和AOP原理 設計模式(3)


  原文鏈接(精品)

SpringIOC的特點

在接觸Spring的過程中,聽到最多的無非兩個名詞,一個是控制反轉,一個是依賴注入。實際這是一個意思,控制反轉代表原來由程序本身去控制對象之間的依賴關系的這種格局被反轉了,通過第三方容器(IOC)去完成控制這些對象的依賴的關系並對它們進行集中管理,借助於“第三方”實現具有依賴關系的對象之間的解耦。 

依賴注入(DI)和控制反轉(IOC)是從不同的角度的描述的同一件事情,就是指通過引入IOC容器,利用依賴關系注入的方式,實現對象之間的解耦。

依賴注入:獲得依賴對象的過程由自身管理變為了由IOC容器主動注入,就是由IOC容器在運行期間,動態地將某種依賴關系注入到對象之中。

SpringAOP的特點
AOP最多聽到的就是面向切面編程,那對於這個名詞,我第一次聽到的時候也是不能理解的。

下面用一個圖和語言來進行描述:

 
切面

在一個項目中和我們業務邏輯和通用的邏輯區分開來,比如我們的一個系統需要記錄日志,記錄日志這個事情是通用的,不管你做什么系統一般都會涉及。那么這一塊就通過AOP來統一集中實現,統一管理。

生活中的一個例子,你去吃飯之前肯定要洗手,飯后肯定要擦嘴。那么,不管你吃什么飯在哪個地方吃。這些通用的過程你都要執行。那么集中抽象出來這些方法,也就形成了AOP。

SpringIOC容器加載Bean的過程

1.第一步 IOC容器


 
初始化

把xml文件位置信息保存,然后調用refresh方法去重新初始化一個新的IOC容器,Refresh方法中使用obtainFreshBeanFactory去獲取,后面的代碼是一些BeanFactory創建后的后處理過程


 
1.1

obtainFreshBeanFactory方法里面,我們看到第一行調用refreshBeanFactory的方法去創建。


 
1.2

在方法中去loadBeanDefintions(),使用XMLReader去解析我們的xml配置文件


 
1.3

后面省略一些源碼的步驟,主要做的就是對xml文件進行解析成我們要的BeanDefinitions,處理每個Bean元素以及元素中的屬性值。最后把beanDefinition注冊進我們的BeanFactory中,

 
1.4

2.注入依賴

 
2.1
 
2.2

AOP的兩種實現方式 以及小例子

主要是兩種,一種是JDK動態代理,一種是Cglib代理。

兩者的區別:
1.JDK動態代理只能代理實現了接口的類,動態代理類的字節碼在程序運行時由Java反射機制動態生成。
2.Cglib是可以代理沒有實現接口的類,cglib是針對類來實現代理的,他的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,所以不能對final修飾的類進行代理。底層采用ASM實現。

Cglib的例子:

package com.Model.CGlibProxy;

public interface AddBook {
    public void addbook();
}


package com.Model.CGlibProxy;

public class AddBookImp implements AddBook {

    @Override
    public void addbook() {
        // TODO Auto-generated method stub
        System.out.println("添加書籍....");
    }

}


package com.Model.CGlibProxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

    private Object target;

    public Object getInstance(Object target) {
        this.target = target;

        Enhancer enhancer = new Enhancer();
        // 設置自己的父類
        enhancer.setSuperclass(target.getClass());
        // 關聯的要使用哪個對象的回調函數 這里指向自己這個對象的回調 那么就是下面這個方面了
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
        // TODO Auto-generated method stub

        System.out.println("事務開始...");
        // 調用父類函數
        arg3.invokeSuper(arg0, arg2);

        System.out.println("事務結束....");

        return null;
    }

    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        AddBookImp oBookImp = (AddBookImp) cglibProxy.getInstance(new AddBookImp());
        oBookImp.addbook();
    }

}

 

JDK動態代理:

//操作定義
public interface SubjectOperations {
    // 打印操作
    public void print();

    // 打印輸入字符串操作
    public void printfStr(String string);
}


public class RealSubject implements SubjectOperations {

    @Override
    public void print() {
        System.out.println("我實現了接口 完成這個打印操作");

    }

    @Override
    public void printfStr(String string) {
        // TODO Auto-generated method stub
        System.out.println("打印輸入的內容:  " + string);
    }

}


public class LogHandler implements InvocationHandler {

    private Object ImpClass;
    private Object lObject;

    public LogHandler(Object realObject) {
        this.ImpClass = realObject;
    }

    public Object bind(Object impclass, Object iObject) {
        this.ImpClass = impclass;
        this.lObject = iObject;
        return Proxy.newProxyInstance(impclass.getClass().getClassLoader(), impclass.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("在目標方法調用前執行.....");
        method.invoke(ImpClass, args);
        System.out.println("在目標方法調用后執行....");
        return null;
    }

    public static void main(String[] args) {

    }
}

class Client {
    public static void main(String[] args) {
        RealSubject subject = new RealSubject();
        LogHandler handler = new LogHandler(subject);

        // 轉化成接口 只能代理實現了接口的類
        SubjectOperations pSubject1 = (SubjectOperations) handler.bind(subject, new LoggerImp());
        System.out.println(pSubject1.getClass().getName());

        pSubject1.print();
        pSubject1.printfStr("YYYYY");
    }
}
 

面試官說讓你講講IOC和AOP時,他們想了解什么?我應該怎么回答

這個問題我也很頭痛,這里我找到一個模版,我打算以后就這樣回答。大家也可以參考一下:

 
知乎

我的回答:
Spring是一套為了解決企業應用開發的復雜性而創建的框架,特點是分層的架構,允許用戶在不同層面使用不同的組件進行組合。同時通過IOC容器來降低耦合,簡化開發。利用AOP來進行切面編程統一管理通用模塊。

我在工作中用到過Spring的IOC容器和AOP面向切面編程,在項目中使用IOC容器幫我很好的理清各個業務對象的關系,同時方便不同組件的更替。在做接口權限驗證時,使用AOP切面編程幫助我能很快的對需要進行驗證的接口完成驗證功能的實現。並且統一進行管理,提高開發效率。

SpringMVC的大致實現過程

 
SpringMVC請求過程

1.用戶發起一個request請求,如果有多個DispatcherServlet,則通過Servletmapping去指定執行的DispatcherServlet。
2.DispatcherServlet把根據URL請求,去HandlerMapping中查找注冊了的URL映射,並返回相應的Handler(一個Controller,多個攔截器),給DispatcherServlet。
3.DispatcherServlet傳遞Handler給HandlerAdapter去執行,返回一個ModelAndView。
4.DispatcherServlet把ModelAndView傳遞給視圖解析器去解析,返回一個視圖view。
5.組裝上Model數據后變成Response請求返回給客戶端。

SpringIOC和AOP中用到的設計模式

簡單工廠

在Spring中經常利用BeanFactory的getBean方法去獲取Bean就是一個簡單工廠的設計模式的實現,通過Bean的ID去獲取這個對象的實例。Bean的ID一般配置在XML文件中

工廠方法

在工廠方法模式中, Spring不會直接利用反射機制創建bean對象, 而是會利用反射機制先找到Factory類,然后利用Factory再去生成bean對象。

而Factory Mothod方式也分兩種, 分別是靜態工廠方法 和 實例工廠方法。

靜態工廠方法

定義一個類

public class Car {
    private int id;
    private String name;
    private int price;

//省略構造函數 getter和setter函數
}

 

定義一個靜態工廠類

public class CarStaticFactory {
    private static Map<Integer, Car> map = new HashMap<Integer,Car>();

    static{
        map.put(1, new Car(1,"Honda",300000));
        map.put(2, new Car(2,"Audi",440000));
        map.put(3, new Car(3,"BMW",540000));
    }

    public static Car getCar(int id){
        return map.get(id);
    }
}

 

XML中的配置文件,可以看到我們指定的class不再是具體的Bean,而是生產Bean的工廠,然后通過工廠方法去創建,有一點不好的地方就是map的初始化在程序中進行,耦合度相對高

<!-- 
        Static Factory method:
        class: the class of Factory
        factory-method: method of get Bean Object
        constructor-arg: parameters of factory-method
     -->
    <bean id="bmwCar" class="com.home.factoryMethod.CarStaticFactory" factory-method="getCar">
        <constructor-arg value="3"></constructor-arg>           
    </bean>

    <bean id="audiCar" class="com.home.factoryMethod.CarStaticFactory" factory-method="getCar">
        <constructor-arg value="2"></constructor-arg>           
    </bean>

 

實例工廠

我們創建一個工廠,這次獲取car的方法不是靜態的了。

public class CarInstanceFactory {
    private Map<Integer, Car> map = new HashMap<Integer,Car>();

    public void setMap(Map<Integer, Car> map) {
        this.map = map;
    }

    public CarInstanceFactory(){
    }

    public Car getCar(int id){
        return map.get(id);
    }
}

 

XML配置文件,把工廠的初始化在文件中配置完成。不用在代碼中編寫。

<!-- Instance Factory Method:
         1.must create a bean for the Instance Factroy First
     -->
     <bean id="carFactory" class="com.home.factoryMethod.CarInstanceFactory">
        <property name="map">
            <map>
                <entry key="4">
                        <bean class="com.home.factoryMethod.Car">
                            <property name="id" value="4"></property>   
                            <property name="name" value="Honda"></property> 
                            <property name="price" value="300000"></property>   
                        </bean>
                </entry>    

                <entry key="6">
                        <bean class="com.home.factoryMethod.Car">
                            <property name="id" value="6"></property>   
                            <property name="name" value="ford"></property>  
                            <property name="price" value="500000"></property>   
                        </bean>
                </entry>
            </map>  
        </property>
     </bean>

     <!-- 2.use Factory bean to get bean objectr 
        factory-bean : the bean define above
        factory-method: method of get Bean Object
        constructor-arg: parameters of factory-method
     -->
     <bean id="car4" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="4"></constructor-arg>           
     </bean>

     <bean id="car6" factory-bean="carFactory" factory-method="getCar">
        <constructor-arg value="6"></constructor-arg>           
     </bean

 

單例模式

保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
spring中的單例模式完成了后半句話,即提供了全局的訪問點BeanFactory。但沒有從構造器級別去控制單例,這是因為spring管理的是是任意的java對象。

下面問答中會專門說一下Spring中的單例模式

核心提示點:Spring下默認的bean均為singleton,可以通過singleton=“true|false” 或者 scope=“?”來指定

代理模式

為其他對象提供一種代理以控制對這個對象的訪問。 從結構上來看和Decorator模式類似,但Proxy是控制,更像是一種對功能的限制,而Decorator是增加職責。
spring的Proxy模式在aop中有體現,比如JdkDynamicAopProxy和Cglib2AopProxy。

觀察者模式

定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
spring中Observer模式常用的地方是listener的實現。如ApplicationListener。ServletContextListener

servlet和Filter初始化前和銷毀后,都會給實現了servletContextListener接口的監聽器發出相應的通知。

模板方法

定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
Template Method模式一般是需要繼承的。這里想要探討另一種對Template Method的理解。spring中的JdbcTemplate,在用這個類時並不想去繼承這個類,因為這個類的方法太多,但是我們還是想用到JdbcTemplate已有的穩定的、公用的數據庫連接,那么我們怎么辦呢?我們可以把變化的東西抽出來作為一個參數傳入JdbcTemplate的方法中。但是變化的東西是一段代碼,而且這段代碼會用到JdbcTemplate中的變量。怎么辦?那我們就用回調對象吧。在這個回調對象中定義一個操縱JdbcTemplate中變量的方法,我們去實現這個方法,就把變化的東西集中到這里了。然后我們再傳入這個回調對象到JdbcTemplate,從而完成了調用。這可能是Template Method不需要繼承的另一種實現方式吧。

不用繼承的模版方法
 
我們把想要的connection連接賦值到回調對象中
 
獲取從JDBCTemplate中傳入的穩定的Connection對象,進行自己定義的操作
繼承的模版方法,比如常見

我們定義一個算法的執行過程,里面會調用4個小步驟,具體的小步驟的實現我們交過實現的子類去完成。然后用戶調用整個process方法就能實現功能。

public abstract class EntityProcessor {
 
    public final void processEntity() {
        getEntityData();
        createEntity();
        validateEntity();
        persistEntity();
    }
 
    protected abstract void getEntityData();
    protected abstract void createEntity();
    protected abstract void validateEntity();
    protected abstract void persistEntity();
 
}

 

IOC 的單例模式是怎么實現的

IOC的單例模式不是使用的懶漢式或者餓漢式,使用的是

單例注冊表,通過把Bean的名稱和對象組成的key-value注冊進HashMap中。每次需要獲取相應的類時,根據名稱去獲取,如果沒有這個bean就去讀取Bean的定義。估計type是需要單例還是多例,如果是單例注冊進入表並返回,如是多例不注冊,創建一個Bean並返回。

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{    
   /**  
    * 充當了Bean實例的緩存,實現方式和單例注冊表相同  
    */    
   private final Map singletonCache=new HashMap();    
   public Object getBean(String name)throws BeansException{    
       return getBean(name,null,null);    
   }    
...    
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{    
      //對傳入的Bean name稍做處理,防止傳入的Bean name名有非法字符(或則做轉碼)    
      String beanName=transformedBeanName(name);    
      Object bean=null;    
      //手工檢測單例注冊表    
      Object sharedInstance=null;    
      //使用了代碼鎖定同步塊,原理和同步方法相似,但是這種寫法效率更高    
      synchronized(this.singletonCache){    
         sharedInstance=this.singletonCache.get(beanName);    
       }    
      if(sharedInstance!=null){    
         ...    
         //返回合適的緩存Bean實例    
         bean=getObjectForSharedInstance(name,sharedInstance);    
      }else{    
        ...    
        //取得Bean的定義    
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);    
         ...    
        //根據Bean定義判斷,此判斷依據通常來自於組件配置文件的單例屬性開關    
        //<bean id="date" class="java.util.Date" scope="singleton"/>    
        //如果是單例,做如下處理    
        if(mergedBeanDefinition.isSingleton()){    
           synchronized(this.singletonCache){    
            //再次檢測單例注冊表    
             sharedInstance=this.singletonCache.get(beanName);    
             if(sharedInstance==null){    
                ...    
               try {    
                  //真正創建Bean實例    
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);    
                  //向單例注冊表注冊Bean實例    
                   addSingleton(beanName,sharedInstance);    
               }catch (Exception ex) {    
                  ...    
               }finally{    
                  ...    
              }    
             }    
           }    
          bean=getObjectForSharedInstance(name,sharedInstance);    
        }    
       //如果是非單例,即prototpye,每次都要新創建一個Bean實例    
       //<bean id="date" class="java.util.Date" scope="prototype"/>    
       else{    
          bean=createBean(beanName,mergedBeanDefinition,args);    
       }    
}    
...    
   return bean;    
}    
} 

 

如何保證IOC中有狀態Bean的線程安全

有狀態Bean就是一些Bean實例中含有一些非線程安全的成員變量,那么當多個線程去同時操作這個Bean時,就有可能導致對這些變量的操作出現問題。

使用ThreadLocal類可以做到,ThreadLocal會為變量在每個線程中創建本地變量副本,那么每個線程可以訪問自己內部的副本變量。大家互不干擾,以此達到線程安全的目的。

最常見的數據庫連接管理類:

class ConnectionManager {
     
    private static Connection connect = null;
     
    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }
     
    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

 

第一,這里面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創建connect;第二,由於connect是共享變量,那么必然在調用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進行數據庫操作,而另外一個線程調用closeConnection關閉鏈接。

使用ThreadLocal后:

當多個線程去取session的時候,都會去拿自己線程的本地變量副本,沒有就創建一個注冊進去並返回。

private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

 

舉例說明IOC如何使用,AOP如何使用

IOC的使用:
首先定義你需要的Bean的類以及成員變量,然后將這些類全部注冊到Spring的ApplicationContext.xml文件中,然后在xml文件進進行依賴配置。之后只需要在程序中創建一個BeanFactory,加載XML文件,getBean就能獲得我們要的實例了

AOP使用Aspectj:
定義一個Advice類,里面進行一個或多個pointcut的定義,以及在哪個pointcut下使用那些方法,

定義的pointcut方法本身只是用來標記的,用於指定在哪里進行一個切入,比如這里我使用的是within代表切入NeedLogService類中的所有方法


 
定義一個pointcut

有執行前的,有執行后的,有異常時的


 
定義切面方法

當我們調用這個類的方法時,就會發現被增強了


 
被代理類定義
 
測試調用
 
輸出

參考文章:
Spring IOC核心源碼學習
使用 IoC 反轉控制的三種設計模式
Spring的單例模式底層實現
Spring中Singleton模式的線程安全
【SSH進階之路】Spring的AOP逐層深入——采用注解完成AOP(七)
深入解析spring中用到的九種設計模式
Spring 通過工廠方法(Factory Method)來配置bean

 
鏈接:https://www.jianshu.com/p/40d0a6ad21d2 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM