手寫Spring框架準備工做之Spring基礎

什麼是Spring?

file

Spring框架存在的意義就是爲了下降耦合度, 根據不一樣的代碼採用不一樣的方式, 經過IOC來下降主業務邏輯之間的耦合度, 經過AOP來下降系統級服務(如日誌、安全、事務等)和主業務邏輯之間的耦合度. 此外還提供了一個Web層的框架Spring MVC.git

Spring的核心構成

IOC Spring最核心的模塊web

DAO Spring對訪問數據庫的支持,主要是JDBC面試

MVC Spring對MVC設計模式的支持(SSM)spring

AOP (Aspect Orientied Programing)面向切面編程數據庫

ORM 對象關係映射 Spring 和ORM框架整合 Mybatis編程

Spring容器

在介紹Spring容器以前, 咱們先介紹什麼是bean. 簡單來講, 被Spring容器管理的對象就叫bean, 好比Controller/Action, Service, Dao.後端

<bean id="userControllerId" class="org.a.IOC.UserController"></bean>
<bean id="userServiceId" class="org.a.IOC.UserServiceImpl"></bean>
<bean id="BookDaoId" class="org.a.IOC.UserDaoImpl"></bean>

複製代碼

Spring容器管理着項目中全部bean對象的實例化與裝配, 有兩種, 分別是 BeanFactory 和 ApplicationContext. 其中 ApplicationContext 是 BeanFactory 的一個子接口, 補充瞭如下幾個功能:設計模式

  • 更容易與Spring的AOP特性集成
  • 消息資源處理(用於國際化)
  • 事件發佈
  • 應用程序層特定的上下文,如web應用程序中使用的WebApplicationContext

上述幾個功能只需瞭解就行, 對於二者的區別, 咱們須要記住的是:安全

  • BeanFactory 採用延遲加載策略, 在第一次調用getBean()時, 纔去讀取配置信息, 生成某個bean的實例.
  • ApplicationContext 在初始化時就會讀取配置信息, 生成全部bean的實例. 上面兩種特徵致使的結果是, 若是配置信息有錯, -BeanFactory在調用getBean()時纔會拋異常, 而 ApplicationContext 在初始化的時候就會拋異常, 幫助咱們及時檢查配置是否正確.
  • 二者都支持BeanPostProcessor和BeanFactoryPostProcessor, 但BeanFactory須要手動註冊, 而ApplicationContext是自動註冊.

大部分狀況下咱們使用的都是ApplicationContext, 這篇博客下面的內容也都是基於ApplicationContext.bash

file

配置元數據

Spring容器經過讀取元數據來獲取要實例化、裝配的對象. 元數據有三種格式, 分別是XML文件, Java註解和Java代碼. 本文只介紹XML格式和Java註解格式的元數據.

<beans>
    <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl">
        <property name="bookDao" ref="BookDaoId"></property>
    </bean>
    <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean>
</beans>

複製代碼

上面這段代碼就是基於XML文件的元數據, ApplicationContext 的兩個實現類 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 用來加載XML格式的元數據. 二者的區別在於 ClassPathXmlApplicationContext 是基於類路徑, 而 FileSystemXmlApplicationContext 是基於文件路徑.

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
ApplicationContext context = new FileSystemXmlApplicationContext("D:\\springtest\\src\\main\\resources\\spring\\bean.xml");

複製代碼

ApplicationContext的實現類AnnotationConfigApplicationContext用來加載Java註解格式的元數據.

@Configuration
@ComponentScan(basePackages = "org.tyshawn")
public class AppConfig {
}
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

複製代碼

IOC

IoC也稱爲依賴注入(dependency injection, DI). 這是一個過程, 在這個過程當中, 首先經過在對象實例上設置的屬性來定義bean之間的依賴關係, 而後Spring容器在建立bean時注入這些依賴項(這個注入過程也叫作裝配). 依賴注入有兩種, 分別是基於構造方法的注入和基於Setter方法的注入.

