親自動手搭建微服務框架和測試環境-11-Spring Framework

一、架構和組件關係圖

Spring 5的架構圖如下:

image.png


各組件之間的依賴圖如下:

image.png


Spring520 個組件(1300多個文件),這些組件被分別整合在覈心容器(Core Container)、AOPAspect Oriented Programming)和設備支持(Instrmentation)、數據訪問及集成(Data Access/Integeration)、Web、報文發送(Messaging)、Test中。

1、核心容器

spring-beansspring-corespring-context spring-expressionSpring Expression Language, SpEL)組成,4個組件。

 

spring-beans  spring-core 模塊是 Spring 框架的核心模塊,包含了控制反轉(Inversion of

Control, IOC)和依賴注入(Dependency Injection, DI)。

其中,BeanFactory 接口是 Spring 框架中的核心接口,它是工廠模式的具體實現。BeanFactory 使用控制反轉對應用程序的配置和依賴性規範與實際的應用程序代碼進行了分離。但 BeanFactory 容器實例化後並不會自動實例化 Bean,只有當 Bean被使用時 BeanFactory 容器纔會對該 Bean 進行實例化與依賴關係的裝配。

 

spring-context 模塊構架於核心模塊之上,他擴展了 BeanFactory,爲她添加了 Bean 生命週期控制、框架事件體系以及資源加載透明化等功能。此外該模塊還提供了許多企業級支持,如郵件訪問、遠程訪問、任務調度等,ApplicationContext 是該模塊的核心接口,她是 BeanFactory 的超類,與BeanFactory 不同,ApplicationContext 容器實例化後會自動對所有的單實例 Bean 進行實例化與依賴關係的裝配,使之處於待用狀態。

 

spring-expression 模塊是統一表達式語言(EL)的擴展模塊,可以查詢、管理運行中的對象, 

同時也方便的可以調用對象方法、操作數組、集合等。它的語法類似於傳統 EL,但提供了額外的功能,最出色的要數函數調用和簡單字符串的模板函數。這種語言的特性是基於 Spring 產品的需求而設計,他可以非常方便地同 Spring IOC 進行交互。

 

2AOP 和設備支持

spring-aopspring-aspects spring-instrument組成,3個組件。

 

spring-aop  Spring 的另一個核心模塊,是 AOP 主要的實現模塊。作爲繼 OOP 後,對程序員影響最大的編程思想之一,AOP 極大地開拓了人們對於編程的思路。在 Spring 中,他是以 JVM 的動態代理技術爲基礎,然後設計出了一系列的 AOP 橫切實現,比如前置通知、返回通知、異常通知等,同時, Pointcut 接口來匹配切入點,可以使用現有的切入點來設計橫切面,也可以擴展相關方法根據需求進行切入。

 

spring-aspects 模塊集成自 AspectJ 框架,主要是爲 Spring AOP 提供多種 AOP 實現方法。 

 

spring-instrument 模塊是基於 JAVA SE 中的“ava.lang.instrument”進行設計的,應該算是

AOP 的一個支援模塊,主要作用是在 JVM 啓用時,生成一個代理類,程序員通過代理類在運行時修改類的字節,從而改變一個類的功能,實現 AOP 的功能。

 

3、數據訪問及集成

spring-jdbcspring-txspring-ormspring-jms spring-oxm組成,5個組件。

 

spring-jdbc 模塊是 Spring 提供的 JDBC 抽象框架的主要實現模塊,用於簡化 Spring JDBC。主要是提供 JDBC 模板方式、關係數據庫對象化方式、SimpleJdbc 方式、事務管理來簡化 JDBC編程,主要實現類是 JdbcTemplateSimpleJdbcTemplate 以及 NamedParameterJdbcTemplate

 

spring-tx模塊是 Spring JDBC 事務控制實現模塊。使用 Spring 框架,它對事務做了很好的封裝, 通過它的 AOP 配置,可以靈活的配置在任何一層;但是在很多的需求和應用,直接使用 JDBC 事務控制還是有其優勢的。其實,事務是以業務邏輯爲基礎的;一個完整的業務應該對應業務層裏的一個方法; 如果業務操作失敗,則整個事務回滾;所以,事務控制是絕對應該放在業務層的;但是,持久層的設計則應該遵循一個很重要的原則:保證操作的原子性,即持久層裏的每個方法都應該是不可以分割的。所以,在使用 Spring JDBC 事務控制時,應該注意其特殊性。

 

spring-orm 模塊是 ORM 框架支持模塊,主要集成 Hibernate, Java Persistence API (JPA) Java Data Objects (JDO) 用於資源管理、數據訪問對象(DAO)的實現和事務策略。

 

spring-jms 模塊(Java Messaging Service)能夠發送和接受信息,自 Spring Framework 4.1

以後,他還提供了對 spring-messaging 模塊的支撐。

 

spring-oxm 模塊主要提供一個抽象層以支撐 OXMOXM  Object-to-XML-Mapping 的縮寫,它是一個 O/M-mapper,將 java 對象映射成 XML 數據,或者將 XML 數據映射成 java 對象),例如:JAXB,Castor, XMLBeans, JiBX  XStream 等。

 

4Web

spring-webspring-webmvcspring-websocket spring-webflux 組成,4個組件。

 

spring-web 模塊爲 Spring 提供了最基礎 Web 支持,主要建立於核心容器之上,通過 Servlet   Listeners 來初始化 IOC 容器,也包含一些與 Web 相關的支持。

 

spring-webmvc模塊是一個的Web-Servlet模塊,實現了Spring MVC model-view-Controller)的 Web 應用。

 

spring-websocket 模塊主要是與 Web 前端的全雙工通訊的協議。

 

