springboot03_核心特性及設計思想


4.1.5.springboot核心特性及設計思想【上】

時長:55min

5.1.springboot注解驅動

5.1.1.spring4.X版本注解驅動

  主要是注解裝飾功能的一個完善。提供條件注解裝配,即@Conditional注解使用。

5.1.1.1.什么是條件化裝配?

  其實,它是對是否進行bean裝配的一個條件限制,如果條件返回true,則允許裝配。否則,不允許。

下面通過示例代碼來,說明條件裝配的應用。

1.定義一個配置類

  首先,創建一個springboot項目。然后創建配置類:

package com.wf.demo.springbootdemo.condition;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName SpringConfiguration
 * @Description 統一的配置類
 * @Author wf
 * @Date 2020/7/6 18:09
 * @Version 1.0
 */
@Configuration
public class SpringConfiguration {
    /**
     * 在某個環境下不裝載
     * 或不滿足某個條件不裝載
     * 或者已經裝載過了,不要重復裝載
     * @return
     */
    @Conditional(MyCondition.class)
    @Bean
    public DemoService demoService(){
        return new DemoService();
    }
}

 

說明:

  配置類中,通過@Bean進行bean裝配,然后加上@Conditional注解,就能實現條件裝配。

  @Conditional注解,傳參為Condition接口子類Class對象,可以傳參多個子類對象,多個對象之間是且的關系。

2.定義Condition子類
package com.wf.demo.springbootdemo.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;

/**
 * @ClassName MyCondition
 * @Description 實現條件子類
 * @Author wf
 * @Date 2020/7/6 18:14
 * @Version 1.0
 */
public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //TODO 這里可以寫對應的判斷邏輯
        return false;
    }
}

 

說明:

  子類中實現match方法,進行條件判斷,如果返回false,則不允許bean裝配。

3.bean定義
package com.wf.demo.springbootdemo.condition;

/**
 * @ClassName DemoService
 * @Description service bean類
 * @Author wf
 * @Date 2020/7/6 18:10
 * @Version 1.0
 */
public class DemoService {
}

 

4.測試bean實例獲取
package com.wf.demo.springbootdemo.condition;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @ClassName ConditionMain
 * @Description 測試類
 * @Author wf
 * @Date 2020/7/6 18:17
 * @Version 1.0
 */
public class ConditionMain {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        DemoService bean = context.getBean(DemoService.class);
        System.out.println(bean);
    }
}

 

測試結果如下:

 

 

 結果拋出異常,因為條件返回false,不允許裝配。

 

下面修改match方法,返回true,如下所示:

 

 

 再次運行測試,成功裝配,如下所示:

 

 

5.1.2.spring5.X版本注解驅動

引入@Indexed注解,是用來提升性能的。當項目文件目錄很多時,掃描@Component,@Service。。。這些注解時,

就會很耗時,而@Indexed注解,可以提升注解掃描的性能。

 

總結

  spring的注解驅動的發展,是為了bean裝配更加簡單。

  

下面通過springboot中整合redis來說明,注解驅動的簡便性。

5.1.2.1.springboot整合redis示例

1.pom.xml中引入redis依賴
 <!--整合redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

 

版本由springboot-parent來管理。

2.redis服務安裝

  在生產中會有專門的redis服務安裝redis服務。而學習環境下,一般是在虛擬機上安裝redis應用服務器。

3.controller中引用redis代碼
package com.wf.demo.springbootdemo.web;

import com.wf.demo.springbootdemo.dao.pojo.User;
import com.wf.demo.springbootdemo.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName HelloController
 * @Description 測試controller
 * @Author wf
 * @Date 2020/7/6 11:27
 * @Version 1.0
 */
@RestController
public class HelloController {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    //配置文件注入
//    @Value("Mic")
//    private String name;
    @Autowired
    private IUserService userService;

    @GetMapping("hello")
    public String test(){
        User user = new User();
        user.setId(18);
        user.setName("MIc");
        userService.insert(user);
        return "success";
    }

