2.走向自動裝配

2.走向自動裝配

Spring 模式註解裝配

2-1 走向自動裝配

  • 課程介紹java

    • spring framework手動裝配
    • spring boot自動裝配
    • spring boot自動裝配是以spring framework手動裝配爲基礎實現的

2-2 Spring Framework 手動裝配

[模式註解(Stereotype Annotations)]()

A *stereotype annotation is an annotation that is used to declare the role that a component plays within the application. For example, the @Repository annotation in the Spring Framework is a marker for any class that fulfills the role or stereotype* of a repository (also known as Data Access Object or DAO).

@Component is a generic stereotype for any Spring-managed component. Any component annotated with @Component is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with @Component is also a candidate for component scanning. For example, @Service is meta-annotated with @Component.web

  1. 模式註解是一種用於聲明在應用中扮演「組件」角色的註解。如 Spring Framework 中的 @Repository 標註在任何類上 ,用於扮演倉儲角色的模式註解。
  2. @Component 做爲一種由 Spring 容器託管的通用模式組件,任何被 @Component 標註的組件均爲組件掃描的候選對象。相似地,凡是被 @Component 元標註(meta-annotated)的註解,也即被@Component 註解標註過的註解,如 @Service ,且這兩個註解的簽名也一致時,當任何組件標註它時,也被視做組件掃描的候選對象 。

模式註解舉例

Spring Framework 註解 場景說明 起始版本
@Repository 數據倉儲模式註解 2.0
@Component 通用組件模式註解 2.5
@Service 服務模式註解 2.5
@Controller Web 控制器模式註解 2.5
@Configuration 配置類模式註解 3.0

裝配方式

<context:component-scan> 方式
<?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.xsd
        http://www.springframework.org/schema/context                 http://www.springframework.org/schema/context/springcontext.xsd">
<!-- 激活註解驅動特性 -->
        <context:annotation-config />
<!-- 找尋被 @Component 或者其派生 Annotation 標記的類(Class),將它們註冊爲 Spring Bean -->
        <context:component-scan base-package="com.imooc.dive.in.spring.boot" />
        </beans>
@ComponentScan 方式
@ComponentScan(basePackages = "com.imooc.dive.in.spring.boot")
public class SpringConfiguration {
...
}

2-3 Spring Framework手動裝配自定義模式註解

自定義模式註解

  • @firstLevelRepository註解「繼承」了@repository,同時@repository註解又「繼承」了@component,三個註解在註解關係上層層「繼承」,同時註解的簽名字段即註解屬性方法也一致,只有一個value字段,用於指定bean的name。同理,@SecondLevelRepository註解被@FirstLevelRepository註解標註也會有一樣的效果,體現了層次性。 「派生性」側重於從spring已有的註解生成新的註解,「層次性」側重於這些註解之間所謂的層層」繼承「關係。
@Component 「派生性」
  • FirstLevelRepository註解定義
package com.imooc.springbootautoconfigure.annotation;
import org.springframework.stereotype.Repository;
import java.lang.annotation.*;
/**
 * 一級 {@link Repository @Repository}
 *
 * @author gaorj
 * @since 2018/10/16
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repository
public @interface FirstLevelRepository {

    String value() default "";

}
@Component 「層次性」
  • @SecondLevelRepository註解定義
package com.imooc.springbootautoconfigure.annotation;

import java.lang.annotation.*;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@FirstLevelRepository
public @interface SecondLevelRepository {

    String value() default "";

}
  • 兩個自定義註解的使用
package com.imooc.springbootautoconfigure.repository;

import com.imooc.springbootautoconfigure.annotation.FirstLevelRepository;
import com.imooc.springbootautoconfigure.annotation.SecondLevelRepository;
import org.springframework.stereotype.Component;

/**
 *
 * @author gaorj
 * @since 2018/10/16
 */