spring-webflux 是一個新的非堵塞函數式 Reactive Web 框架,可以用來建立異步的,非阻塞,事件驅動的服務,並且擴展性非常好。

 

5、報文發送

包括spring-messaging 1個組件。

 

spring-messaging是從 Spring4 開始新加入的一個模塊,主要職責是爲 Spring 框架集成一些基礎的報文傳送應用。

 

6Test

包含spring-test1個組件。

spring-test 模塊主要爲測試提供支持的,畢竟在不需要發佈(程序)到你的應用服務器或者連接到其他企業設施的情況下能夠執行一些集成測試或者其他測試對於任何企業都是非常重要的。


二、 Spring Bean基礎

Bean配置信息定義了Bean的實現及依賴關係,Spring容器根據各種形式的Bean配置信息在容器內部建立Bean定義註冊表,然後根據註冊表加載、實例化Bean,並建立BeanBean的依賴關係,最後將這些準備就緒的Bean放到Bean緩存池中,以供外層的應用程序進行調用。下圖是一張老圖:

image.png


1bean配置

bean配置有三種方法:

基於xml配置Bean

使用註解定義Bean

基於java類提供Bean定義信息

 

1.1 基於xml配置Bean

 對於基於XML的配置,Spring 2.0以後使用Schema的格式,使得不同類型的配置擁有了自己的命名空間,是配置文件更具擴展性。

image.png


默認命名空間:它沒有空間名,用於Spring Bean的定義;

xsi命名空間:這個命名空間用於爲每個文檔中命名空間指定相應的Schema樣式文件,是標準組織定義的標準命名空間;

aop命名空間:這個命名空間是Spring配置AOP的命名空間,是用戶自定義的命名空間。

 

命名空間的定義分爲兩個步驟:第一步指定命名空間的名稱;第二步指定命名空間的Schema文檔樣式文件的位置,用空格或回車換行進行分分隔。

 

1.1.1 Bean基本配置

Spring容器的配置文件中定義一個簡要Bean的配置片段如下所示:

image.png


一般情況下,Spring IOC容器中的一個Bean即對應配置文件中的一個<bean>,這種鏡像對應關係應該容易理解。其中id爲這個Bean的名稱,通過容器的getBean("foo")即可獲取對應的Bean,在容器中起到定位查找的作用,是外部程序和Spring IOC容器進行交互的橋樑。class屬性指定了Bean對應的實現類。

下面是基於XML的配置文件定義了兩個簡單的Bean

<?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="car" name="#car1" class="com.baobaotao.simple.Car"></bean>  

     <bean id="boss" class="com.baobaotao.simple.Boss"></bean>

</beans>

1.1.2 依賴注入

屬性注入

構造函數注入

工廠方式注入

1.2 使用註解定義Bean

我們知道,Spring容器成功啓動的三大要件分別是:Bean定義信息、Bean實現類以及Spring本身。如果採用基於XML的配置,Bean定義信息和Bean實現類本身是分離的,而採用基於註解的配置方式時,Bean定義信息即通過在Bean實現類上標註註解實現。

下面是使用註解定義一個DAOBean

package com.baobaotao.anno;

 

import org.springframework.stereotype.Component;

import org.springframework.stereotype.Repository;

//①通過Repository定義一個DAOBean

 

@Component("userDao")

public class UserDao {

}

處,我們使用@Component註解在UserDao類聲明處對類進行標註,它可以被Spring容器識別,Spring容器自動將POJO轉換爲容器管理的Bean

它和以下的XML配置是等效的:

<bean id="userDao" class="com.baobaotao.anno.UserDao"/>

