1. 程式人生 > 實用技巧 >Spring--IOC註解方式注入

Spring--IOC註解方式注入

Spring實現IOC注入的方式有xml註解兩種方式,異曲同工,這裡我們講述註解方式,因為這也是一種趨勢,主要優點實現簡便,程式碼可讀性強(個人理解)。
註解方式實現IOC注入,主要涉及以下幾個註解

  • @Configuration:新增該註解的類被視為上下文,裡面帶有@Bean註解的都將被注入到IOC容器
  • @ComponentScan:掃描註定包下的所有帶@Component的類,注入到IOC容器
  • @Bean:新增該註解的方法將返回一個例項,並被注入到IOC容器
  • @Component:新增該註解的方法自動被注入到IOC容器,需要被@ComponentScan掃描到
  • @Scope:指定例項的作用域
  • @PostConstruct:新增該註解的方法在例項初始化的時候被執行
  • @PreDestory:新增該註解的方法在例項銷燬的時候被執行

註解方式實現的一個簡單的案例

第一步:@Configuration配置一個上下文環境,並在裡面使用@Bean註解返回需要注入的物件,指定beanId

@Configuration
public class BeanConfiguration {

    //將一個bean交由spring建立並管理
    @Bean(value = "bean1")
    public Bean1 getBean1() {
        return new Bean1();
    }
}

第二部:根據beanId獲取需要的例項物件

public class IoCAnnotationTest {

    @Test
    public void test() {
        //獲取Spring上下文
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(BeanConfiguration.class);
        //獲取bean
//        Bean1 bean1 = (Bean1) context.getBean("getBean1");//beanId預設為方法名
        Bean1 bean1 = (Bean1) context.getBean("bean1");//通過@Bean value屬性設定beanId
        System.out.println(bean1);
    }
}

如果有很多bean要注入,那麼要寫很多@Bean註解嗎,這裡有更方便的寫法

//建立一個class配置檔案
@Configuration
//掃描指定包下的所有帶@Component的bean,交由Spring管理
@ComponentScan(value = "com.kingja.iocannotation.bean")
public class BeanScanConfiguration {

}

在需要注入的類上新增@Component註解,並設定beanId

//通過value設定beanId
@Component(value = "bean2")
//預設beanId是類名(首字母小寫bean2)
public class Bean2 {
}

注入Bean的幾種方式

通過方法注入Bean

  • 構造方法注入Bean
  • setter方法注入Bean
通過構造方法注入
@Component
public class OutterBean {
    private InnerBean innerBean;
    @Autowired
    public OutterBean(InnerBean innerBean) {
        this.innerBean = innerBean;
    }
}
通過setter方法注入
@Autowired
public void setInnerBean2(InnerBean innerBean2) {
    this.innerBean2 = innerBean2;
}
通過屬性注入
@Component
public class OutterBean {
    @Autowired
    private InnerBean innerBean3;
注入集合
/**
 * 1.尋找所有統一泛型的集合,方法名符合引數名的集合進行注入
 * 2.尋找所有型別為泛型型別,組合成集合然後進行注入
 * 3.如果需要精確注入,則用@Qualifier("myStringList")進行制定beanId
 * 方式2優先於方式1
 * 將會在容器裡尋找List<String>的集合自動注入,引數名stringList對應@Bean註解的的方法名
 */
@Autowired
@Qualifier("myStringList")
public void setStringList(List<String> stringList) {
    this.stringList = stringList;
}
@Configuration
//掃描指定包下的所有帶@Component的bean,交由Spring管理
@ComponentScan(value = "com.kingja.iocannotation.bean")
public class BeanScanConfiguration {
@Bean("myStringList")
public List<String> stringList3() {
    List<String> list = new ArrayList<>();
    list.add("ooo");
    list.add("kkk");
    return list;
}
注入普通欄位
@Component
public class OutterBean {
    private String stringValue;
    private Integer intValue;

    @Value("666")
    public void setStringValue(String stringValue) {
        this.stringValue = stringValue;
    }

    /**
     * @Value將字串直接轉成整型進行注入
     */
    @Value("999")
    public void setIntValue(Integer intValue) {
        this.intValue = intValue;
    }
注入直接可用的介面
@Component
public class OutterBean {
    private ApplicationContext applicationContext;
    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

IOC的作用域

實際需求中,我們可能需要每次返回的物件是同一個,也可能每次建立不同的例項物件,也可能根據不同的場景,比如不同請求,不同會話等返回不同例項,這就涉及到Bean的作用域。
Bean的作用域主要包含以下幾種:

  • singleton:單例模式,建立的例項是同一個
  • prototype:每次建立不同的例項
  • request:每次請求建立不同的例項
  • session:每個回話週期內建立同一個例項
  • application:伺服器執行週期內建立內一個例項
  • websocket:每次websocket連線建立同一個例項
    通過測試我們來檢驗singleton和prototype兩種作用域
@Configuration
public class BeanScopeConfiguration {