//@FirstLevelRepository(value = "myFirstLevelRepository")//value指定bean的名稱
//@Component(value = "myFirstLevelRepository")//一樣的效果,註解的派生性
@SecondLevelRepository(value = "myFirstLevelRepository")//一樣的效果,註解的層次性
public class MyFirstLevelRepository {
}
  • Repository啓動類的使用,用於驗證自定義註解的使用。此處沒有使用默認的@SpringBootApplication註解,使用SpringApplicationBuilder初始化上下文
package com.imooc.springbootautoconfigure.bootstrap;

import com.imooc.springbootautoconfigure.repository.MyFirstLevelRepository;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * Repository啓動類
 * @author gaorj
 * @since 2018/10/16
 */
//掃描bean目錄
@ComponentScan(value ="com.imooc.springbootautoconfigure.repository")
public class RepositoryBootstrap {

    public static void main(String[] args) {
        //springboot上下文初始化
        ConfigurableApplicationContext context = new SpringApplicationBuilder(RepositoryBootstrap.class)
                //非web工程
                .web(WebApplicationType.NONE)
                .run(args);
        //myFirstLevelRepository bean是否存在
        MyFirstLevelRepository myFirstLevelRepository = context.getBean("myFirstLevelRepository", MyFirstLevelRepository.class);

        System.out.println("myFirstLevelRepository"+myFirstLevelRepository);


        //關閉上下文
        context.close();
    }
}

Spring @Enable 模塊裝配

  • Spring Framework 3.1 開始支持」@Enable 模塊驅動「。所謂「模塊」是指具有相同領域的功能組件集合, 組合所造成一個獨立的單元。所謂模塊是爲了實現一個功能所涉及到的各個組件的集合。好比 Web MVC 模塊、AspectJ代理模塊、Caching(緩存)模塊、JMX(Java 管 理擴展)模塊、Async(異步處理)模塊等。

2-4 @Enable 模塊裝配兩種方式

@Enable 註解模塊舉例

框架實現 @Enable 註解模塊 激活模塊
Spring Framework @EnableWebMvc Web MVC 模塊
@EnableTransactionManagement 事務管理模塊
@EnableCaching Caching 模塊
@EnableMBeanExport JMX 模塊
@EnableAsync 異步處理模塊
EnableWebFlux Web Flux 模塊
@EnableAspectJAutoProxy AspectJ 代理模塊
Spring Boot @EnableAutoConfiguration 自動裝配模塊
@EnableManagementContext Actuator 管理模塊
@EnableConfigurationProperties 配置屬性綁定模塊
@EnableOAuth2Sso OAuth2 單點登陸模塊
Spring Cloud @EnableEurekaServer Eureka服務器模塊
@EnableConfigServer 配置服務器模塊
@EnableFeignClients Feign客戶端模塊
@EnableZuulProxy 服務網關 Zuul 模塊
@EnableCircuitBreaker 服務熔斷模塊

實現方式

註解驅動方式
  • 經過引入@Import一個被@configuration模式註解的類來實現此模塊的功能,加載相應的bean。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
@Configuration
public class DelegatingWebMvcConfiguration extends
WebMvcConfigurationSupport {
...
}
接口編程方式
  • 引入@Import的類必須實現ImportSelector接口,此接口可以返回多個配置bean,根據註解配置的簽名信息決定引入bean,從而實現模塊裝配的多樣化。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
...
}
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
/**
* {@inheritDoc}
* @return {@link ProxyCachingConfiguration} or {@code
AspectJCacheConfiguration} for
* {@code PROXY} and {@code ASPECTJ} values of {@link
EnableCaching#mode()}, respectively
*/
public String[] selectImports(AdviceMode adviceMode) {
            switch (adviceMode) {
                case PROXY:
                return new String[] {                AutoProxyRegistrar.class.getName(),ProxyCachingConfiguration.class.getName() };
                case ASPECTJ:
            return new String[] {
        AnnotationConfigUtils.CACHE_ASPECT_CONFIGURATION_CLASS_NAME };
                default:
                return null;
            }
}
  • ImportSelector接口
package org.springframework.context.annotation;

import org.springframework.core.type.AnnotationMetadata;

