Spring基礎知識

一 概述

1 使用Spring的好處

  • Spring使開發人員可以使用POJO開發企業級應用程序(相對於EJB)
  • Spring採用模塊化方式組織。(須要哪些模塊則引入便可)
  • 使用Spring編寫的應用程序很簡單(由於依賴於環境的代碼被移動到此框架中)
  • Spring MVC是一個設計良好的Web MVC框架(用於替換Struts )
  • Spring提供了一個方便的API,用於將特定於技術的異常(例如,JDBC,Hibernate或JDO拋出)轉換爲一致的,未經檢查的異常。
  • 輕量級IoC容器,有利於在具備有限內存和CPU資源的計算機上開發和部署應用程序。
  • Spring提供了一致的事務管理接口.

2 依賴注入(DI)

  • 依賴注入是控制反轉的一個實現,控制反轉是一種設計模式,減小代碼之間的耦合。
  • 依賴注入有三種方式
    • 構造器注入
    • 接口注入
    • setter注入
  • A依賴B,B注入A
classA{
    AInterface a;
 
    A(){}
    
    //一個方法
    AMethod() {
        a = new AInterfaceImp();
    }
}

// 當AInterface有一個新的實現但願使用,那麼須要改變classA的代碼

// 構造器注入
classA{
    AInterface a;
 
    A(AInterface a){
        this.a = a;
    }
}

// setter注入
classA{
    AInterface a;
 
    A(){}
    
    public void setA(AInterface a){
        this.a = a;
    }
}

3 面向切面編程(AOP)

  • 跨越應用程序多個點的功能稱爲跨領域問題,這些跨領域問題在概念上與應用程序的業務邏輯分離。 各方面有各類常見的良好示例,包括日誌記錄,聲明式事務,安全性,緩存等。
  • OOP中模塊化的關鍵單元是類,而在AOP中,模塊化單元是方面。
  • DI幫助您將應用程序對象彼此分離,而AOP可幫助您將交叉問題與它們所影響的對象分離。
  • Spring Framework的AOP模塊提供了面向方面的編程實現,容許您定義方法攔截器和切入點,以便乾淨地解耦實現應該分離的功能的代碼。

二 架構

Spring Framework Architecture

1 核心容器

  • Core模塊提供了框架的基本部分,包括IoC和依賴注入功能。java

  • Bean模塊提供BeanFactory,它是工廠模式的複雜實現
  • Context模塊創建在Core和Beans模塊提供的堅實基礎之上,它是訪問定義和配置的任何對象的媒介。 ApplicationContext接口是Context模塊的焦點。
  • SpEL模塊提供了一種強大的表達式語言,用於在運行時查詢和操做對象圖。web

2 數據訪問/集成

  • JDBC:提供了JDBC抽象層,無需冗長的JDBC相關編碼
  • ORM:爲流行的對象關係映射API提供集成層,包括JPA,JDO,Hibernate和MyBatis。
  • JMS:用於生成和使用消息的功能。
  • Transaction:支持對實現特殊接口和全部POJO的類進行編程和聲明式事務管理

3 Web

  • Web模塊提供基本的面向Web的集成功能,例如多部分文件上載功能以及使用servlet偵聽器和麪向Web的應用程序上下文初始化IoC容器。
  • Web-MVC模塊包含用於Web應用程序的Spring的模型 - 視圖 - 控制器(MVC)實現。
  • Web-Socket模塊支持Web應用程序中客戶端和服務器之間基於WebSocket的雙向通訊。

4 其他

  • AOP模塊提供面向方面的編程實現,容許您定義方法攔截器和切入點,以乾淨地解耦實現應該分離的功能的代碼。
  • Aspects模塊提供與AspectJ的集成,後者又是一個功能強大且成熟的AOP框架。
  • Instrumentation模塊提供了在某些應用程序服務器中使用的類檢測支持和類加載器實現。
  • Messaging模塊提供對STOMP的支持,做爲在應用程序中使用的WebSocket子協議。它還支持用於從WebSocket客戶端路由和處理STOMP消息的註釋編程模型。
  • Test模塊支持使用JUnit或TestNG框架測試Spring組件。

三 IOC容器

Spring容器是Spring Framework的核心。 容器將建立對象,將它們鏈接在一塊兒,配置它們,並管理從建立到銷燬的整個生命週期。 Spring容器使用DI來管理組成應用程序的組件。spring

