Spring學習(一)

@Bean、@Configuration、@Component、 @Service、 @Repository 和 @Controller註解

首先聲明兩點:前端

一、除了@Bean,其餘註解都被註解@Component修飾了,因此是基於@Component的擴展,只不過不一樣的業務場景做用不同,共同點:均可以用來注入IOC容器生成Bean對象,當使用基於註解的配置和類路徑掃描的時候,這些類就會被實例化、被掃描到IOC容器中。java

二、這些註解都是用來注入IOC容器,生成Bean對象的(還須要配置自動組件掃描才能夠哦,即@ComponentScan),只不過@Bean比較特殊,須要寫在方法上,被@Bean修飾的方法的返回值用來生成Bean對象,id默認爲方法名,方法的返回值能夠是接口、實現類,生成的bean是方法中具體返回的實現類。不過有一點要注意,@Bean修飾的方法所在的類必須是IOC容器中的Bean對象,即所在的類必須被@Configuration、@Component等註解修飾,@Bean一般配合@Configuration使用。web

 

1、@Component@Service@Controller@Repository註解

引用spring的官方文檔中的一段描述:spring

在Spring2.0以前的版本中,@Repository註解能夠標記在任何的類上,用來代表該類是用來執行與數據庫相關的操做(即dao對象),並支持自動處理數據庫操做產生的異常數據庫

在Spring2.5版本中,引入了更多的Spring類註解:@Component,@Service,@Controller@Component是一個通用的Spring容器管理的單例bean組件。而@Repository@Service@Controller就是針對不一樣的使用場景所採起的特定功能化的註解組件。spring-mvc

所以,當你的一個類被@Component所註解,那麼就意味着一樣能夠用@Repository@Service@Controller來替代它,同時這些註解會具有有更多的功能,並且功能各異。mvc

最後,若是你不知道要在項目的業務層採用@Service仍是@Component註解。那麼,@Service是一個更好的選擇。app

就如上文所說的,@Repository早已被支持了在你的持久層做爲一個標記能夠去自動處理數據庫操做產生的異常(譯者注:由於原生的java操做數據庫所產生的異常只定義了幾種,可是產生數據庫異常的緣由卻有不少種,這樣對於數據庫操做的報錯排查形成了必定的影響;而Spring拓展了原生的持久層異常,針對不一樣的產生緣由有了更多的異常進行描述。因此,在註解了@Repository的類上若是數據庫操做中拋出了異常,就能對其進行處理,轉而拋出的是翻譯後的spring專屬數據庫異常,方便咱們對異常進行排查處理)。框架

註解 含義
@Component 最普通的組件,能夠被注入到spring容器進行管理
@Repository 做用於持久層
@Service 做用於業務邏輯層
@Controller 做用於表現層(spring-mvc的註解)

這幾個註解幾乎能夠說是同樣的:由於被這些註解修飾的類就會被Spring掃描到並注入到Spring的bean容器中。less

這裏,有兩個註解是不能被其餘註解所互換的:

@Controller 註解的bean會被spring-mvc框架所使用。 
@Repository 會被做爲持久層操做(數據庫)的bean來使用 
若是想使用自定義的組件註解,那麼只要在你定義的新註解中加上@Component便可:

@Component 
@Scope("prototype")
public @interface ScheduleJob {...}

這樣,全部被@ScheduleJob註解的類就均可以注入到spring容器來進行管理。咱們所須要作的,就是寫一些新的代碼來處理這個自定義註解(譯者注:能夠用反射的方法),進而執行咱們想要執行的工做。

@Component就是跟<bean>同樣,能夠託管到Spring容器進行管理。

@Service@Controller , @Repository = {@Component + 一些特定的功能}。這個就意味着這些註解在部分功能上是同樣的。

固然,下面三個註解被用於爲咱們的應用進行分層:

@Controller註解類進行前端請求的處理,轉發,重定向。包括調用Service層的方法 
@Service註解類處理業務邏輯 
@Repository註解類做爲DAO對象(數據訪問對象,Data Access Objects),這些類能夠直接對數據庫進行操做 
有這些分層操做的話,代碼之間就實現了鬆耦合,代碼之間的調用也清晰明朗,便於項目的管理;假想一下,若是隻用@Controller註解,那麼全部的請求轉發,業務處理,數據庫操做代碼都糅合在一個地方,那這樣的代碼該有多難拓展和維護。

這段總結:

@Component@Service@Controller@Repository是spring註解,註解後能夠被spring框架所掃描並注入到spring容器來進行管理 
@Component是通用註解,其餘三個註解是這個註解的拓展,而且具備了特定的功能 
@Repository註解在持久層中,具備將數據庫操做拋出的原生異常翻譯轉化爲spring的持久層異常的功能。 
@Controller層是spring-mvc的註解,具備將請求進行轉發,重定向的功能。 
@Service層是業務邏輯層註解,這個註解只是標註該類處於業務邏輯層。 
用這些註解對應用進行分層以後,就能將請求處理,義務邏輯處理,數據庫操做處理分離出來,爲代碼解耦,也方便了之後項目的維護和開發。

 

2、@Component與@Bean的區別