/**
 * Interface to be implemented by types that determine which @{@link Configuration}
 * class(es) should be imported based on a given selection criteria, usually one or more
 * annotation attributes.
 *
 * <p>An {@link ImportSelector} may implement any of the following
 * {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
 * methods will be called prior to {@link #selectImports}:
 * <ul>
 * <li>{@link org.springframework.context.EnvironmentAware EnvironmentAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}</li>
 * <li>{@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}</li>
 * <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
 * </ul>
 *
 * <p>ImportSelectors are usually processed in the same way as regular {@code @Import}
 * annotations, however, it is also possible to defer selection of imports until all
 * {@code @Configuration} classes have been processed (see {@link DeferredImportSelector}
 * for details).
 *
 * @author Chris Beams
 * @since 3.1
 * @see DeferredImportSelector
 * @see Import
 * @see ImportBeanDefinitionRegistrar
 * @see Configuration
 */
public interface ImportSelector {

    /**
     * Select and return the names of which class(es) should be imported based on
     * the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);

}

自定義 @Enable 模塊

基於註解驅動實現 - @EnableHelloWorld
  • HelloWorldConfiguration -> HelloWorld
  • 自定義@EnableHelloWorld註解
package com.imooc.springbootautoconfigure.annotation;

import com.imooc.springbootautoconfigure.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
//@Import(HelloWorldConfiguration.class)//註解驅動實現方式
@Import(HelloWorldImportSelector.class)//接口編程實現方式
public @interface EnableHelloWorld {
}
  • 基於@Configuration註解import引入的配置類
package com.imooc.springbootautoconfigure.configuration;

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

/**
 * @author gaorj
 * @since 2018/10/16
 */
@Configuration
public class HelloWorldConfiguration {

    @Bean
    public String helloWorld() { // 方法名即 Bean 名稱
        return "Hello,World 2018";
    }
}
基於接口驅動實現 - @EnableServer
  • HelloWorldImportSelector -> HelloWorldConfiguration -> HelloWorld
  • 基於實現ImportSelector接口import引入的配置類
package com.imooc.springbootautoconfigure.annotation;

import com.imooc.springbootautoconfigure.configuration.HelloWorldConfiguration;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @author gaorj
 * @since 2018/10/16
 */
public class HelloWorldImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{HelloWorldConfiguration.class.getName()};
    }
}
  • 驗證@EnableHelloWorld啓動類
package com.imooc.springbootautoconfigure.bootstrap;

import com.imooc.springbootautoconfigure.annotation.EnableHelloWorld;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@EnableHelloWorld
public class EnableHelloWorldBootstrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableHelloWorldBootstrap.class)
                .web(WebApplicationType.NONE)
                .run(args);

        // helloWorld Bean 是否存在
        String helloWorld =
                context.getBean("helloWorld", String.class);

        System.out.println("helloWorld Bean : " + helloWorld);

        // 關閉上下文
        context.close();

    }
}
  • 兩種實現方式對比,接口驅動實現因爲中間通過ImportSelector轉換,此種方式相比註解驅動實現更有彈性,可以實現bean加載方式的多樣化,自定義實現多種返回值,相似於條件裝配。

Spring 條件裝配

2-5 Spring條件裝配

  • 從 Spring Framework 3.1 開始,容許在 Bean 裝配時增長前置條件判斷

條件註解舉例

Spring 註解 場景說明 起始版本
@Profile 配置化條件裝配 3.1
@Conditional 編程條件裝配 4.0
  • profile參數配置方式有侷限,conditional自定義編程更大的靈活性

實現方式

配置方式 - @Profile
  • @profile註解其實是經過@conditional註解實現的
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {

    /**
     * The set of profiles for which the annotated component should be registered.
     */
    String[] value();

}
編程方式 - @Conditional
  • @Conditional 註解的定義及其使用,必須配合Condition接口使用。
  • 判斷@Conditional註解的簽名類OnClassCondition.class是否在當前classpath下存在。
  • OnClassCondition.class這個類必須實現Condition接口,在此類中根據 @ConditionalOnClass註解的簽名信息做匹配判斷,是否裝配此bean。