    @GetMapping("/say")
    public String say(){
        return redisTemplate.opsForValue().get("name");
    }
}

 

說明:

  springboot已經為了我們使用redis封裝了客戶端工具類RedisTemplate。

  只有使用它的api即可。

4.配置redis連接參數
spring.redis.host=192.168.216.128
#redis.port=6379默認

 

5.運行項目,測試

  只需要啟動main,瀏覽器端訪問controller接口即可。

 

說明:

  我們可以看到,springboot整合redis是如此之簡單。

  這里RedisTemplate實例,能夠直接引用,說明spring IOC容器中已經完成bean的裝配。

  但是,我們是沒有做這部分工作的,而是由springboot進行了裝配。

【1】回顧spring4.X中bean裝配方式

  》xml配置

  》@Configuration注解裝配

  》@Enable裝配

現在,springboot提供了自動裝配的功能。  

5.2.springboot自動裝配

  自動裝配的原理是什么?是如何實現的呢?

5.2.1.spring動態Bean的裝配方案

主要有兩種:
ImportSelector:

Registator

  所謂動態裝載,即根據上下文,或某些條件去裝載一些配置類。

下面通過代碼示例,來說明springboot實現批量掃描配置類的原理。

    所謂批量掃描,就是一次性掃描所有jar包中的配置類。示例代碼中以分包的形式,來模擬不同jar包的掃描。

5.2.1.1.實現ImportSelector的方式

1.在demo2包下創建redis配置類
package com.wf.demo.springbootdemo.demo2;

/**
 * @ClassName RedisConfiguration
 * @Description redis配置類
 * @Author wf
 * @Date 2020/7/6 19:25
 * @Version 1.0
 */

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedisConfiguration {
    @Bean
    public MyRedisTemplate myRedisTemplate(){
        return new MyRedisTemplate();
    }
}
package com.wf.demo.springbootdemo.demo2;

/**
 * @ClassName MyRedisTemplate
 * @Description 自定義redis模板類
 * @Author wf
 * @Date 2020/7/6 19:25
 * @Version 1.0
 */
public class MyRedisTemplate {
}

 

2.在demo3包下創建Mybatis配置類
package com.wf.demo.springbootdemo.demo3;

/**
 * @ClassName MySqlSessionFactory
 * @Description bean
 * @Author wf
 * @Date 2020/7/7 9:44
 * @Version 1.0
 */
public class MySqlSessionFactory {
}
package com.wf.demo.springbootdemo.demo3;

/**
 * @ClassName MybatisConfiguration
 * @Description redis配置類
 * @Author wf
 * @Date 2020/7/6 19:25
 * @Version 1.0
 */

import com.wf.demo.springbootdemo.demo2.MyRedisTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisConfiguration {
    @Bean
    MySqlSessionFactory mySqlSessionFactory(){
        return new MySqlSessionFactory();
    }
}

 

3.scan包下進行springboot批量掃描處理 
 【1】定義一個Selector子類實現
package com.wf.demo.springbootdemo.scan;

import com.wf.demo.springbootdemo.demo2.RedisConfiguration;
import com.wf.demo.springbootdemo.demo3.MybatisConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @ClassName MyImportSelector
 * @Description 子類實現
 * @Author wf
 * @Date 2020/7/6 19:30
 * @Version 1.0
 */
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{MybatisConfiguration.class.getName(), RedisConfiguration.class.getName()};
    }
}

 

說明:

  實現子類中返回了多個配置類的全路徑類名。【即可獲取配置類的位置】

【2】定義Enable注解,來掃描Selector實現子類
package com.wf.demo.springbootdemo.scan;

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableConfiguration {
}

 

【3】springboot啟動入口main中bean獲取測試
package com.wf.demo.springbootdemo;

import com.wf.demo.example.demo2.MyRedisTemplate;
import com.wf.demo.example.demo3.MySqlSessionFactory;
import com.wf.demo.example.scan.EnableConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