除了@Component以外,Spring提供了3個功能基本和@Component等效的註解,它們分別用於對DAOServiceWeb層的Controller進行註解,所以也稱這些註解爲Bean的衍型註解:(類似於xml文件中定義Bean<bean id=" " class=" "/>

@Repository:用於對DAO實現類進行標註;

@Service:用於對Service實現類進行標註;

@Controller:用於對Controller實現類進行標註;

之所以要在@Component之外提供這三個特殊的註解,是爲了讓註解類本身的用途清晰化,此外Spring將賦予它們一些特殊的功能。

1.2.1 使用註解配置信息啓動spring容器

Spring提供了一個context的命名空間,它提供了通過掃描類包以應用註解定義Bean的方式:

<?xml version="1.0" encoding="UTF-8" ?>

<!--①聲明context的命名空間-->

<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

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

         http://www.springframework.org/schema/context

         http://www.springframework.org/schema/context/spring-context-3.0.xsd"

         >

    <!--②掃描類包以應用註解定義的Bean-->

   <context:component-scan base-package="com.baobaotao.anno"/>

   <bean class="com.baobaotao.anno.LogonService"></bean>

   <!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ -->

   <!-- context:component-scan base-package="com.baobaotao">

       <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/>

       <context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/>

       <context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/>

   </context:component-scan -->

</beans>

處聲明context命名空間,在處即可通過context命名空間的component-scanbase-package屬性指定一個需要掃描的基類包,Spring容器將會掃描這個基類包裏的所有類,並從類的註解信息中獲取Bean的定義信息。

如果僅希望掃描特定的類而非基包下的所有類,你們可以使用resource-pattern屬性過濾特定的類,如下所示:

< context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ >

這裏我們將基類包設置爲com.baobaotao,默認情況下resource-pattern屬性的值爲"**/*.class",即基類包裏的所有類。這裏我們設置爲"anno/*.class",Spring僅會掃描基包裏anno子包中的類。

1.3 基於java類提供Bean定義

在普通的POJO類中只要標註@Configuration註解,就可以爲spring容器提供Bean定義的信息了,每個標註了@Bean的類方法都相當於提供了一個Bean的定義信息。

package com.baobaotao.conf;

 

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

//①將一個POJO標註爲定義Bean的配置類

@Configuration

public class AppConf {

        //②以下兩個方法定義了兩個Bean,以提供了Bean的實例化邏輯

    @Bean

    public UserDao userDao(){

       return new UserDao();    

    }

    

    @Bean

    public LogDao logDao(){

        return new LogDao();

    }

    //③定義了logonServiceBean

    @Bean

    public LogonService logonService(){

        LogonService logonService = new LogonService();

                //④將②和③處定義的Bean注入到LogonService Bean

        logonService.setLogDao(logDao());

        logonService.setUserDao(userDao());

        return logonService;

    }

}

處在APPConf類的定義處標註了@Configuration註解,說明這個類可用於爲Spring提供Bean的定義信息。類的方法處可以標註@Bean註解,Bean的類型由方法返回值類型決定,名稱默認和方法名相同,也可以通過入參顯示指定Bean名稱,如@Bean(name="userDao").直接在@Bean所標註的方法中提供Bean的實例化邏輯。

userDao()logDao()方法定義了一個UserDao和一個LogDaoBean,它們的Bean名稱分別是userDaologDao。在處,又定義了一個logonService Bean,並且在處注入處所定義的兩個Bean

因此,以上的配置和以下XML配置時等效的:

<bean id="userDao" class="com.baobaotao.anno.UserDao"/>

<bean id="logDao" class="com.baobaotao.anno.LogDao"/>

<bean id="logService" class="com.baobaotao.conf.LogonService"

    p:logDao-ref="logDao" p:userDao-ref="userDao"/>

基於java類的配置方式和基於XML或基於註解的配置方式相比,前者通過代碼的方式更加靈活地實現了Bean的實例化及Bean之間的裝配,但後面兩者都是通過配置聲明的方式,在靈活性上要稍遜一些,但是配置上要更簡單一些。

2 Bean注入

Bean注入的方式有兩種,一種是在XML中配置,此時分別有屬性注入、構造函數注入和工廠方法注入;另一種則是使用註解的方式注入 @Autowired,@Resource,@Required

2.1 xml文件中配置依賴注入

2.1.1 屬性注入

屬性注入即通過setXxx()方法注入Bean的屬性值或依賴對象,由於屬性注入方式具有可選擇性和靈活性高的優點,因此屬性注入是實際應用中最常採用的注入方式。

屬性注入要求Bean提供一個默認的構造函數,併爲需要注入的屬性提供對應的Setter方法。Spring先調用Bean的默認構造函數實例化Bean對象,然後通過反射的方式調用Setter方法注入屬性值。

package com.baobaotao.anno;

 

import org.springframework.beans.factory.BeanNameAware;

 

public class LogonService implements BeanNameAware{

 

    private LogDao logDao;

 

    private UserDao userDao;

 

    public void setUserDao(UserDao userDao) {

        this.userDao = userDao;

    }

 

    public void setLogDao(LogDao logDao) {

        this.logDao = logDao;

    }

    

    public LogDao getLogDao() {

        return logDao;

    }

    public UserDao getUserDao() {

        return userDao;

    }

    

    public void setBeanName(String beanName) {

        System.out.println("beanName:"+beanName);        

    }

    

    public void initMethod1(){

        System.out.println("initMethod1");

    }

    public void initMethod2(){

        System.out.println("initMethod2");

    }

}

 

bean.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

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

         http://www.springframework.org/schema/context

         http://www.springframework.org/schema/context/spring-context-3.0.xsd"

       default-autowire="byName"

         >

    <bean id="logDao" class="com.baobaotao.anno.LogDao"/>

    <bean id="userDao" class="com.baobaotao.anno.UserDao"/>

   <bean class="com.baobaotao.anno.LogonService">

       <property name="logDao" ref="logDao"></property>

       <property name="userDao" ref="userDao"></property>

   </bean>

</beans>


2.1.2 構造方法注入

使用構造函數注入的前提是Bean必須提供帶參數的構造函數。例如

package com.baobaotao.anno;

 

import org.springframework.beans.factory.BeanNameAware;

 

public class LogonService implements BeanNameAware{

 

    public LogonService(){}

 

    public LogonService(LogDao logDao, UserDao userDao) {

        this.logDao = logDao;

        this.userDao = userDao;

    }

 

    private LogDao logDao;

 

    private UserDao userDao;

 

    public void setUserDao(UserDao userDao) {

        this.userDao = userDao;

    }

 

    public void setLogDao(LogDao logDao) {

        this.logDao = logDao;

    }

    

    public LogDao getLogDao() {

        return logDao;

    }

    public UserDao getUserDao() {

        return userDao;

    }

    

    public void setBeanName(String beanName) {

        System.out.println("beanName:"+beanName);        

    }

    

    public void initMethod1(){

        System.out.println("initMethod1");

    }

    public void initMethod2(){

        System.out.println("initMethod2");

    }

}

 

bean.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

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

         http://www.springframework.org/schema/context

         http://www.springframework.org/schema/context/spring-context-3.0.xsd"

       default-autowire="byName">

 

    <bean id="logDao" class="com.baobaotao.anno.LogDao"/>

    <bean id="userDao" class="com.baobaotao.anno.UserDao"/>

   <bean class="com.baobaotao.anno.LogonService">

      <constructor-arg  ref="logDao"></constructor-arg>

       <constructor-arg ref="userDao"></constructor-arg>

   </bean>

</beans>

 

2.1.3 工廠方法注入

非靜態工廠方法:

有些工廠方法是非靜態的,即必須實例化工廠類後才能調用工廠方法

package com.baobaotao.ditype;

 

public class CarFactory {

   public Car createHongQiCar(){

       Car car = new Car();

       car.setBrand("紅旗CA72");

       return car;

   }

   

   public static Car createCar(){

       Car car = new Car();

       return car;

   }

}

 

工廠類負責創建一個或多個目標類實例,工廠類方法一般以接口或抽象類變量的形式返回目標類實例,工廠類對外屏蔽了目標類的實例化步驟,調用者甚至不用知道具體的目標類是什麼。

<?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:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 

    <!-- 工廠方法-->

    <bean id="carFactory" class="com.baobaotao.ditype.CarFactory" />

    <bean id="car5" factory-bean="carFactory" factory-method="createHongQiCar">

    </bean>

</beans>

靜態工廠方法:

很多工廠類都是靜態的,這意味着用戶在無須創建工廠類實例的情況下就可以調用工廠類方法,因此,靜態工廠方法比非靜態工廠方法的調用更加方便。

<?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:p="http://www.springframework.org/schema/p"

    xsi:schemaLocation="http://www.springframework.org/schema/beans

         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

 

    <bean id="car6" class="com.baobaotao.ditype.CarFactory"

        factory-method="createCar"></bean>

</beans>

 

2.2 使用註解的方式注入

2.2.1 使用@Autowired進行自動注入

Spring通過@Autowired註解實現Bean的依賴注入,下面是一個例子:

package com.baobaotao.anno;

 

import org.springframework.beans.factory.BeanNameAware;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Service;

//① 定義一個ServiceBean(不需要在XML中定義Bean

@Service

public class LogonService implements BeanNameAware{

        //② 分別注入LogDaoUserDaoBean(不需要在XML中定義property屬性注入)

    @Autowired(required=false)

    private LogDao logDao;

    @Autowired

    @Qualifier("userDao")

    private UserDao userDao;

    

    public LogDao getLogDao() {

        return logDao;

    }

    public UserDao getUserDao() {

        return userDao;

    }

    

    public void setBeanName(String beanName) {

        System.out.println("beanName:"+beanName);        

    }

    

    public void initMethod1(){

        System.out.println("initMethod1");

    }

    public void initMethod2(){

        System.out.println("initMethod2");

    }

}

處,我們使用@ServiceLogonService標註爲一個Bean,在處,通過@Autowired注入LogDaoUserDaoBean@Autowired默認按類型匹配的方式,在容器查找匹配的Bean,當有且僅有一個匹配的Bean時,Spring將其注入到@Autowired標註的變量中。

 

2.2.2 使用@Autowiredrequired屬性

如果容器中沒有一個和標註變量類型匹配的BeanSpring容器啓動時將報NoSuchBeanDefinitionException的異常。如果希望Spring即使找不到匹配的Bean完成注入也不用拋出異常,那麼可以使用@Autowired(required=false)進行標註:

@Service

public class LogonService implements BeanNameAware{

    @Autowired(required=false)

    private LogDao logDao;

        ...

}

默認情況下,@Autowiredrequired屬性的值爲true,即要求一定要找到匹配的Bean,否則將報異常。

 

2.2.3 使用@Qualifier指定注入Bean的名稱

 

如果容器中有一個以上匹配的Bean時,則可以通過@Qualifier註解限定Bean的名稱,如下所示:

@Service

public class LogonService implements BeanNameAware{

    @Autowired(required=false)

    private LogDao logDao;

    //①注入名爲UserDao,類型爲UserDaoBean

    @Autowired

    @Qualifier("userDao")

    private UserDao userDao;

}

 

這裏假設容器有兩個類型爲UserDaoBean,一個名爲userDao,另一個名爲otherUserDao,則處會注入名爲userDaoBean

 

2.2.4 對類方法進行標註

 

@Autowired可以對類成員變量及方法的入參進行標註,下面我們在類的方法上使用@Autowired註解:

package com.baobaotao.anno;

 

import org.springframework.beans.factory.BeanNameAware;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Service;

 

@Service

public class LogonService implements BeanNameAware{

    

    private LogDao logDao;

    private UserDao userDao;

    

    

    @Autowired

    public void setLogDao(LogDao logDao) {

        this.logDao = logDao;

    }

    

    @Autowired

    @Qualifier("userDao")

    public void setUserDao(UserDao userDao) {

        System.out.println("auto inject");

        this.userDao = userDao;

    }

}

如果一個方法擁有多個入參,在默認情況下,Spring自動選擇匹配入參類型的Bean進行注入。Spring允許對方法入參標註@Qualifier以指定注入Bean的名稱,如下所示:

    @Autowired

    public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){

        System.out.println("multi param inject");

        this.userDao = userDao;

        this.logDao =logDao;

    }