基於構造方法的注入

public interface IBookDao {
    void insert();
}
public class BookDaoImpl implements IBookDao {
    @Override
    public void insert() {
        System.out.println("add book");
    }
}

public interface IBookService {
    void addBook();
}
public class BookServiceImpl implements IBookService {
    private IBookDao bookDao;
    public BookServiceImpl(IBookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void addBook() {
        this.bookDao.insert();
    }
}

<beans>
    <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl">
        <constructor-arg ref="BookDaoId"/>
    </bean>
    <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean>
</beans>

public class Test{
    public static void main(String[] args) throws ParseException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        IBookService bookService = (IBookService) context.getBean("BookServiceId");
        bookService.addBook();
    }
}

複製代碼

基於Setter方法的注入

public interface IBookDao {
    void insert();
}
public class BookDaoImpl implements IBookDao {
    @Override
    public void insert() {
        System.out.println("add book");
    }
}

public interface IBookService {
    void addBook();
}
public class BookServiceImpl implements IBookService {
    private IBookDao bookDao;
    public void setBookDao(IBookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void addBook() {
        this.bookDao.insert();
    }
}

<beans>
    <bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl">
        <property name="bookDao" ref="BookDaoId"></property>
    </bean>
    <bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean>
</beans>

public class Test{
    public static void main(String[] args) throws ParseException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        IBookService bookService = (IBookService) context.getBean("BookServiceId");
        bookService.addBook();
    }
}

複製代碼

Bean的做用域

Bean的做用域有六種, 其中後四種只支持Web應用.

做用域 描述
singleton 默認. bean在每個Spring容器內只有一個實例
prototype 每次從Spring容器中獲取到的bean都是一個新的實例
request bean在每個 HTTP Request 中只有一個實例, 只支持Web應用
session bean在每個 HTTP Session 中只有一個實例, 只支持Web應用
application bean在每個 ServletContext 中只有一個實例, 只支持Web應用
websocket bean在每個 WebSocket 中只有一個實例, 只支持Web應用
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

複製代碼

singleton和application的區別 (1) 在做用域爲singleton時, bean在每個Spring容器內只有一個實例, 而應用能夠有多個容器. (2) 在做用域爲application時, bean在整個應用中只有一個實例. (3) 做用域application只支持Web應用.

//能夠看到, 從兩個容器中獲取的bean不是同一個
public class Test{
    public static void main(String[] args) throws ParseException {
        ApplicationContext context1 = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ApplicationContext context2 = new FileSystemXmlApplicationContext("D:\\springtest\\src\\main\\resources\\spring\\bean.xml");
        IBookService bookService1 = (IBookService) context1.getBean("BookServiceId");
        IBookService bookService2 = (IBookService) context2.getBean("BookServiceId");
        System.out.println(bookService1);
        System.out.println(bookService2);
    }
}

org.tyshawn.service.impl.BookServiceImpl@23faf8f2
org.tyshawn.service.impl.BookServiceImpl@2d6eabae

複製代碼

具備多例bean依賴的單例bean

一個bean的做用域是 singleton, 而它的屬性的做用域是 prototype, 以下所示:

<bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl" scope="singleton">
    <constructor-arg ref="BookDaoId"/>
</bean>
<bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl" scope="prototype"></bean>

複製代碼

咱們想要的效果是, 每次獲取BookServiceId時都是同一個bean, 而它的屬性BookDaoId都是一個新的bean. 但這種狀況是不可能的, 由於BookServiceId只會實例化, 裝載一次. 要想達到咱們指望的效果, 須要使用方法注入:

方法注入

Spring框架經過使用來自CGLIB庫的字節碼生成器來動態生成覆蓋該方法的子類來實現此方法注入.

public class BookServiceImpl implements IBookService {
    private IBookDao bookDao;