//@EnableConfiguration
@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args);
        System.out.println(ca.getBean(MyRedisTemplate.class));
        System.out.println(ca.getBean(MySqlSessionFactory.class));
    }

}

 

說明:

  這里先注釋掉@EnableConfiguration注解,測試結果,肯定會報錯。如下所示:

 

 然后,放開注解。bean注入成功,如下所示:

 

再次運行測試,結果如下:

 

總結:

  為什么加上@EnableConfiguration注解,就能掃描到所有的配置類了呢?

  因為這個注解里面,使用@Import注解,告知了spring所有配置類的位置所在。

  其實是ImportSelector接口實現子類中,重寫selectImports方法,告訴了spring要掃描的配置類的位置在哪里。

4.selectImports方法的實現

  上面的實現中,傳遞的是配置類的全路徑類名,其實也可以直接傳遞要裝配bean的全路徑類名。

方法實現修改示例如下:

 

 然后,啟動main,測試結果如下:

思考:

  現在,已經基本弄清楚,批量掃描配置類的底層原理了。那么,就需要進行功能擴展,現在,只是通過傳參兩個類名,

然后就可以掃描兩個配置類了。

  如果有很多的第三方組件需要批量掃描,總不能一個個傳參類名吧,這么做顯然是不合理的。

  所以,在selectImports方法中,一定可以通過某種機制去完成指定路徑的配置類的掃描。

  這里的某種機制,也體現了springboot 約定優於配置的設計思想。我們可以想到,第三方starter組件由不同團隊進行開發,組件的名稱

和包路徑肯定是不一樣的。

  因此,springboot 就做出統一約定【定個標准】,你們第三方去開發starter組件,需要把配置類的說明【在某個路徑下的全類名】要告訴

springboot引擎。

定義的標准是:

  每一個starter組件,都需要定義路徑classpath:META-INF/spring.factories文件【每一個jar包里面都要有】。有了這個文件,springboot

就很好處理了。只需要掃描並解析classpath*:META-INF/spring.factories文件,就可以獲得相關配置類的全路徑類名,然后傳遞到數組里面就可以了。

注意:

  當我寫完代碼之后,針對包結構,做了如下調整:

 

5.2.2.springboot源碼驗證自動裝配-批量實現原理

5.2.2.1.SpringBootApplication注解開始分析

 

通過注解類的定義,注解上引用@EnableAutoConfiguration注解,應該就是自動裝配的作用。

1.分析@EnableAutoConfiguration注解

 

 可以看到通過@Import注解,引入Selector接口的實現,下面實現子類的代碼邏輯:

2.分析AutoConfigurationImportSelector動態裝配實現邏輯
public class AutoConfigurationImportSelector implements DeferredImportSelector
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

跟蹤this.getAutoConfigurationEntry方法:

 

通過debug啟動main入口程序,進入斷點處,可以獲取到配置類的全路徑類名。

再次跟蹤getCandidateConfigurations方法:

 

 

 

 

 

顯然,就是在解析classpath下META-INF/spring.factories文件。

5.2.2.2.springboot自動裝配原理圖解

如下圖所示 :

 

 

4.1.6.springboot核心特性及設計思想【下】

 5.2.3.springboot掃描並解析classpath下META-INF/spring.factories文件的具體實現

通過上面的源碼分析,定位到getCandidateConfigurations方法。

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
        Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
        return configurations;
    }

 

方法實現邏輯中,調用SpringFactoriesLoader工具類。

5.2.3.1.SpringFactoriesLoader工具類

  根據定義類名,就是加載spring.factories文件。這里調用SpringFactoriesLoader.loadFactoryNames()方法

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
    }

 

返回一個List,再次調用 loadSpringFactories()方法,如下所示:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader); //先從緩存中獲取 if (result != null) {
            return result;  //緩存中有數據,直接返回
        } else {
            try {//這里會有多個jar包,就對應多個文件
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();

                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url); //得到文件資源路徑
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();

                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;

                        for(int var11 = 0; var11 < var10; ++var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }

                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }

 

 下面來看一個spring.factories文件是什么樣子的,在springboot項目中,全局搜索spring.factories,如下所示:

 