容器經過讀取提供的配置元數據來獲取有關要實例化,配置和組裝的對象的指令。 配置元數據能夠由XML,Java註釋或Java代碼表示。Spring IoC容器利用Java POJO類和配置元數據來生成徹底配置和可執行的系統或應用程序。數據庫

Spring IoC Container

Spring提供兩種不一樣的容器編程

No 容器和描述
1 這是提供DI基本支持的最簡單容器,由org.springframework.beans.factory.BeanFactory接口定義。 BeanFactory和相關的接口,如BeanFactoryAware,InitializingBean,DisposableBean,仍然存在於Spring中,目的是向後兼容與Spring集成的大量第三方框架
2 此容器添加了更多特定於企業的功能,例如從屬性文件解析文本消息的功能以及將應用程序事件發佈到感興趣的事件偵聽器的功能。 此容器由org.springframework.context.ApplicationContext接口定義。

注:ApplicationContext包含BeanFactory的全部功能。設計模式

四 Bean定義

1 概述

構成應用程序主幹並由Spring IoC容器管理的對象稱爲bean。 bean是一個由Spring IoC容器實例化,組裝和管理的對象。 這些bean是使用您提供給容器的配置元數據建立的。數組

No 配置和描述
1 class 此屬性是必需的,並指定建立的bean類型
2 name 此屬性惟一地指定bean標識符
3 scope 指定bean的做用範圍,默認爲singleton、prototype.....
4 constructor-arg 用於注入依賴項
5 properties 用於注入依賴項
6 autowiring mode 用於注入依賴項
7 lazy-initialization mode 在第一次請求時建立bean實例,不是在啓動時建立
8 initialization method 在容器設置了bean以後全部必要屬性以後調用的回調。
9 destruction method 當包含bean的容器被銷燬時使用的回調。

2 Spring元配置格式

  • XML方式
  • 註解方式
  • Java類方式
<?xml version = "1.0" encoding = "UTF-8"?>

<beans xmlns = "http://www.springframework.org/schema/beans"
   xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation = "http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

   <!-- 簡單類定義 -->
   <bean id = "..." class = "...">
      <!-- 這個bean的協做者和配置就在這裏 -->
   </bean>

   <!-- 懶加載模式 -->
   <bean id = "..." class = "..." lazy-init = "true">
      <!-- 這個bean的協做者和配置就在這裏 -->
   </bean>

   <!-- 配置初始化方法 -->
   <bean id = "..." class = "..." init-method = "...">
      <!-- 這個bean的協做者和配置就在這裏 -->
   </bean>

   <!-- 配置銷燬方法 -->
   <bean id = "..." class = "..." destroy-method = "...">
      <!-- 這個bean的協做者和配置就在這裏 -->
   </bean>
</beans>

五 Bean的做用域

No 做用域與描述
1 singlton 單例(默認)
2 prototype 多例
3 request HTTP請求。(web)
4 session HTTP會話。(web)
5 global-session 全局HTTP會話(web)

(1)單例

@Component
public class SingletonBean {
    private String message;

    public void setMessage(String msg) {
        this.message = msg;
    }

    public void print() {
        System.out.println(message);
    }
}

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext context = null;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.context = applicationContext;
    }
    
    public static <T> T getBean(String beanName) {
        return (T) context.getBean(beanName);
    }
}

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
         SingletonBean singletonBean = SpringContextUtil.getBean("singletonBean");
        singletonBean.setMessage("singleton");
        singletonBean.print(); // singleton
        SingletonBean singletonBean2 = SpringContextUtil.getBean("singletonBean");
        singletonBean2.print();// singleton
    }
}

(2)多例

@Component
@Scope("prototype")
public class PrototypeBean {
    private String message;

    public void setMessage(String msg) {
        this.message = msg;
    }

    public void print() {
        System.out.println(message);
    }
}

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
        PrototypeBean prototypeBean = SpringContextUtil.getBean("prototypeBean");
        prototypeBean.setMessage("prototype"); 
        prototypeBean.print();// prototype
        PrototypeBean prototypeBean2 = SpringContextUtil.getBean("prototypeBean");
        prototypeBean2.print();// null
    }
}

六 Bean的生命週期

Spring bean的生命週期很容易理解。 實例化bean時,可能須要執行一些初始化以使其進入可用狀態。 相似地,當再也不須要bean並將其從容器中移除時,可能須要進行一些清理。緩存

@Component
public class LifeCycleBean implements InitializingBean,DisposableBean {
    @Value("${lifecycle.message}")
    private String message;  // 構造方法後message纔有值

    public void print() {
        System.out.println(message);
    }
    