    public IBookDao getBookDao() {
        return bookDao;
    }

    @Override
    public void addBook() {
        IBookDao bookDao = getBookDao();
        System.out.println(bookDao);
    }
}

<bean id="BookServiceId" class="org.tyshawn.service.impl.BookServiceImpl" scope="singleton">
    <lookup-method name="getBookDao" bean="BookDaoId"></lookup-method>
</bean>
<bean id="BookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl" scope="prototype"></bean>

public class Test{
    public static void main(String[] args) throws ParseException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        BookServiceImpl bookService1 = (BookServiceImpl) context.getBean("BookServiceId");
        BookServiceImpl bookService2 = (BookServiceImpl) context.getBean("BookServiceId");
        bookService1.addBook();
        bookService2.addBook();
    }
}

org.tyshawn.dao.Impl.BookDaoImpl@6121c9d6
org.tyshawn.dao.Impl.BookDaoImpl@87f383f

複製代碼

Bean的生命週期

生命週期回調

(1) 初始化回調

在Spring容器將bean實例化, 設置屬性值以後將會執行初始化回調. 初始化回調有兩種設置方式:

方式一(推薦)

<bean id="exampleInitBean1" class="org.tyshawn.example.ExampleBean" init-method="init"/>

public class ExampleBean {
    public void init() {
        System.out.println("do some initialization work.");
    }
}
複製代碼

方式二(不推薦)

<bean id="exampleInitBean2" class="org.tyshawn.example.AnotherExampleBean"/>

public class AnotherExampleBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("do some initialization work.");
    }
}

public class Test{
    public static void main(String[] args) throws ParseException {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example1 = (ExampleBean) context.getBean("exampleInitBean1");
        AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleInitBean2");
        System.out.println(example1);
        System.out.println(example2);
    }
}

do some initialization work.
do some initialization work.
org.tyshawn.example.ExampleBean@4eb7f003
org.tyshawn.example.AnotherExampleBean@eafc191
複製代碼

(2) 銷燬回調

當bean被銷燬以前, 將會執行銷燬回調. 銷燬回調有兩種設置方式:

方式一(推薦)

<bean id="exampleDestoryBean1" class="org.tyshawn.example.ExampleBean" destroy-method="destory"/>

public class ExampleBean {
    public void destroy() {
        System.out.println("do some destruction work.");
    }
}
複製代碼

方式二(不推薦)

<bean id="exampleDestoryBean2" class="org.tyshawn.example.AnotherExampleBean"/>

public class AnotherExampleBean implements DisposableBean {
    @Override
    public void destroy() {
        System.out.println("do some destruction work.");
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example1 = (ExampleBean) context.getBean("exampleDestoryBean1");
        AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleDestoryBean2");
	  //當容器被關閉時, 容器內的bean就被銷燬了
        context.registerShutdownHook();
    }
}

do some destruction  work.
do some destruction  work.
複製代碼

(3) 初始化回調 / 銷燬回調的兩種方式同時配置

當 初始化回調 / 銷燬回調的兩種方式同時配置時會出現什麼結果呢?

<bean id="exampleDestoryBean2" class="org.tyshawn.example.AnotherExampleBean" destroy-method="cleanup"/>

public class AnotherExampleBean implements DisposableBean {
    @Override
    public void destroy() {
        System.out.println("do some destruction work.");
    }

    public void cleanup() {
        System.out.println("do some cleanup work.");
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        AnotherExampleBean example2 = (AnotherExampleBean) context.getBean("exampleDestoryBean2");
        context.registerShutdownHook();
    }
}

do some destruction work.
do some cleanup work.
複製代碼

結果是兩種方式都執行, 但 DisposableBean / InitializingBean 在前, destroy-method / init-method 在後.

(4) 啓動和關閉回調

若是Spring容器中的bean實現了 Lifecycle 接口, 當Spring容器啓動時, 將會調用這些bean的start()方法, 當Spring容器關閉時, 將會調用這些bean的stop()方法.