然后,任意打開一個文件,如下所示:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

 

 可以看到,和properties文件很像,數據存儲中key/value形式。

key為:org.springframework.boot.autoconfigure.EnableAutoConfiguration

value為:配置類全路徑類名,通過,拼接成字符串

 

注意:

  spring.factory文件可能是存儲其他配置的,並不全是自動裝配的配置。舉例如下:

 

它是作為starter定義的配置文件。

 

 5.2.3.2.SPI機制

  Service provider interface【簡稱SPI】,這是一 種什么樣的機制呢?它的核心思想是什么?

  我們以java中數據庫連接為例。jdk定義數據庫連接的接口。但沒有定義實現。而是數據庫廠商提供實現方案。

如:mysql連接,我們會引入一個mysql的驅動包。【這就是一種實現方案】

 

  然后,我在項目上下文環境,一般是application.properties文件中定義數據庫連接參數。 如:jdbc.url/username/password...

在jdk環境中,提供對這些配置參數的解析。

  當獲得這些配置參數,這可以引用驅動包的服務, 建立通信。這就是一種擴展機制。這種擴展點設計,不是為了分隔接口定義

與實現分離,而是為功能的擴展。從而,提升框架的擴展性和靈活性。

  

  下面以springboot中spring.factories文件,作為SPI機制的運用來加以說明:

  每一個組件,打成一個jar包。在jar包中都會有一個spring.factories文件,對於自動裝配來說,這個文件定義自動裝配類的

實現。key代表着自動裝配的定義,如下所示:

 

 

下面以示例代碼來說明,這樣一種SPI機制是什么樣的。

1.創建一個maven項目,模擬jdk定義數據庫連接的接口

項目結構如下所示:

【1】定義一個建立連接的接口

  因為我們是站在框架設計的角度,我們沒有具體的實現,不知道它的具體實現是什么樣子的。

但是,我們可以定義一種規范,提供spi擴展點,讓實現方根據我們定義的規范來做具體的實現。

  代碼如下所示:

package com.wf.spi;

/**
 * @ClassName DataBaseDriver
 * @Description 驅動接口定義
 * @Author wf
 * @Date 2020/7/7 14:12
 * @Version 1.0
 */
public interface DataBaseDriver {
    String buildConnect(String host);
}

 

【2】使用maven install命令打成一個jar包

2.創建第二個maven項目,模擬mysql實現驅動包
【1】實現項目中引入接口項目的jar包依賴

修改pom.xml,如下所示:

 <dependency>
      <groupId>com.wf.spi</groupId>
      <artifactId>DataBaseDriver</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

 

【2】實現驅動服務
package com.wf.spi.mysql;

import com.wf.spi.DataBaseDriver;

/**
 * @ClassName MysqlDriver
 * @Description mysql驅動服務實現
 * @Author wf
 * @Date 2020/7/7 14:31
 * @Version 1.0
 */
public class MysqlDriver implements DataBaseDriver {
    @Override
    public String buildConnect(String url) {
        return "mysql的驅動實現:"+url;
    }
}

 

【3】定義擴展點

  完成了這樣一個mysql驅動實現,我怎么去使用它呢?

  就需要定義規范,通常是由配置文件,進行相關規范定義。

  這里,就需要創建resources資源文件夾,下面創建META-INF目錄。再目錄下創建services目錄,

然后在services目錄下創建text文件,文件命名為

驅動接口的全路徑類名。文件的內容也配置為接口實現類的全路徑類名。如下所示:

 

注意:

  這個是針對所有jar包規范。必須按此規范配置。

【4】項目完成后,打包。

 3.創建第三個maven項目,模擬用戶使用jdk連接mysql