在以上例子中,UserDao的入參注入名爲userDaoBean,而LogDao的入參注入LogDao類型的Bean

一般情況下,在Spring容器中大部分的Bean都是單實例的,所以我們一般都無須通過@Repository@Service等註解的value屬性爲Bean指定名稱,也無須使用@Qualifier按名稱進行注入。

 

2.2.5 對標準註解的支持

 

此外,Spring還支持@Resource@Inject註解,這兩個標準註解和@Autowired註解的功能類似,都是對類變量及方法入參提供自動注入的功能。@Resource要求提供一個Bean名稱的屬性,如果屬性爲空,則自動採用標註處的變量名或方法名作爲Bean的名稱。

package com.baobaotao.anno;

 

import javax.annotation.PostConstruct;

import javax.annotation.PreDestroy;

import javax.annotation.Resource;

 

import org.springframework.stereotype.Component;

 

@Component

public class Boss {

    

    private Car car;

    

    public Boss(){

        System.out.println("construct...");

    }

 

//    @Autowired

//    private void setCar(Car car){

//        System.out.println("execute in setCar");

//        this.car = car;

//    }

    

    @Resource("car")

    private void setCar(Car car){

        System.out.println("execute in setCar");

        this.car = car;

    }

    

    @PostConstruct

    private void init1(){

        System.out.println("execute in init1");

    }

    