    // 1
    public LifeCycleBean() {
        System.out.println("Constructor");
    }

    // 2
    @PostConstruct
    public void init(){
        System.out.println("@PostConstruct");
    }
    
    // 3
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("implements InitializingBean");
    }

    // 4
    @PreDestroy
    public void preDestory(){
        System.out.println("@PreDestroy");
    }
    
    // 5
    @Override
    public void destroy() throws Exception {
        System.out.println("implements DisposableBean");
    }
}

七 Bean後處理器

(1)概述

BeanPostProcessor接口定義了您能夠實現的回調方法,以提供您本身的實例化邏輯,依賴關係解析邏輯等。您還能夠在Spring容器經過插入一個或多個實例化,配置和初始化bean以後實現一些自定義邏輯 BeanPostProcessor實現。安全

您能夠配置多個BeanPostProcessor接口,您能夠經過設置訂單屬性來控制這些BeanPostProcessor接口的執行順序,前提是BeanPostProcessor實現了Ordered接口。服務器

BeanPostProcessors在bean(或對象)實例上運行,這意味着Spring IoC容器實例化一個bean實例,而後BeanPostProcessor接口完成它們的工做。

ApplicationContext自動檢測使用BeanPostProcessor接口的實現定義的任何bean,並將這些bean註冊爲後處理器,而後在建立bean時由容器適當調用。

// 注意該類做用於全部的Spring Bean
@Component
public class BeanPostProcessors implements BeanPostProcessor,Ordered  {
    // 2
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            System.out.println("postProcessBeforeInitialization " + beanName);
        return bean;
    }

    // 5
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            System.out.println("postProcessAfterInitialization " + beanName);
        return bean;
    }
    
    @Override
    public int getOrder() { return 1; }
}

@Component
public class LifeCycleBean implements InitializingBean,DisposableBean {
    @Value("${lifecycle.message}")
    private String message;  // 構造方法後message纔有值

    public void print() {
        System.out.println(message);
    }
    
    // 1
    public LifeCycleBean() {
        System.out.println("Constructor");
    }

    // 3
    @PostConstruct
    public void init(){
        System.out.println("@PostConstruct");
    }
    
    //4
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("implements InitializingBean");
    }  
    
    // 6
    @PreDestroy
    public void preDestory(){
        System.out.println("@PreDestroy");
    }
    
    // 7
    @Override
    public void destroy() throws Exception {
        System.out.println("implements DisposableBean");
    }
}

(2)加上BeanPostProcessor後的總體順序:

  • Construtor
  • BeanPostProcessor.postProcessBeforeInitialization()
  • @PostConstrutor
  • InitializingBean.afterPropertiesSet()
  • BeanPostProcessor.postProcessAfterInitialization()
  • DisposableBean.destory()

(3)多個BeanPostProcessor順序

實現Ordered並設定順序值

@Component
public class BeanPostProcessors2 implements BeanPostProcessor,Ordered {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            System.out.println("postProcessBeforeInitialization2 " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (beanName.equals("lifeCycleBean"))
            System.out.println("postProcessAfterInitialization2 " + beanName);
        return bean;
    }

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

八 Bean定義繼承

bean定義能夠包含許多配置信息,包括構造函數參數,屬性值和特定於容器的信息,例如初始化方法,靜態工廠方法名稱等。

子類bean定義從父定義繼承配置數據。子定義能夠根據須要覆蓋某些值或添加其餘值。

Spring Bean定義繼承與Java類繼承無關,但繼承概念是相同的。 您能夠將父bean定義定義爲模板,其餘子bean能夠從父bean繼承所需的配置。

使用基於XML的配置元數據時,可使用parent屬性指定子bean定義,並將父bean指定爲此屬性的值。

<!-- app-conf-1.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="animal" class="com.concretepage.Animal" init-method="initA">
        <property name="name" value="Hathi"/>
        <property name="age" value="20"/>
    </bean>
    