[1]pom.xml中引入接口包和實現包
<dependency>
      <groupId>com.wf.spi</groupId>
      <artifactId>DataBaseDriver</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

    <dependency>
      <groupId>com.wf.spi.mysql</groupId>
      <artifactId>mysql-driver-connector</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>

 

【2】創建測試代碼
package com.wf.spi.use;

import com.wf.spi.DataBaseDriver;

import java.util.ServiceLoader;

public class App 
{
    public static void main( String[] args )
    {   //加載實現
        ServiceLoader<DataBaseDriver> serviceLoader = ServiceLoader.load(DataBaseDriver.class);
        for (DataBaseDriver driver : serviceLoader) {
            System.out.println(driver.buildConnect("Test"));
        }
    }
}

 

測試結果如下所示:

 

說明:

  這就是jdk底層的SPI機制應用。這里使用jdk提供的ServiceLoader工具類,加載實現類

 

 4.java中SPI規范

  java中要想實現SPI擴展,需要滿足幾個條件:

  》需要在classpath下創建目錄META-INF/services【必須完全一致】

  》在該目錄擴展點【定義接口】的text文件,文件名為定義接口全路徑類名

    》文件內容,配置接口實現類的全類名。如果有多個實現,換行配置多個

    》文件編碼格式為UTF-8

  》實現方式:使用jdk提供ServiceLoader工具類,加載實現類。

 

5.2.4.再了解springboot條件裝配

 我們發現有一個奇怪的現象:spring-boot與redis整合的starter包中沒有spring.factories文件,如下所示:

 

同樣,整合jdbc的starter包中,也沒有,如下所示:

 

這到底是為什么呢?這就涉及到springboot官方定義starter包的規范問題。

5.2.4.1.springboot官方定義starter包的規范

  springboot官方定義的starter包,存在一些規范:

官方包命名規范,如:spring-boot-starter-XXX.jar

第三方包命名規范,如:xxx-spring-boot-starter.jar

  如:mybatis定義starter命名,mybatis-spring-boot-starter

 springboot官方包,不存放配置類,而是專門定義自動裝配的包,如下所示:

 

這個包里,會把springboot提供的所有自動裝配的配置類信息,進行統一配置在spring.factories文件中。

 

  這是一種違反常規的設計,那么,spring-boot是如何實現自動裝配的呢?與常規的裝配方式有什么區別嗎?

下面以redis自動裝配為例,來進行說明:

5.2.4.2.springboot自動裝配再理解

  以redis自動裝配為例,搜索redis自動裝配配置類RedisAutoConfiguration,代碼實現如下:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

 

 1.@ConditionalOnClass({RedisOperations.class})

  可以看到配置類上,聲明@ConditionalOnClass({RedisOperations.class})注解,它有什么作用呢?

該注解,傳入參數為RedisOperations類的class對象,這個類是定義在spring-data-redis整合包中的。定義如下所示:

 

 

這個注解的含義是:

  如果傳入參數所在包,未引入,導致條件不滿足,就不允許裝配。

  所以,springboot官方starter包,其實是一種條件裝配,不需要引入配置類。

思考:

  如果條件傳參對象,所在包不存在,怎么辦?

  其實,這就涉及到maven依賴的傳遞依賴。所謂傳遞依賴,當前項目與依賴jar包如果不傳遞,當前項目未引入依賴時不會報錯。

 

下面以一個demo示例,來說明springboot自動裝配的過程。

5.2.4.3.spring自動裝配示例demo

1.創建第一個maven,模擬第三方組件
【1】pom.xml中引入spring-context依賴
   <spring.version>5.2.7.RELEASE</spring.version>
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>

 

【2】創建配置類
package com.wf.autoconfig.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ClassName MyConfiguration
 * @Description 配置類
 * @Author wf
 * @Date 2020/7/7 16:54
 * @Version 1.0
 */
@Configuration
public class MyConfiguration {
    @Bean
   public MyCore myCore(){
        return new MyCore();
   }
}

 

 

【3】創建bean類

 