    @PostConstruct

    private void init2(){

        System.out.println("execute in init1");

    }

    

    @PreDestroy

    private void destory1(){

        System.out.println("execute in destory1");

    }

    

    @PreDestroy

    private void destory2(){

        System.out.println("execute in destory2");

    }

}

 

這時,如果@Resource未指定"car"屬性,則也可以根據屬性方法得到需要注入的Bean名稱。可見@Autowired默認按類型匹配注入Bean@Resource則按名稱匹配注入Bean。而@Inject@Autowired一樣也是按類型匹配注入的Bean的,只不過它沒有required屬性。可見不管是@Resource還是@Inject註解,其功能都沒有@Autowired豐富,因此除非必須,大可不必在乎這兩個註解。(類似於Xml中使用<constructor-arg ref="logDao"></constructor-arg>或者<property name="logDao" ref="logDao"></property>進行注入,如果使用了@Autowired或者Resource等,這不需要在定義Bean時使用屬性注入和構造方法注入了)

 

2.2.6 關於Autowired@Resource

 

1.@Autowired注入是按照類型注入的,只要配置文件中的bean類型和需要的bean類型是一致的,這時候注入就沒問題。但是如果相同類型的bean不止一個,此時注入就會出現問題,Spring容器無法啓動。 
2.@Resourced標籤是按照bean的名字來進行注入的,如果我們沒有在使用@Resource時指定bean的名字,同時Spring容器中又沒有該名字的bean,這時候@Resource就會退化爲@Autowired即按照類型注入,這樣就有可能違背了使用@Resource的初衷。所以建議在使用@Resource時都顯示指定一下bean的名字@Resource(name="xxx") 

 

2.2.7 @Resource@Autowired生效的幾種方式

 

1.xml配置文件中顯式指定 

<!-- 爲了使用Autowired標籤,我們必須在這裏配置一個bean的後置處理器 -->  

    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />   

      

    <!-- 爲了使用@Resource標籤,這裏必須配置一個後置處理器 -->  

    <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />   

2.xml配置文件中使用context:annotation-config 

<context:annotation-config />


3.xml配置文件中使用context:component-scan 

<context:component-scan base-package="com.baobaotao.anno"/>

 

4.重寫Spring容器的Context,在自定義BeanFactory時調用AnnotationConfigUtils.registerAnnotationConfigProcessors()把這兩個註解處理器增加到容器中。 

編寫自己的XmlWebApplicationContext,在這個context中重寫customizeBeanFactory(),在這個方法中調用了AnnotationConfigUtils.registerAnnotationConfigProcessors()方法把這兩自動註解處理器加入到BeanDefinitions中,這樣公在web層就支持@Resource@Autowired進行自動注入。如下:

package com.alibaba.citrus.springext.support.context;

 

import com.alibaba.citrus.springext.ResourceLoadingExtendable;

import com.alibaba.citrus.springext.ResourceLoadingExtender;

import com.alibaba.citrus.springext.support.context.InheritableListableBeanFactory;

import com.alibaba.citrus.springext.support.resolver.XmlBeanDefinitionReaderProcessor;

import java.io.IOException;

import org.springframework.beans.factory.support.DefaultListableBeanFactory;

import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import org.springframework.context.ApplicationListener;

import org.springframework.context.annotation.AnnotationConfigUtils;

import org.springframework.core.io.Resource;

import org.springframework.core.io.support.ResourcePatternResolver;

 

public class XmlWebApplicationContext extends org.springframework.web.context.support.XmlWebApplicationContext implements ResourceLoadingExtendable {

    private ResourceLoadingExtender resourceLoadingExtender;

    private boolean parentResolvableDependenciesAccessible = true;

 

    public XmlWebApplicationContext() {

    }

 

    public boolean isParentResolvableDependenciesAccessible() {

        return this.parentResolvableDependenciesAccessible;

    }

 

    public void setParentResolvableDependenciesAccessible(boolean parentResolvableDependenciesAccessible) {

        this.parentResolvableDependenciesAccessible = parentResolvableDependenciesAccessible;

    }

 

    public void setResourceLoadingExtender(ResourceLoadingExtender resourceLoadingExtender) {

        if(this.resourceLoadingExtender != null) {

            this.getApplicationListeners().remove(this.resourceLoadingExtender);

        }

 

        this.resourceLoadingExtender = resourceLoadingExtender;

        if(resourceLoadingExtender instanceof ApplicationListener) {

            this.addApplicationListener((ApplicationListener)resourceLoadingExtender);

        }

 

    }

 

    protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {

        (new XmlBeanDefinitionReaderProcessor(beanDefinitionReader)).addConfigurationPointsSupport();

    }

 

    protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) {

        super.customizeBeanFactory(beanFactory);

    //AnnotationConfigUtils.registerAnnotationConfigProcessors()方法把這兩自動註解處理器加入到BeanDefinitions,web層就支持@Resource@Autowired進行自動注入

        AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory, (Object)null);

    }

 

    protected DefaultListableBeanFactory createBeanFactory() {

        return (DefaultListableBeanFactory)(this.isParentResolvableDependenciesAccessible()?new InheritableListableBeanFactory(this.getInternalParentBeanFactory()):super.createBeanFactory());

    }

 

    protected Resource getResourceByPath(String path) {

        Resource resource = null;

        if(this.resourceLoadingExtender != null) {

            resource = this.resourceLoadingExtender.getResourceByPath(path);

        }

 

        if(resource == null) {

            resource = super.getResourceByPath(path);

        }

 

        return resource;

    }

 

    protected ResourcePatternResolver getResourcePatternResolver() {

        final ResourcePatternResolver defaultResolver = super.getResourcePatternResolver();

        return new ResourcePatternResolver() {

            public Resource[] getResources(String locationPattern) throws IOException {

                ResourcePatternResolver resolver = null;

                if(XmlWebApplicationContext.this.resourceLoadingExtender != null) {

                    resolver = XmlWebApplicationContext.this.resourceLoadingExtender.getResourcePatternResolver();

                }

 

                if(resolver == null) {

                    resolver = defaultResolver;

                }

 

                return resolver.getResources(locationPattern);

            }

 

            public ClassLoader getClassLoader() {

                return defaultResolver.getClassLoader();

            }

 

            public Resource getResource(String location) {

                return defaultResolver.getResource(location);

            }

        };

    }

}

三、 ×××

1、克隆

git clone https://github.com/spring-projects/spring-framework.git

2、使用maven

1Core

支持dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL, AOP,其依賴爲:

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-core</artifactId>

    <version>5.1.2.RELEASE</version>

</dependency>

2WebMVC

支持MVC, View Technologies, CORS, Web Socket, RESTful,同時繼承Spring Web的功能core HTTP integration,包括Servlet filters, Spring HTTP Invoker, infrastructure to integrate with 
other web frameworks and HTTP technologies e.g. Hessian, Burlap。其依賴爲:

<dependency>

    <groupId>org.springframework</groupId>

    <artifactId>spring-webmvc</artifactId>

    <version>5.1.2.RELEASE</version>

</dependency>

注意:依賴了spring-webmvc就不需要依賴spring-web

3spring-boot-starter-web

注意:啓動器spring-boot-starter-web基於Spring MVC構建RESTful風格的web應用,使用內嵌tomcat作爲默認容器

 

3、使用Gradle

1spring-corespring-webmvc

dependencies {

api 'org.springframework:spring-core:5.0.8.RELEASE'

api 'org.springframework:spring-webmvc:5.0.8.RELEASE'

}

或者

dependencies {

compile group: 'org.springframework', name: 'spring-core', version: '5.0.8.RELEASE'

compile group: 'org.springframework', name: 'spring-webmvc', version: '5.0.8.RELEASE'

}

2spring-boot-starter-web

dependencies {

    compile("org.springframework.boot:spring-boot-starter-web")

}

 

注意:啓動器spring-boot-starter-web基於Spring MVC構建RESTful風格的web應用,使用內嵌tomcat作爲默認容器

 

可以在Spring Boot”中瞭解註解和啓動器清單。


四、SOAP Web Service生產服務

1)依賴Spring Web

 

***Maven***

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web-services</artifactId>

</dependency>

 

***Gradle***

dependencies {

    compile("org.springframework.boot:spring-boot-starter-web-services")

}

 

2)定義web service domain

使用XSDXML schema file)定義Domain

 

例如:在resources目錄下創建countries.xsd文件來定義國家的名稱、人口、首都和貨幣。

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://jiaxiaomei.com/test/soap-test"

targetNamespace="http://jiaxiaomei.com/test/soap-test" elementFormDefault="qualified">

 

    <xs:element name="getCountryRequest">

        <xs:complexType>

            <xs:sequence>

                <xs:element name="name" type="xs:string"/>

            </xs:sequence>

        </xs:complexType>

    </xs:element>

 

    <xs:element name="getCountryResponse">

        <xs:complexType>

            <xs:sequence>

                <xs:element name="country" type="tns:country"/>

            </xs:sequence>

        </xs:complexType>

    </xs:element>

 

    <xs:complexType name="country">

        <xs:sequence>

            <xs:element name="name" type="xs:string"/>

            <xs:element name="population" type="xs:int"/>

            <xs:element name="capital" type="xs:string"/>

            <xs:element name="currency" type="tns:currency"/>

        </xs:sequence>

    </xs:complexType>

 

    <xs:simpleType name="currency">

        <xs:restriction base="xs:string">

            <xs:enumeration value="GBP"/>

            <xs:enumeration value="EUR"/>

            <xs:enumeration value="PLN"/>

        </xs:restriction>

    </xs:simpleType>

</xs:schema>

 

3)產生Domain

maven或者gradle基於xsd文件自動創建Domain類。

 

***Maven*** 修改pom.xml

首先新增依賴:

<dependency>

<groupId>wsdl4j</groupId>

<artifactId>wsdl4j</artifactId>

</dependency>

然後增加構建插件:

<!-- tag::JAXB2, xsd[] -->

<plugin>

<groupId>org.codehaus.mojo</groupId>

<artifactId>jaxb2-maven-plugin</artifactId>

<version>1.6</version>

<executions>

<execution>

<id>xjc</id>

<goals>

<goal>xjc</goal>

</goals>

</execution>

</executions>

<configuration>

<schemaDirectory>${project.basedir}/src/main/resources/</schemaDirectory>

<outputDirectory>${project.basedir}/src/main/java</outputDirectory>

<clearOutputDir>false</clearOutputDir>

</configuration>

</plugin>

<!-- end::JAXB2, xsd[] -->

 

 

注意plugin標籤放在plugins標籤裏面。

保存後,eclipse會自動執行xjc,在java目錄下自動生成六個java文件:


image.png


***Gradle*** 需要在build.gradle中配置JAXB

A、在bootJar裏面增加:

from genJaxb.classesDir

B、新增configurations

configurations {

    jaxb

}

C、在dependencies裏面增加對Spring WSJAXB的依賴:

    compile("wsdl4j:wsdl4j:1.6.1")

    jaxb("org.glassfish.jaxb:jaxb-xjc:2.2.11")

    compile(files(genJaxb.classesDir).builtBy(genJaxb))

D、增加genJaxb任務(Ant任務,因爲gradle還不支持JAXB任務):

task genJaxb {

    ext.sourcesDir = "${buildDir}/generated-sources/jaxb"

    ext.classesDir = "${buildDir}/classes/jaxb"

    ext.schema = "src/main/resources/countries.xsd"

 

    outputs.dir classesDir

 

    doLast() {

        project.ant {

            taskdef name: "xjc", classname: "com.sun.tools.xjc.XJCTask",

                    classpath: configurations.jaxb.asPath

            mkdir(dir: sourcesDir)

            mkdir(dir: classesDir)

 

            xjc(destdir: sourcesDir, schema: schema) {

                arg(value: "-wsdl")

                produces(dir: sourcesDir, includes: "**/*.java")

            }

 

            javac(destdir: classesDir, source: 1.6, target: 1.6, debug: true,

                    debugLevel: "lines,vars,source",

                    classpath: configurations.jaxb.asPath) {

                src(path: sourcesDir)

                include(name: "**/*.java")

                include(name: "*.java")

            }

 

            copy(todir: classesDir) {

                fileset(dir: sourcesDir, erroronmissingdir: false) {

                    exclude(name: "**/*.java")

                }

            }

        }

    }

}

 

E、增加afterEclipseImport任務

task afterEclipseImport {

dependsOn "genJaxb"

}

 

保存後自動生成java類,注意gradle採用了ant任務,比maven複雜很多。

關於jaxbant任務的更多知識,參見“9JAXBANT”。

 

4)創建Repository

該類需要註解@Component,給web service提供數據。

 

例如:

A、創建CountryRepository類,並增加註解@Component

 

B、增加一個靜態成員:

private static final Map<String, Country> countries = new HashMap<>();

 

C、增加初始化數據的方法initData,返回類型爲void,代碼爲:

Country spain = new Country();

spain.setName("Spain");

spain.setCapital("Madrid");

spain.setCurrency(Currency.EUR);

spain.setPopulation(46704314);

 

countries.put(spain.getName(), spain);

 

Country poland = new Country();

poland.setName("Poland");

poland.setCapital("Warsaw");

poland.setCurrency(Currency.PLN);

poland.setPopulation(38186860);

 

countries.put(poland.getName(), poland);

 

Country uk = new Country();

uk.setName("United Kingdom");

uk.setCapital("London");

uk.setCurrency(Currency.GBP);

uk.setPopulation(63705000);

 

countries.put(uk.getName(), uk);

 

D、增加查找方法findCountry,參數爲name,返回Country

Assert.notNull(name, "The country's name must not be null");

return countries.get(name);

 

5)創建service endpoint來處理SOAP請求

 

A、創建endpoint類,加註解@Endpoint,增加構造函數並註解@Autowired

 

按照country例子:

@Autowired

public CountryEndpoint(CountryRepository countryRepository) {

this.countryRepository = countryRepository;

}

 

其中,@Endpoint註冊一個Spring WS類作爲處理SOAP消息的候選者。

 

B、增加靜態成員NAMESPACE_URI

例如:

private static final String NAMESPACE_URI = "http://jiaxiaomei.com/test/soap-test";

 

C、增加Repository的私有成員

例如:

private CountryRepository countryRepository;

 

D、增加查詢方法,註解@PayloadRoot@RequestPayload@ResponsePayload

例如:

@PayloadRoot(namespace = NAMESPACE_URI, localPart = "getCountryRequest")

@ResponsePayload

public GetCountryResponse getCountry(@RequestPayload GetCountryRequest request) {

GetCountryResponse response = new GetCountryResponse();

response.setCountry(countryRepository.findCountry(request.getName()));

 

return response;

}

 

其中,@PayloadRootSpring WS提供,選擇處理方法,屬性包括namespacelocalpart

@RequestPayloadSpring WS提供,表示SOAP消息將會綁定到request參數。

@ResponsePayloadSpring WS提供,映射返回值到響應負載中。

 

6)配置web service bean

A、從WsConfigurerAdapter繼承一個webservice配置類,並註解@EnableWs@Configuration

例如:

@EnableWs

@Configuration

public class WebServiceConfig extends WsConfigurerAdapter {

 

B、自定義DispatcherServlet bean

Spring WS使用不同的servlet類型來處理SOAP消息,該類型是MessageDispatcherServlet

需要注入ApplicationContext到方法中,這樣才能讓Spring WS自動偵測到Spring bean

重新命名MessageDispatcherServlet注入方法,就不替換Spring Boot的默認DispatcherServlet bean,默認bean通過鏈接點/提供服務,自定義的MessageDispatcherServlet通過ServletRegistrationBean重新設定鏈接點。

前面的@Endpoint註解已經提示DefaultMethodEndpointAdapter設置註解驅動Spring WS編程模型。這裏就採用這種註解驅動模型。

 

例如:

@Bean

public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {

MessageDispatcherServlet servlet = new MessageDispatcherServlet();

servlet.setApplicationContext(applicationContext);

servlet.setTransformWsdlLocations(true);

return new ServletRegistrationBean(servlet, "/ws/*");

}

 

C、基於DefaultWsdl11Definition構建WSDL定義

DefaultWsdl11Definition使用XsdSchema接口展示WSDL1.1標準。

例如:

@Bean(name = "countries")

public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema countriesSchema) {

DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();

wsdl11Definition.setPortTypeName("CountriesPort");

wsdl11Definition.setLocationUri("/ws");

wsdl11Definition.setTargetNamespace("http://jiaxiaomei.com/test/soap-test");

wsdl11Definition.setSchema(countriesSchema);

return wsdl11Definition;

}