    @Bean(value = "defaultBean")
    public Bean1 getDefaultBean() {
        return new Bean1();
    }
    @Bean(value = "singletonBean")
    @Scope(value = "singleton")
    public Bean1 getSingletonBean() {
        return new Bean1();
    }
    @Bean(value = "prototypeBean")
    @Scope(value = "prototype")
    public Bean1 getPrototypeBean() {
        return new Bean1();
    }
}
@Test
public void testScope() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(BeanScopeConfiguration.class);
    Bean1 defaultBean1 = (Bean1) context.getBean("defaultBean");
    Bean1 defaultBean2 = (Bean1) context.getBean("defaultBean");
    System.out.println("defaultBean1 : "+defaultBean1);
    System.out.println("defaultBean2 : "+defaultBean2);
    Bean1 singletonBean1 = (Bean1) context.getBean("singletonBean");
    Bean1 singletonBean2 = (Bean1) context.getBean("singletonBean");
    System.out.println("singletonBean1 : "+singletonBean1);
    System.out.println("singletonBean1 : "+singletonBean2);
    Bean1 prototypeBean1 = (Bean1) context.getBean("prototypeBean");
    Bean1 prototypeBean2 = (Bean1) context.getBean("prototypeBean");
    System.out.println("prototypeBean1 : "+prototypeBean1);
    System.out.println("prototypeBean2 : "+prototypeBean2);
}

defaultBean1 : com.kingja.iocannotation.bean.Bean1@70cf32e3
defaultBean2 : com.kingja.iocannotation.bean.Bean1@70cf32e3
singletonBean1 : com.kingja.iocannotation.bean.Bean1@5a59ca5e
singletonBean1 : com.kingja.iocannotation.bean.Bean1@5a59ca5e
prototypeBean1 : com.kingja.iocannotation.bean.Bean1@4d1bf319
prototypeBean2 : com.kingja.iocannotation.bean.Bean1@6f53b8a

根據日誌可以看出,預設scope為singleton,prototype就是每次呼叫都是建立新的例項,但是預設單例建立的例項和新增單例註解建立的例項不是一樣的。

@Lazy 懶載入

預設建立例項是在獲取上下文的同時進行建立,但是特殊要求也可以滿足在需要Bean的時候才建立,這就涉及到Bean的懶載入
@Lazy可以和以下幾種註解進行配合使用:

  • @Configuration: 當前上下文下的所有例項
  • @Bean:當前例項
  • @Component:當前元件(例項)
@Configuration
//@Lazy //全域性配置懶載入
public class BeanLazyConfiguration {

    @Bean(value = "lazyBean")
    @Lazy
    public Bean1 getLazyBean() {
        return new Bean1();
    }
}
public class Bean1 {
    public Bean1() {
        System.out.println("Bean1 init");
    }
}
@Test
public void testLazy() {
    AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(BeanLazyConfiguration.class);
    System.out.println("context init");
    Bean1 bean = (Bean1) context.getBean("lazyBean");
    System.out.println(bean);
}

看下輸出日誌

context init
Bean1 init
com.kingja.iocannotation.bean.Bean1@3e08ff24

在看下去掉懶載入後的日誌輸出

    @Bean(value = "lazyBean")
//    @Lazy
    public Bean1 getLazyBean() {
        return new Bean1();
    }
Bean1 init
context init
com.kingja.iocannotation.bean.Bean1@7c137fd5

可以看出去掉懶載入後也就是預設模式是在初始化上下文的時候呀直接建立了例項物件

bean的初始化和銷燬

實際場景中,我們需要監聽IOC容器中,Bean的生命週期,有三種方法可以實現。
方法一:實現InitializingBean, DisposableBean介面

@Component
public class LifecycleBean implements InitializingBean, DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet");
    }
}
afterPropertiesSet
com.kingja.iocannotation.bean.LifecycleBean@183ec003

我們看到,銷燬的方法並沒有執行,因為上下文沒有銷燬,我們嘗試把當前的上下文關閉,把
AnnotationConfigApplicationContext 換成AbstractApplicationContext,並呼叫close()。


    @Test
    public void testLifecycle() {
//        AnnotationConfigApplicationContext context =new AnnotationConfigApplicationContext(CommonBeanConfiguration.class);
        AbstractApplicationContext context =new AnnotationConfigApplicationContext(CommonBeanConfiguration.class);
        LifecycleBean bean = (LifecycleBean) context.getBean("lifecycleBean");
        System.out.println(bean);
        context.close();
    }
afterPropertiesSet
com.kingja.iocannotation.bean.LifecycleBean@183ec003
destroy

這樣,銷燬方法也呼叫了,可見Bean的銷燬是由上下文的銷燬觸發的。
方法二:Bean類上設定@PostConstruct和@PreDestory註解,自定義初始化和銷燬方法

@PostConstruct
public void onInit() throws Exception {
    System.out.println("onInit");
}
@PreDestroy
public void onDestory() throws Exception {
    System.out.println("onDestory");
}
onInit
afterPropertiesSet
com.kingja.iocannotation.bean.LifecycleBean@1fe20588
onDestory
destroy

同樣,自定義的方法也正常執行了
方法二:
設定@Bean的initMethod和destroyMethod屬性

@Bean(value = "lifecycleBean" ,initMethod = "onInitByAnnotation",destroyMethod = "onDestoryByAnnotation")
public LifecycleBean getLifecycleBean() {
    return new LifecycleBean();
}
onInit
afterPropertiesSet
onInitByAnnotation
com.kingja.iocannotation.bean.LifecycleBean@6973bf95
onDestory
destroy
onDestoryByAnnotation

同樣也正常執行了
總結

以上內容包含了IOC容器註解方式注入的大部分功能,這是基於Spring方式,如果是Spring Boot方式,還有更加簡單,請聽下回分解。