首先咱們看看這兩個註解的做用:

  • @Component註解代表一個類會做爲組件類,並告知Spring要爲這個類建立bean。
  • @Bean註解告訴Spring這個方法將會返回一個對象,這個對象要註冊爲Spring應用上下文中的bean。一般方法體中包含了最終產生bean實例的邏輯。

二者的目的是同樣的,都是註冊bean到Spring容器中。

@Component(@Controller、@Service、@Repository)一般是經過類路徑掃描來自動偵測以及自動裝配到Spring容器中。

而@Bean註解一般是咱們在標有該註解的方法中定義產生這個bean的邏輯。

舉個栗子:

@Controller

//在這裏用Component,Controller,Service,Repository均可以起到相同的做用。

@RequestMapping(″/web/controller1″)
public class WebController {
    .....
}

而@Bean的用途則更加靈活

當咱們引用第三方庫中的類須要裝配到Spring容器時,則只能經過@Bean來實現

舉個例子:

public class WireThirdLibClass {
    @Bean
    public ThirdLibClass getThirdLibClass() {
        return new ThirdLibClass();
    }
}

再舉個只能用@Bean的例子:

@Bean
public OneService getService(status) {
    case (status)  {
        when 1:
                return new serviceImpl1();
        when 2:
                return new serviceImpl2();
        when 3:
                return new serviceImpl3();
    }
}

以上這個例子是沒法用Component以及其具體實現註解(Controller、Service、Repository)來實現的。

這段總結:@Component和@Bean都是用來註冊Bean並裝配到Spring容器中,可是Bean比Component的自定義性更強。能夠實現一些Component實現不了的自定義加載類。

 

3、@Configuration 和 @Component 區別

 一句話歸納就是 @Configuration 中全部帶 @Bean 註解的方法都會被動態代理,所以調用該方法返回的都是同一個實例。(不清楚往下面看)

@Configuration 註解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

 從定義來看,@Configuration註解本質上仍是 @Component,所以 <context:component-scan/> 或者 @ComponentScan 都能處理@Configuration 註解的類。

@Configuration 標記的類必須符合下面的要求:

  • 配置類必須以類的形式提供(不能是工廠方法返回的實例),容許經過生成子類在運行時加強(cglib 動態代理)。
  • 配置類不能是 final 類(無法動態代理)。
  • 配置註解一般爲了經過 @Bean 註解生成 Spring 容器管理的類,
  • 配置類必須是非本地的(即不能在方法中聲明,不能是 private)。
  • 任何嵌套配置類都必須聲明爲static
  • @Bean 方法可能不會反過來建立進一步的配置類(也就是返回的 bean 若是帶有 @Configuration,也不會被特殊處理,只會做爲普通的 bean)。

加載過程:

Spring 容器在啓動時,會加載默認的一些 PostPRocessor,其中就有 ConfigurationClassPostProcessor,這個後置處理程序專門處理帶有 @Configuration 註解的類,這個程序會在 bean 定義加載完成後,在 bean 初始化前進行處理。主要處理的過程就是使用 cglib 動態代理加強類,並且是對其中帶有 @Bean 註解的方法進行處理。 

在 ConfigurationClassPostProcessor 中的 postProcessBeanFactory 方法中調用了下面的方法:

/**
 * Post-processes a BeanFactory in search of Configuration class BeanDefinitions;
 * any candidates are then enhanced by a {@link ConfigurationClassEnhancer}.
 * Candidate status is determined by BeanDefinition attribute metadata.
 * @see ConfigurationClassEnhancer
 */
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
    Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
    for (String beanName : beanFactory.getBeanDefinitionNames()) {
        BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
        if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
            //省略部分代碼
            configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
        }
    }
    if (configBeanDefs.isEmpty()) {
        // nothing to enhance -> return immediately
        return;
    }
    ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
    for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
        AbstractBeanDefinition beanDef = entry.getValue();
        // If a @Configuration class gets proxied, always proxy the target class
        beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
        try {
            // Set enhanced subclass of the user-specified bean class
            Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
            Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
            if (configClass != enhancedClass) {
                //省略部分代碼
                beanDef.setBeanClass(enhancedClass);
            }
        }
        catch (Throwable ex) {
            throw new IllegalStateException(
              "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
        }
    }
}

 

 在方法的第一次循環中,查找到全部帶有 @Configuration 註解的 bean 定義,而後在第二個 for 循環中,經過下面的方法對類進行加強:

Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);

 

 而後使用加強後的類替換了原有的 beanClass

beanDef.setBeanClass(enhancedClass);

因此到此時,全部帶有 @Configuration 註解的 bean 都已經變成了加強的類。

下面關注上面的 enhance 加強方法,多跟一步就能看到下面的方法:

/**
 * Creates a new CGLIB {@link Enhancer} instance.
 */
private Enhancer newEnhancer(Class<?> superclass, ClassLoader classLoader) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(superclass);
    enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
    enhancer.setUseFactory(false);
    enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
    enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
    enhancer.setCallbackFilter(CALLBACK_FILTER);
    enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
    return enhancer;
}