public interface Lifecycle {
    void start();

    void stop();

    boolean isRunning();
複製代碼

在不少狀況下, start()方法和stop()方法的調用順序是重要的, 若是兩個bean存在依賴關係, 好比 a 依賴 b (b是a的屬性), 這時 a 先調用start()方法, b 先調用stop()方法. 但若是咱們不知道依賴關係, 卻想讓 a 在 b 以前調用start()方法, 這時咱們就能夠用 SmartLifecycle 接口代替 Lifecycle 接口.

public interface Phased {
    int getPhase();
}

public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();

    void stop(Runnable callback);
複製代碼

SmartLifecycle接口的方法介紹:

當 isAutoStartup() 返回true, Spring容器啓動時會調用bean的 start() 方法

當 isRunning() 返回true, Spring容器銷燬時會調用bean的 stop(Runnable runnable) 方法

getPhase() 返回的是優先級, 當有多個bean時, 返回值大的先執行start()方法, 銷燬時順序相反. 容器內沒有實現SmartLifecycle接口, 而實現了Lifecycle接口的bean返回值是0. 負數表明最高優先級.

<bean id="exampleDestoryBean" class="org.tyshawn.example.ExampleBean" />

  public class ExampleBean implements SmartLifecycle {
      private boolean isRunning = false;
  
      @Override
      public boolean isAutoStartup() {
          return true;
      }
  
      @Override
      public void stop(Runnable runnable) {
          runnable.run();
          System.out.println("stop runnable ...");
      }
  
      @Override
      public void start() {
          isRunning = true;
          System.out.println("start run ...");
      }
  
      @Override
      public void stop() {
          System.out.println("stop run ...");
      }
  
      @Override
      public boolean isRunning() {
          return isRunning;
      }
  
      @Override
      public int getPhase() {
          return -1;
      }
  }
  
  public class Test{
      public static void main(String[] args) throws Exception {
          ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
          ExampleBean example = (ExampleBean) context.getBean("exampleDestoryBean");
          System.out.println(example);
          context.registerShutdownHook();
      }
  }
  
  start run ...
  org.tyshawn.example.ExampleBean@1b26f7b2
  stop runnable ...
複製代碼

(5)初始化回調/銷燬回調 和 啓動/關閉回調的執行順序

在瞭解了初始化回調/銷燬回調 和 啓動/關閉回調以後, 咱們確定很想知道當他們同時存在時, 他們的執行順即是怎麼樣的, 以下:

<bean id="exampleBean" class="org.tyshawn.example.ExampleBean" init-method="init" destroy-method="destroy"/>

public class ExampleBean implements SmartLifecycle{
    private boolean isRunning = false;

    public void init() {
        System.out.println("do some init work.");
    }

    public void destroy() {
        System.out.println("do some destruction work.");
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable runnable) {
        runnable.run();
        System.out.println("stop runnable ...");
    }

    @Override
    public void start() {
        isRunning = true;
        System.out.println("start run ...");
    }

    @Override
    public void stop() {
        System.out.println("stop run ...");
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }

    @Override
    public int getPhase() {
        return -1;
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example1 = (ExampleBean) context.getBean("exampleBean");
        context.registerShutdownHook();
    }
}

do some init work.
start run ...
stop runnable ...
do some destruction work.
複製代碼

從上面的實例可知, 容器啓動時先執行初始化回調, 再執行啓動回調. 容器關閉時, 先執行關閉回調, 再執行銷燬回調.

XXXAware

Spring提供了一系列以Aware結尾的接口, 翻譯爲XXX可感知, 做用是若是某個bean須要Spring提供一些特定的依賴, 就實現對應的XXXAware接口. 下面是咱們經常使用三個Aware接口:

接口 做用
BeanNameAware 提供聲明bean時的id
BeanFactoryAware 提供BeanFactory容器
ApplicationContextAware 提供ApplicationContext容器

(1) BeanNameAware

<bean id="exampleDestoryBean" class="org.tyshawn.example.ExampleBean" />

public class ExampleBean implements BeanNameAware{