package com.wf.autoconfig.demo;

/**
 * @ClassName MyCore
 * @Description 核心類
 * @Author wf
 * @Date 2020/7/7 16:52
 * @Version 1.0
 */
public class MyCore {
    public String study(){
        System.out.println("I'm learning p6 lesson");
        return "Gupaoedu.com";
    }
}

 

【4】打成一個jar包。

使用mvn install命令。

 

 

 2.在springboot項目中使用自定義組件
【1】pom.xml中引入組件依賴
 <!--引入自定義第三方組件依賴-->
        <dependency>
            <groupId>com.wf.autoconfig.demo</groupId>
            <artifactId>my-core-app</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

 

 【2】使用第三方組件中bean

  要想以spring注入的方式使用,顯然需要先完成自動裝配。先在main入口方法,測試一下bean能否獲取。

代碼如下所示:

package com.wf.demo.springbootdemo;

import com.wf.autoconfig.demo.MyCore;
import com.wf.demo.example.demo2.MyRedisTemplate;
import com.wf.demo.example.demo3.MySqlSessionFactory;
import com.wf.demo.example.scan.EnableConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@EnableConfiguration
@SpringBootApplication
public class SpringBootDemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext ca = SpringApplication.run(SpringBootDemoApplication.class, args);
        System.out.println(ca.getBean(MyRedisTemplate.class));
        System.out.println(ca.getBean(MySqlSessionFactory.class));
        System.out.println(ca.getBean(MyCore.class));   //獲取新定義第三方組件中bean實例
    }

}

 

然后,啟動main,進行測試,顯然,無法注入,會報錯,如下所示:

 

 

3.修改組件,完成自動裝配相關配置

  這里基於SPI機制進行裝配。

  創建resources資源文件夾,然后創建META-INF目錄,在目錄創建spring.factories文件,文件配置如下:

 

 

 

 具體配置內容如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wf.autoconfig.demo.MyConfiguration

 

然后,重新打包項目。

【1】再次在springboot項目中運行main

測試結果如下:

 

說明:

  自定義第三方組件bean成功注入。

 

 4.修改第三組件,實現條件注入
【1】引入springboot依賴starter包

  因為@ConditionalOnClass注解是springboot特有注解。

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.3.1.RELEASE</version>
      <optional>true</optional>
    </dependency>
<!--RedisOperations類依賴包-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.3.1.RELEASE</version>
<optional>true</optional>
</dependency>
 

 

【2】修改配置類,添加條件裝配

 

然后,重新打包項目。

 【3】回到springboot項目,測試

注釋掉spring-data-redis所在包依賴,如下所示:

 

說明:

  因為未引入所依賴jar,就會導致條件不滿足,無法進行bean裝配。測試肯定會報錯,如下所示:

 

反之,引入條件傳參依賴包,條件滿足,可完成自動裝配。

5.修改自定義組件,使用第二種配置方式實現條件裝配

  在META-INF目錄下,創建spring-autoconfigure-metadata.properties配置文件。如下所示:

 

 

配置內容如下:

com.wf.autoconfig.demo.MyConfiguration.ConditionalOnClass=com.wf.DemoClass

 

這里,其實是以配置文件的方式來完成條件注入。而上面是以注解的方式完成條件注入。

 

  不同的是,這種配置文件的方式注入,如果在組件中能找到com.wf.DemoClass就表示條件滿足,

可以在springboot項目中完成自動裝配。

  這里先不定義這個DemoClass類,讓條件不滿足,然后打包項目。

【1】回到springboot項目,測試bean獲取

  顯然,是無法自動裝配的。報錯如下所示:

 

 【2】修改自定義組件,定義條件類

如下所示:

 

 然后,重新打包,回到springboot項目進行測試,如下所示:

 

 總結:

  使用配置文件實現條件裝配,更為簡單。不需要引入springboot的依賴包。

也不需要springboot項目是否導包來設置條件。

 


免責聲明!

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



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