經過 cglib 代理的類在調用方法時,會經過 CallbackFilter 調用,這裏的 CALLBACK_FILTER 以下:

// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
        new BeanMethodInterceptor(),
        new BeanFactoryAwareMethodInterceptor(),
        NoOp.INSTANCE
};
 
private static final ConditionalCallbackFilter CALLBACK_FILTER = 
        new ConditionalCallbackFilter(CALLBACKS);

 其中 BeanMethodInterceptor 匹配方法以下:

@Override
public boolean isMatch(Method candidateMethod) {
    return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
}
 
//BeanAnnotationHelper
public static boolean isBeanAnnotated(Method method) {
    return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}

也就是當方法有 @Bean 註解的時候,就會執行這個回調方法。

另外一個  BeanFactoryAwareMethodInterceptor 匹配的方法以下:
@Override
public boolean isMatch(Method candidateMethod) {
    return (candidateMethod.getName().equals("setBeanFactory") &&
            candidateMethod.getParameterTypes().length == 1 &&
            BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
            BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}

 當前類還須要實現 BeanFactoryAware 接口,上面的 isMatch 就是匹配的這個接口的方法。

@Bean 註解方法執行策略:

先給一個簡單的示例代碼:

@Configuration
public class MyBeanConfig {
 
    @Bean
    public Country country(){
        return new Country();
    }
 
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
 
}
相信大多數人第一次看到上面 userInfo() 中調用 country() 時,會認爲這裏的 Country 和上面 @Bean 方法返回的 Country可能不是同一個對象,所以可能會經過下面的方式來替代這種方式:

@Autowired 
private Country country;

實際上不須要這麼作(後面會給出須要這樣作的場景),直接調用 country() 方法返回的是同一個實例。

下面看調用 country() 和 userInfo() 方法時的邏輯。

 


如今咱們已經知道  @Configuration 註解的類是如何被處理的了,如今關注上面的  BeanMethodInterceptor,看看帶有  @Bean 註解的方法執行的邏輯。下面分解來看  intercept 方法。
//首先經過反射從加強的 Configuration 註解類中獲取 beanFactory
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
 
//而後經過方法獲取 beanName,默認爲方法名,能夠經過 @Bean 註解指定
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
 
//肯定這個 bean 是否指定了代理的範圍
//默認下面 if 條件 false 不會執行
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
    String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
    if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
        beanName = scopedBeanName;
    }
}
 
//中間跳過一段 Factorybean 相關代碼
 
//判斷當前執行的方法是否爲正在執行的 @Bean 方法
//由於存在在 userInfo() 方法中調用 country() 方法
//若是 country() 也有 @Bean 註解,那麼這個返回值就是 false.
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
    // 判斷返回值類型,若是是 BeanFactoryPostProcessor 就寫警告日誌
    if (logger.isWarnEnabled() &&
            BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
        logger.warn(String.format(
            "@Bean method %s.%s is non-static and returns an object " +
            "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
            "result in a failure to process annotations such as @Autowired, " +
            "@Resource and @PostConstruct within the method's declaring " +
            "@Configuration class. Add the 'static' modifier to this method to avoid " +
            "these container lifecycle issues; see @Bean javadoc for complete details.",
            beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
    }
    //直接調用原方法建立 bean
    return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//若是不知足上面 if,也就是在 userInfo() 中調用的 country() 方法
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);

 

關於 isCurrentlyInvokedFactoryMethod 方法
能夠參考 SimpleInstantiationStrategy 中的 instantiate 方法,這裏先設置的調用方法:

currentlyInvokedFactoryMethod.set(factoryMethod);
return factoryMethod.invoke(factoryBean, args);
而經過方法內部直接調用 country() 方法時,不走上面的邏輯,直接進的代理方法,也就是當前的 intercept方法,所以當前的工廠方法和執行的方法就不相同了。

 

obtainBeanInstanceFromFactory 方法比較簡單,就是經過 beanFactory.getBean 獲取 Country,若是已經建立了就會直接返回,若是沒有執行過,就會經過 invokeSuper 首次執行。

所以咱們在 @Configuration 註解定義的 bean 方法中能夠直接調用方法,不須要 @Autowired 注入後使用。

@Component 注意:

@Component 註解並無經過 cglib 來代理@Bean 方法的調用,所以像下面這樣配置時,就是兩個不一樣的 country。

@Component
public class MyBeanConfig {
 
    @Bean
    public Country country(){
        return new Country();
    }
 
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
 
}

 

有些特殊狀況下,咱們不但願 MyBeanConfig 被代理(代理後會變成WebMvcConfig$$EnhancerBySpringCGLIB$$8bef3235293)時,就得用 @Component,這種狀況下,上面的寫法就須要改爲下面這樣:

@Component
public class MyBeanConfig {
 
    @Autowired
    private Country country;
 
    @Bean
    public Country country(){
        return new Country();
    }
 
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country);
    }
 
}

這種方式能夠保證使用的同一個 Country 實例。

 

轉載:https://blog.csdn.net/fansili/article/details/78740877

https://www.jianshu.com/p/3fbfbb843b63

https://blog.csdn.net/aa1358075776/article/details/81022306

相關文章
相關標籤/搜索