    <bean id="elephant" class="com.concretepage.Elephant" parent="animal" init-method="initE">
        <property name="age" value="30"/>
        <property name="location" value="Varanasi"/>
    </bean>
</beans> 
<!-- 注意目前不知道如何使用註解替代:parent 和 abstract -->
public class Animal {
    private String name;
    private Integer age;
    public void initA() {
        System.out.println("Inside initA()");
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class Elephant extends Animal {
    private String location;
    public void initE() {
        System.out.println("Inside initE()");
    }   
    public String getLocation() {
        return location;
    }
    public void setLocation(String location) {
        this.location = location;
    }
} 

public class SpringDemo {
    public static void main(String[] args) {
        AbstractApplicationContext  context = new ClassPathXmlApplicationContext("app-conf-1.xml");
        Elephant elephant=(Elephant)context.getBean("elephant");
        System.out.println(elephant.getLocation()); // Varanasi
        System.out.println(elephant.getName()); // Hathi
        System.out.println(elephant.getAge()); // 30
        context.registerShutdownHook();
    }
}

九 依賴注入

(1)構造器注入和Setter注入

public class TextEditor {
   private SpellChecker spellChecker;
   
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
    
   public void setSpellChecker(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
}

@Configuration
@Import(ConfigA.class) // 注入
public class Config{
    // 方式1:構造器注入
    @Bean
    public TextEditor textEditor(){
        return new TextEditor(spellChecker());
    }
    
    // 方式2:setter注入
    @Scope("prototype")
    @Bean(initMethod = "init", destroyMethod = "cleanup")
    public TextEditor textEditor(){
        TextEditor textEditor = new TextEditor();
        textEditor.setSpellChecker(spellChecker());
        return textEditor;
    }
    
    @Bean 
    public SpellChecker spellChecker(){
        return new SpellChecker();
    }
}

(2)自動注入

// 默認第一種方式最簡單,無需寫構造器和setter方法
@Component
public class TextEditor {
   @Autowired // 方式一
   private SpellChecker spellChecker;
   
   @Autowired // 方式二
   @Qualifier("spellChecker") // 根據名稱指定
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
    
   // 方式二 JSR-250
   @Resource(name = "spellChecker")
   public TextEditor(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
    
   @Autowired // 方式三
   public void setSpellChecker(SpellChecker spellChecker) {
      this.spellChecker = spellChecker;
   }
}

十 注入集合

// 數組
@Value("${name.list}")
private String[] names;

// 列表
@Value("#{T(java.util.Arrays).asList('${name.list:a,b,c}')}") 
private List<String> list;

// 列表
@Value("${name.list}")
private List<String> names;

// Set
@Value("${name.list}")
private Set<String> names; // LinkedHashSet

// map
@Value("#{${valuesMap}}")
private Map<String, Integer> valuesMap;

// map.get(key):key不存在時報錯
@Value("#{${valuesMap}.key1}")
private Integer valuesMapKey1;

// 安全方式不會報錯,設置爲null(默認值)
@Value("#{${valuesMap}['unknownKey']}")
private Integer unknownMapKey;

// 設置默認值
@Value("#{${unknownMap : {key1: '1', key2: '2'}}}")
private Map<String, Integer> unknownMap;
 
@Value("#{${valuesMap}['unknownKey'] ?: 5}")
private Integer unknownMapKeyWithDefaultValue;

// 過濾
@Value("#{${valuesMap}.?[value>'1']}")
private Map<String, Integer> valuesMapFiltered;

@Value("#{systemProperties}")
private Map<String, String> systemPropertiesMap;

// application.properties
name.list=d,e,f
valuesMap={key1: '1', key2: '2', key3: '3'}

十一 Spring的事件處理

您已經在全部章節中看到Spring的核心是ApplicationContext,它管理bean的完整生命週期。 ApplicationContext在加載bean時發佈某些類型的事件。 例如,在啓動上下文時發佈ContextStartedEvent,並在上下文中止時發佈ContextStoppedEvent。

ApplicationContext中的事件處理是經過ApplicationEvent類和ApplicationListener接口提供的。 所以,若是bean實現了ApplicationListener,那麼每次將ApplicationEvent發佈到ApplicationContext時,都會通知該bean。

No Spring內置事件
1 ContextRefreshedEvent 在初始化或刷新ApplicationContext時發佈此事件。這也可使用ConfigurableApplicationContext接口上的refresh()方法引起。
2 ContextStartedEvent 使用ConfigurableApplicationContext接口上的start()方法啓動ApplicationContext時,將發佈此事件。您能夠輪詢數據庫,也能夠在收到此事件後從新啓動任何已中止的應用程序。
3 ContextStoppedEvent 使用ConfigurableApplicationContext接口上的stop()方法中止ApplicationContext時,將發佈此事件。收到此活動後,您能夠進行必要的清理工做。
4 ContextClosedEvent 使用ConfigurableApplicationContext接口上的close()方法關閉ApplicationContext時,將發佈此事件。封閉的環境達到其生命的終點;它沒法刷新或從新啓動。
5 RequestHandledEvent 這是一個特定於Web的事件,告訴全部bean已經爲HTTP請求提供服務。
public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        context.start(); // 發出ContextStartedEvent事件
        context.stop(); // 發出ContextStoppedEvent事件
}

@Component
public class SpringEventListener {
    // 1
    @EventListener
    public void contextRefreshedEvent(ContextRefreshedEvent event) {
        System.out.println("ContextRefreshedEvent "+event);
    }
    // 2
    @EventListener
    public void contextStartedEvent (ContextStartedEvent event) {
        System.out.println("ContextStartedEvent  "+event);
    }
    // 3
    @EventListener
    public void contextStoppedEvent (ContextStoppedEvent event) {
        System.out.println("ContextStoppedEvent  "+event);
    }
    // 4
    @EventListener
    public void contextClosedEvent (ContextClosedEvent event) {
        System.out.println("ContextClosedEvent  "+event);
    }
}

十二 自定義Spring事件

public class CustomEvent extends ApplicationEvent {
    public CustomEvent(Object source) {
        super(source);
    }

    public String toString(){
        return "My Custom Event";
    }
}

@Component
public class CustomPublisher implements ApplicationEventPublisherAware {
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void publish() {
        CustomEvent ce = new CustomEvent(this);
        publisher.publishEvent(ce);
    }
}

// 方式1
@Component
public class CustomEventHandler implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println(event);
    }
}

// 方式2
@Component
public class CustomEventListener {