    @Override
    public void setBeanName(String beanName) {
        System.out.println("beanName: " + beanName);
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example = (ExampleBean) context.getBean("exampleDestoryBean");
    }
}

beanName: exampleDestoryBean
複製代碼

(2) BeanFactoryAware

<bean id="exampleBean" class="org.tyshawn.example.ExampleBean" />

public class AnotherExampleBean implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("ApplicationContext: " + applicationContext);
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example = (ExampleBean) context.getBean("exampleBean");
    }
}

BeanFactory: org.springframework.beans.factory.support.DefaultListableBeanFactory@2344fc66: defining beans [exampleBean]; root of factory hierarchy
複製代碼

(3) ApplicationContextAware

<bean id="exampleBean" class="org.tyshawn.example.AnotherExampleBean" />

public class AnotherExampleBean implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("ApplicationContext: " + applicationContext);
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        AnotherExampleBean example = (AnotherExampleBean) context.getBean("exampleBean");
    }
}

ApplicationContext: org.springframework.context.support.ClassPathXmlApplicationContext@573fd745: startup date [Thu Apr 18 18:59:43 CST 2019]; root of context hierarchy
複製代碼

BeanPostProcessor

若是咱們想在Spring容器完成bean的初始化先後加一些定製邏輯, 咱們能夠向容器註冊一個或多個定製BeanPostProcessor實現. 當有多個BeanPostProcessor定製時, 咱們同時要實現Ordered接口.

<bean id="exampleBean" class="org.tyshawn.example.AnotherExampleBean" init-method="init"/>
<bean class="org.tyshawn.example.CustomBeanPostProcessor1"/>
<bean class="org.tyshawn.example.CustomBeanPostProcessor2"/>

public class CustomBeanPostProcessor1 implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean執行init()方法以前的定製邏輯1.");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean執行init()方法以後的定製邏輯1.");
        return bean;
    }

    @Override
    public int getOrder() {
        return 2;
    }
}

public class CustomBeanPostProcessor2 implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean執行init()方法以前的定製邏輯2.");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("bean執行init()方法以後的定製邏輯2.");
        return bean;
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

public class AnotherExampleBean {
    public void init() {
        System.out.println("init() ...");
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        AnotherExampleBean example = (AnotherExampleBean) context.getBean("exampleBean");
    }
}

bean執行init()方法以前的定製邏輯2.
bean執行init()方法以前的定製邏輯1.
init() ...
bean執行init()方法以後的定製邏輯2.
bean執行init()方法以後的定製邏輯1.
複製代碼

bean的生命週期

bean在Spring容器中的生命週期是怎麼樣的呢?
(1) 實例化
(2) 設置屬性值
(3) 調用BeanNameAware的setBeanName()方法
(4) 調用BeanFactoryAware的setBeanFactory()方法
(5) 調用ApplicationContext的setApplicationContext()方法
(6) 調用BeanPostProcessor的postProcessBeforeInitialization()方法
(7) 調用InitializingBean的afterPropertiesSet()方法
(8) 調用xml配置的初始化方法
(9) 調用BeanPostProcessor的postProcessAfterInitialization()方法
(10) 容器啓動.
(11) bean可使用了.
(12) 容器關閉.
(13) 調用DisposableBean的destory()方法
(14) 調用xml配置的銷燬方法

代碼實例以下:

<bean id="exampleBean" class="org.tyshawn.example.ExampleBean" init-method="init" destroy-method="dest"/>
<bean class="org.tyshawn.example.CustomBeanPostProcessor"/>