其中@Bean的屬性name實際上指定了wsdl的文件名,所以,wsdl文件可以獲得於這個鏈接:

http://<host>:<port>/ws/countries.wsdl

 

7)構建Application類,註解@SpringBootApplication

Build後啓動這個服務,創建一個請求xml,編寫進入SOAP請求。

例如:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"

  xmlns:gs="http://jiaxiaomei.com/test/soap-test">

   <soapenv:Header/>

   <soapenv:Body>

      <gs:getCountryRequest>

         <gs:name>Spain</gs:name>

      </gs:getCountryRequest>

   </soapenv:Body>

</soapenv:Envelope>

 

下載curl,地址:https://curl.haxx.se/,設置bin目錄到環境變量path。然後,在命令行中使用curl發送soap請求,如下:

image.png


下載libxml2,包含三個文件iconv-1.14-win32-x86_64.7zzlib-1.2.8-win32-x86_64.7zlibxml2-2.9.3-win32-x86_64.7z,下載地址:https://www.zlatkovic.com/pub/libxml/64bit/;解壓後把三個bin都放到path環境變量中。然後在命令行中執行:

curl --header "content-type: text/xml" -d @request.xml http://localhost:8080/ws |xmllint --format

image.png

這讓soap response文件更規整。


五、 RESTful Web Service生產服務

參考:https://spring.io/guides/gs/rest-service/做了一個實際例子。

1)新建一個gradle project,修改build.gradle

A、修改依賴爲:

dependencies {

    // This dependency is exported to consumers, that is to say found on their compile classpath.

    //api 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'

 

    // This dependency is used internally, and not exposed to consumers on their own compile classpath.

    implementation 'org.springframework.boot:spring-boot-starter-web:2.1.0.RELEASE'

 

    // spring-boot-starter-test based on JUnit test framework

    testImplementation 'org.springframework.boot:spring-boot-starter-test:2.1.0.RELEASE'

}

 

B、修改構建腳本

buildscript {

    repositories {

        mavenCentral()

    }

    dependencies {

        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.1.0.RELEASE")

    }

}

 

C、引用插件

apply plugin: 'eclipse'

apply plugin: 'idea'

apply plugin: 'org.springframework.boot'

apply plugin: 'io.spring.dependency-management'

 

D、增加jar包輸出信息

bootJar {

    baseName = 'restfultest'

    version =  '0.1.0'

}

 

E、增加兼容性信息

sourceCompatibility = 1.8

targetCompatibility = 1.8

 

保存後刷新項目(項目右鍵菜單àgradleàrefresh gradle project),提示:

image.png


修改項目gradle設置:

image.png


2)新建一個model


image.png


3)新建一個controller

image.png


@RestControllerSpring4裏面引入,參見“5Spring Boot”的“Annotation”,用於標註類爲控制器,該控制器每個方法都返回一個model,而不是view

@RequestMapping註解映射HTTP請求http://host:port/msg到所標註的方法上。

@RequestParam註解綁定HTTP請求參數到所標註的方法參數上(http://host:port/msg?name=)。

注意:這是一個RESTful web service controller,而不是傳統的MVC controller。這個controller僅僅返回一個model對象,這個對象數據直接使用JSON寫到HTTP responseMappingJackson2HttpMessageConverter自動被執行來支持Jackson 2。傳統MVC controller使用view技術在server-side把數據轉換爲html

4)新建一個Application

image.png


5)執行

使用Boot Dashboard的左上角按鈕啓動服務。

image.png


Chrome中驗證:

image.png


六、 SOAP Web Service消費服務

參考:https://spring.io/guides/gs/consuming-web-service/

 

1)新建一個maven項目

修改pom.xml

image.png


2)編寫任務,自動從WSDL產生類。

image.png


3)創建一個web service客戶端

WebServiceGatewaySupport類繼承而來:

image.png


其中getCountry方法完成SOAP數據交換任務。

GetCountryRequestGetCountryResponse都是jaxb自動產生的。

WebServiceGatewaySupport的方法getWebServiceTemplate獲得WebServiceTemplate,然後使用marshalSendAndReceive方法來實際完成wsdl文件接收和解析。

SoapActionCallback用來獲取SOAPAction headerWSDL解析時需要這個頭,其中包含<soap:operation/> 元素。

4)配置web service

Spring WS通過Spring FrameworkOXM模塊中的Jaxb2Marshaller來存取XML請求。

image.png


marshaller bean指向domain對象集合,在XMLPOJOs之間轉換。

countryClient bean用來配置web serviceURI,並綁定marshaller


5)配置應用入口類

image.png


七、 RESTful Web Service消費服務

參考:https://spring.io/guides/gs/consuming-rest/

 

1)新建一個gradle project,修改build.gradle,如下

image.png


2)創建一個domain類,如下:

image.png


3)創建一個Application類,如下:

image.png


其中服務地址引用RESTful Web Service生產服務”,執行後輸出:

image.png


4)改造Application

image.png


其中@BeanrestTemplate註解到CommandLineRunner回調函數中。

 

由於和RESTful Web Service生產服務”使用的8080端口衝突,所以需要增加Application.yml,如下:

image.png


啓動後,如下:

image.png


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章