    @EventListener
    public void listen(CustomEvent customEvent) {
        System.out.println(customEvent);
    }
}

@SpringBootApplication
public class MainApplication {
    
     public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        CustomPublisher customPublisher = (CustomPublisher) context.getBean("customPublisher");
        customPublisher.publish();
     }
}

十三 AOP:面向切面編程

面向方面編程須要將程序邏輯分解爲稱爲橫切關注點的不一樣部分。 跨越應用程序多個點的功能稱爲跨領域問題,這些跨領域問題在概念上與應用程序的業務邏輯分離。 有許多常見的好例子,如日誌記錄,審計,聲明式事務,安全性,緩存等。

OOP中模塊化的關鍵單元是類,而在AOP中,模塊化單元是方面。 依賴注入能夠幫助您將應用程序對象相互分離,AOP能夠幫助您將交叉問題與它們所影響的對象分離。 AOP就像Perl,.NET,Java等編程語言中的觸發器。

Spring AOP模塊提供攔截器來攔截應用程序。例如,執行方法時,能夠在方法執行以前或以後添加額外的功能

(1)術語

術語 描述
Aspect 切面 切面是通知和切點的結合。例如,一個日誌模塊爲了記錄日誌將被 AOP 方面調用。應用程序能夠擁有任意數量的方面,這取決於需求。
Join point 鏈接點 程序執行過程當中的某個特定的點,或者說特定的時候。
Advice 通知 Spring AOP 中,有前置通知、後置通知、異常通知、最終通知、環繞通知 5 種。
Pointcut 切點是用來匹配定位鏈接點的。一組一個或多個鏈接點,通知應該被執行。
Weaving 織入 切面應用到鏈接點中
Introduction 引用 引用容許你添加新方法或屬性到現有的類中。
Target 目標類 被一個或者多個方面所通知的對象,這個對象永遠是一個被代理對象。

(2)通知的類型

通知 描述
前置通知 在一個方法執行以前,執行通知。
後置通知 在一個方法執行以後,不考慮其結果,執行通知。
返回後通知 在一個方法執行以後,只有在方法成功完成時,才能執行通知。
拋出異常後通知 在一個方法執行以後,只有在方法退出拋出異常時,才能執行通知。
環繞通知 在建議方法調用以前和以後,執行通知。

(3)demo

// Aspect
@Component
@Aspect
public class TimeLoggingAspect {
    // Advice
    @Around("execution(* com.littleevil.autowiredtest.aop.UserService.*(..))") // Pointcut
    public Object userAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("@Around: Before calculation-" + new Date());
        Object result = joinPoint.proceed();
        System.out.println("@Around: After calculation-" + new Date());
        return result;
    }
}

// Target
@Service
public class UserService {
    public Integer multiply(int a, int b){
        int res = a*b;
        System.out.println(a+ "*" + b +"= " + res);
        return res;
    }
}

@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MainApplication.class, args);
        UserService userService = (UserService) context.getBean("userService");
        Integer res = userService.multiply(1, 3);
        System.out.println(res);
    }

}
相關文章
相關標籤/搜索