public class CustomBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("調用BeanPostProcessor的postProcessBeforeInitialization()方法");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("調用BeanPostProcessor的postProcessAfterInitialization()方法");
        return bean;
    }
}

public class ExampleBean implements InitializingBean, DisposableBean, SmartLifecycle, BeanNameAware, BeanFactoryAware, ApplicationContextAware {
    private boolean isRunning = false;

    public void init() {
        System.out.println("調用xml配置的初始化方法");
    }

    public void dest() {
        System.out.println("調用xml配置的銷燬方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("調用InitializingBean的afterPropertiesSet()方法");
    }

    @Override
    public void destroy() {
        System.out.println("調用DisposableBean的destory()方法");
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public void stop(Runnable runnable) {
        runnable.run();
        System.out.println("容器關閉.");
    }

    @Override
    public void start() {
        isRunning = true;
        System.out.println("容器啓動.");
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean isRunning() {
        return isRunning;
    }

    @Override
    public int getPhase() {
        return -1;
    }

    @Override
    public void setBeanName(String beanName) {
        System.out.println("調用BeanNameAware的setBeanName()方法");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("調用BeanFactoryAware的setBeanFactory()方法");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("調用ApplicationContext的setApplicationContext()方法");
    }

    public void sayHello() {
        System.out.println("bean可使用了.");
    }
}

public class Test{
    public static void main(String[] args) throws Exception {
        ConfigurableApplicationContext  context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
        ExampleBean example = (ExampleBean) context.getBean("exampleBean");
        example.sayHello();
        context.registerShutdownHook();
    }
}

調用BeanNameAware的setBeanName()方法
調用BeanFactoryAware的setBeanFactory()方法
調用ApplicationContext的setApplicationContext()方法
調用BeanPostProcessor的postProcessBeforeInitialization()方法
調用InitializingBean的afterPropertiesSet()方法
調用xml配置的初始化方法
調用BeanPostProcessor的postProcessAfterInitialization()方法
容器啓動.
bean可使用了.
容器關閉.
調用DisposableBean的destory()方法
調用xml配置的銷燬方法
複製代碼

FactoryBean

面試中經常問到FactoryBean和BeanFactory的區別, 咱們已經知道BeanFactory是一個Spring容器, 應用中全部bean的實例都存儲在其中, 那FactoryBean是什麼呢?

FactoryBean是一個生成bean的工廠. 通常狀況下咱們都是用XML文件來配置bean, 但若是咱們有複雜的初始化邏輯, 相對於冗長的XML, 用Java代碼能夠更好地表達, 這時咱們就能夠建立本身的FactoryBean, 在該類中編寫複雜的初始化邏輯, 而後將定製的FactoryBean插入容器中.

public interface FactoryBean<T> {
    T getObject() throws Exception; 

    Class<?> getObjectType();

    boolean isSingleton();
複製代碼

FactoryBean接口的方法介紹:

  • getObject(), 返回工廠建立的對象的實例.
  • getObjectType(), 返回對象類型.
  • isSingleton, 是不是單例.
<bean id="tool" class="org.tyshawn.example.ToolFactory">
      <property name="toolName" value="iphone xs"/>
  </bean>	
  
  public class Tool {
      private String name;
  
      public Tool(String name) {
          this.name = name;
      }
  
      public String getName() {
          return name;
      }
  
      public void setName(String name) {
          this.name = name;
      }
  }
  
  public class ToolFactory implements FactoryBean<Tool> {
      private String toolName;
  
      @Override
      public Tool getObject() throws Exception {
          return new Tool(toolName);
      }
  
      @Override
      public Class<?> getObjectType() {
          return Tool.class;
      }
  
      @Override
      public boolean isSingleton() {
          return false;
      }
  
      public String getToolName() {
          return toolName;
      }
  
      public void setToolName(String toolName) {
          this.toolName = toolName;
      }
  }
  
  public class Test{
      public static void main(String[] args) throws Exception {
          ConfigurableApplicationContext  context = new ClassPathXmlApplicationContext("classpath:spring/bean.xml");
          Tool tool = (Tool) context.getBean("tool");
          System.out.println(tool.getName());
      }
  }
  
  iphone xs	
複製代碼

基於註解的容器配置

(1) 容器配置

@ComponentScan用來配置類掃描路徑.

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
}
複製代碼

等同於XML配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>
複製代碼

(2) 聲明bean

在基於XML的配置中, 下面這行代碼聲明瞭一個bean.

<bean id="bookDaoId" class="org.tyshawn.dao.Impl.BookDaoImpl"></bean>
複製代碼

在基於註解的容器配置中, 咱們能夠經過如下註解聲明一個bean.

  • @Component, 聲明一個bean.
  • @Controller, 聲明一個web層的bean.
  • @Service, 聲明一個service層的bean.
  • @Repository, 聲明一個dao層的bean. (3) 自動注入屬性

@Autowired 爲按類型注入屬性, 若是注入屬性有多個時, 須要經過 @Qualifier 指定名稱. @Resource = @Autowired + @Qualifier.

@Configuration
@ComponentScan(basePackages = "org.tyshawn")
public class AppConfig {
}

@Repository("bookDao")
public class BookDaoImpl implements IBookDao {
    @Override
    public void insert() {
        System.out.println("add book");
    }
}

@Service("bookService")
public class BookServiceImpl implements IBookService {
//    @Autowired
//    @Qualifier(value = "bookDao")
    @Resource(name = "bookDao")
    private IBookDao bookDao;

    @Override
    public void addBook() {
        bookDao.insert();
    }
}

public class Test{

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        IBookService bookService = context.getBean("bookService", BookServiceImpl.class);
        bookService.addBook();
    }
}
add book
複製代碼

(3) 做用域

  • @Scope(「singleton」)
  • @Scope(「prototype」)
  • @RequestScope
  • @SessionScope
  • @ApplicationScope
@Configuration
  @ComponentScan(basePackages = "org.tyshawn")
  public class AppConfig {
  }
  
  @Component("exampleBean")
  @Scope("prototype")
  public class ExampleBean {
  }
  
  public class Test{
  
      public static void main(String[] args) {
          ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
          ExampleBean exampleBean1 = context.getBean("exampleBean", ExampleBean.class);
          ExampleBean exampleBean2 = context.getBean("exampleBean", ExampleBean.class);
          System.out.println(exampleBean1);
          System.out.println(exampleBean2);
      }
  }
  org.tyshawn.example.ExampleBean@64c87930
  org.tyshawn.example.ExampleBean@400cff1a
複製代碼

(4) 生命週期回調

  • @PostConstruct, 初始化回調
  • @PreDestroy, 銷燬回調
@Configuration
  @ComponentScan(basePackages = "org.tyshawn")
  public class AppConfig {
  }
  
  @Component("exampleBean")
  public class ExampleBean {
      @PostConstruct
      public void init() {
          System.out.println("初始化方法");
      }
  
      @PreDestroy
      public void destroy() {
          System.out.println("銷燬方法");
      }
  }
  
  public class Test{
  
      public static void main(String[] args) {
          AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
          ExampleBean exampleBean = context.getBean("exampleBean", ExampleBean.class);
          System.out.println(exampleBean);
          context.registerShutdownHook();
      }
  }
  
  初始化方法
  org.tyshawn.example.ExampleBean@5d47c63f
  銷燬方法
複製代碼

關於Spring的介紹到此結束, 對於新手來講這些內容足夠咱們瞭解Spring是個什麼技術, 也足夠咱們去應付面試官了. 若是想要更深層次的瞭解Spring, 能夠去看官方文檔, 閱讀源碼.

來源:t.im/kzfp

開源項目推薦

做者的開源項目推薦:

關注公衆號回覆開源項目便可獲取

相關文章
相關標籤/搜索