/**
 * {@link Conditional} that only matches when the specified classes are on the classpath.
 *
 * @author Phillip Webb
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

    /**
     * The classes that must be present. Since this annotation is parsed by loading class
     * bytecode, it is safe to specify classes here that may ultimately not be on the
     * classpath, only if this annotation is directly on the affected component and
     * <b>not</b> if this annotation is used as a composed, meta-annotation. In order to
     * use this annotation as a meta-annotation, only use the {@link #name} attribute.
     * @return the classes that must be present
     */
    Class<?>[] value() default {};

    /**
     * The classes names that must be present.
     * @return the class names that must be present.
     */
    String[] name() default {};

}
  • @Conditional註解的定義
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

    /**
     * All {@link Condition}s that must {@linkplain Condition#matches match}
     * in order for the component to be registered.
     */
    Class<? extends Condition>[] value();

}
  • Condition接口
public interface Condition {

   /**
    * Determine if the condition matches.
    * @param context the condition context
    * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
    * or {@link org.springframework.core.type.MethodMetadata method} being checked.
    * @return {@code true} if the condition matches and the component can be registered
    * or {@code false} to veto registration.
    */
   boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

2-6 基於配置方式實現自定義條件裝配

自定義條件裝配

基於配置方式實現 - @Profile
  • 計算服務,多整數求和 :sum
  • @Profile("Java7") : for 循環
  • @Profile("Java8") : Lambda
  • CalculateService求和接口
package com.imooc.springbootautoconfigure.service;

/**
 * @author gaorj
 * @since 2018/10/16
 */
public interface CalculateService {

    /**
     * 從多個整數 sum 求和
     * @param values 多個整數
     * @return sum 累加值
     */
    Integer sum(Integer... values);

}
  • CalculateService求和接口java7實現
package com.imooc.springbootautoconfigure.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@Service
@Profile("Java7")
public class Java7CalculateService implements CalculateService {
    @Override
    public Integer sum(Integer... values) {
        System.out.println("Java 7 for 循環實現 ");
        int sum = 0;
        for (int i = 0; i < values.length; i++) {
            sum += values[i];
        }
        return sum;
    }

    public static void main(String[] args) {
        CalculateService calculateService = new Java7CalculateService();
        System.out.println(calculateService.sum(1,2,3,4,5,6,7,8,9,10));
    }
}
  • CalculateService求和接口java8實現
package com.imooc.springbootautoconfigure.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

import java.util.stream.Stream;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@Service
@Profile("Java8")//條件配置裝配
public class Java8CalculateService implements CalculateService{
    @Override
    public Integer sum(Integer... values) {
        System.out.println("Java 8 Lambda 實現");
        //Integer sum = Stream.of(values).reduce(0, (integer1, integer2) -> integer1 + integer2);
        int sum = Stream.of(values).reduce(0, Integer::sum);
        return sum;
    }

    public static void main(String[] args) {
        CalculateService calculateService = new Java8CalculateService();
        System.out.println(calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }
}
  • @Profile啓動類驗證
package com.imooc.springbootautoconfigure.bootstrap;

import com.imooc.springbootautoconfigure.service.CalculateService;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * @author gaorj
 * @since 2018/10/16
 */
@SpringBootApplication(scanBasePackages ="com.imooc.springbootautoconfigure.service")
public class CalculateServiceBootstrap {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder(CalculateServiceBootstrap.class).web(WebApplicationType.NONE)
                .profiles("Java7")
                .run(args);

        // CalculateService Bean 是否存在
        CalculateService calculateService = context.getBean(CalculateService.class);

        System.out.println("calculateService.sum(1...10) : " +
                calculateService.sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

        // 關閉上下文
        context.close();
    }
}

2-7 基於編程方式實現條件裝配

基於編程方式實現 - @ConditionalOnSystemProperty
相關文章
相關標籤/搜索