Spring Framework 的核心組件有三個: Spring Core,Spring Context 和 Spring Beans,它們奠基了 Spring 的基礎並撐起了 Spring 的框架結構。Spring 的其它功能特性例如 Web、AOP、JDBC 等都是在其基礎上發展實現的。
I. Bean 組件
Spring 使用工廠模式來管理程序中使用的對象(Bean),Bean 工廠最上層的接口爲 BeanFactory,簡單來看,工廠就是根據須要返回相應的 Bean 實例。java
public interface BeanFactory {
//... Object getBean(String name); }
在工廠模式中,在工廠的實現類中生成 Bean 返回給調用客戶端,這就要求客戶端提供生成本身所需類實例的工廠類,增長客戶負擔。Spring 結合控制反轉和依賴注入爲客戶端提供所需的實例,簡化了客戶端的操做。具體的實現方式大體以下。spring
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { /** Map of bean definition objects, keyed by bean name */ private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>; public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){ //... } }
beanDefinitionMap 做爲具體的 Bean 容器,Spring 建立的對象實例保存其中。客戶端須要時,使用工廠的 getBean 方法去試圖獲得相應的實例,若是實例已存在,則返回該實例;若是實例不存在,則首先產生相應實例並經過 registerBeanDefinition 方法將其保存在 beanDefinitionMap 中(Lazy Initialization),而後返回該實例給客戶端。markdown
beanDefinitionMap 並不直接保存實例自己,而是將實例封裝在 BeanDefinition 對象後進行保存。BeanDefinition 包含了實例的全部信息,其簡化版的定義以下。框架
public class BeanDefinition { private Object bean; private Class<?> beanClass; private String beanClassName; // Bean 屬性字段的初始化值 private BeanPropertyValues beanPropertyValues; //... }
Spring Bean 工廠生產 Bean 時
1) 先將實例的類型參數保存到 beanClass 和 beanClassName,將須要初始化的字段名和值保存到 beanPropertyValues 中,這個過程 Spring 經過控制反轉來實現,本文第二小節將予以簡要說明
2) 生成 bean 實例,並利用反射機制將須要初始化的字段值寫入 bean 實例,將實例保存在 bean 中,完成 BeanDefinition 的構建。
假設咱們已經完成了步驟 1) 的操做,以後的過程用代碼表述以下所示。ide
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){ //生成 bean 實例,並完成初始化 Object bean = createBean(beanDefinition); //將 bean 實例保存在 beanDefinition 中 beanDefinition.setBean(bean); //將 beanDefinition 實例保存在 Spring 容器中 beanDefinitionMap.put(beanName, beanDefinition); } protected Object createBean(BeanDefinition beanDefinition) { try{ Object bean = beanDefinition.getBeanClass().newInstance(); try { setBeanPropertyValues(bean, beanDefinition); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) { e.printStackTrace(); } return bean; }catch(InstantiationException e){ e.printStackTrace(); }catch(IllegalAccessException e){ e.printStackTrace(); } return null; } protected void setBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ for(PropertyValue pv : beanDefinition.getBeanPropertyValues().getBeanPropertyValues()){ Field beanFiled = bean.getClass().getDeclaredField(pv.getName()); beanFiled.setAccessible(true); beanFiled.set(bean, pv.getValue()); } }
II. Context 組件
通常地,傳統的程序設計中,不管是使用工廠建立實例,或是直接建立實例,實例調用者都要先主動建立實例,然後才能使用。控制反轉(Inverse of Control) 將實例的建立過程交由容器實現,調用者將控制權交出,是所謂控制反轉。
依賴注入(Dependence Injection) 在控制反轉的基礎上更進一步。若是沒有依賴注入,容器建立實例並保存後,調用者須要使用 getBean(String beanName) 才能獲取到實例。使用依賴注入時,容器會將 Bean 實例自動注入到完成相應配置的調用者,供其進一步使用。函數
Context 組件藉助上述的控制反轉和依賴注入,協助實現了 Spring 的 Ioc 容器。下面咱們以一個 Service 類做爲所需的 Bean 實例進行說明。實際應用中,咱們會須要 Spring 管理不少 Bean 實例。post
public class SampleService { private String service; public String getService() { return service; } public void setService(String service) { this.service= service; } }
在程序運行過程當中,須要一個 SampleService ,咱們不讓調用者 new 一個實例,而是在配置文件中代表該 SampleService 的實例交由 Spring 容器進行管理,並指定其初始化參數。配置文件即資源,其內容以下。this
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean name="sampleService " class="com.service.SampleService "> <property name="service" value="This is a service"></property> </bean> </beans>
Spring Core 組件提供 ResourceLoader 接口,便於讀入 xml 文件或其餘資源文件。其核心功能代碼應該提供以下方法。url
public class ResourceLoader { public Resource getResource(String location){ URL resource = this.getClass().getClassLoader().getResource(location); return new UrlResource(resource); } } // UrlResource 的功能代碼 public class UrlResource implements Resource { private final URL url; public UrlResource(URL url){ this.url = url; } @Override public InputStream getInputStream() throws IOException { URLConnection urlConnection = url.openConnection(); urlConnection.connect(); return urlConnection.getInputStream(); } }
即加載資源文件,並以數據流的形式返回。Context 根據資源中的定義,生成相應的 bean 並保存在容器中,bean 的名字是 sampleService ,供程序進一步使用。這樣就完成了控制反轉的工做。接下來就須要把 sampleService 注入到須要使用它的地方,亦即完成依賴注入操做。
如今假設 SampleController 中使用 SampleService 的對象,Spring 提供三種依賴注入的方式,構造器注入、setter 注入和註解注入。spa
public class SampleController { /** * 3. 註解注入 **/ /* @Autowired */ private SampleService sampleService; /** * 1. 構造器注入 **/ public SampleController(SampleService sampleService){ this.sampleService = sampleService; } //無參構造函數 public SampleController(){} // 類的核心功能 public void process(){ System.out.println(sampleService.getService()); } /** * 2. setter 注入 **/ /*public void setService(SampleService service) { this.service= service; }*/ }
三種注入方式在配置文件中對應不一樣的配置方式,在前面 xml 文件的基礎上,咱們能夠分別實現這三種注入方式。須要注意的是,這裏 SampleController 也是使用 Spring 的 Ioc 容器生成管理的。
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean name="sampleService " class="com.service.SampleService "> <property name="service" value="This is a service"></property> </bean> <!-- 1. 構造器注入方式爲SampleContorller 的 bean 注入 SampleService --> <bean name="sampleContorller" class="com.controller.SampleContorller"> <!-- index 是構造方法中相應參數的順序 --> <constructor-arg index="0" ref="sampleService"></constructor-arg> </bean> <!-- 2. setter 注入方式爲SampleContorller 的 bean 注入 SampleService --> <!-- <bean name="sampleContorller" class="com.controller.SampleContorller"> <property name="sampleService " ref="sampleService"></property> </bean> --> <!-- 3. 註解注入方式爲SampleContorller 的 bean 注入 SampleService --> <!-- <bean name="sampleContorller" class="com.controller.SampleContorller"> <!-- 不須要配置,Spring 自動按照類型注入相應的 bean --> </bean> --> </beans>