天天看點

Spring IOC 容器注解使用

1、Spring 容器繼承圖

Spring IOC 容器注解使用

 2、控制反轉和依賴注入

  1. 什麼是控制反轉?我覺得有必要先了解軟體設計的一個重要思想:依賴倒置原則(Dependency Inversion Principle )
  2. 什麼是依賴倒置原則?假設我們設計一輛汽車:先設計輪子,然後根據輪子大小設計底盤,接着根據底盤設計車身,最後根據車身設計好整個汽車。這裡就出現了一個“依賴”關系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。
Spring IOC 容器注解使用

從上圖來看好像沒什麼問題,但萬一輪胎尺寸改了,那麼地盤需要改,地盤改了,車身也改了,讓後整個汽車構造都改了,牽一發而動全身。但如果反過來假如汽車公司決定修改輪胎的 我們就隻需要改動輪子的設計,而不需要動底盤,車身,汽車 的設計了。

Spring IOC 容器注解使用

IOC容器的核心思想在于資源不由使用資源的雙方管理,而由不使用資源的第三方管理,這可以帶來很多好處。第 一,資源集中管理,實作資源的可配置和易管理。第二,降低了使用資源雙方的依賴程度,也就是我們說的耦合度。舉個例子,假如我們需要找個房子,我們需要租個房子,我們可以自己去找房源(但我們需要依賴房源等因素),也可以交由中介進行管理(這樣子我們就是不直接依賴房源了,這個交給中介管理)。

Spring IOC 容器注解使用

3、Spring IOC 容器注解使用

1、我們很多時候通過基于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.xsd">

    <bean id="user" class="com.cym.bean.compent.User"></bean>
</beans>
           

讀取容器中的bean的方式

public class BeanMain {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
        Object user = classPathXmlApplicationContext.getBean("user");
        System.out.println(user);
//        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//        Object user1 = annotationConfigApplicationContext.getBean("person");
//        System.out.println(user1);
    }
}
           

2、基于讀取配置類的形式定義Bean資訊 

@Configuration
public class BeanConfig {

    @Bean(name = "person")
    public User user(){
        return new User();
    }
}
           

注意 : 通過 @Bean 的形式是使用的話, bean 的預設名稱是方法名,若 @Bean(value="bean 的名稱 ") 那麼 bean 的名稱是指定的 去容器中讀取 Bean 的資訊( 傳入配置類 )

public class BeanMain {

    public static void main(String[] args) {
//        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
//        Object user = classPathXmlApplicationContext.getBean("user");
//        System.out.println(user);
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("person");
        System.out.println(user1);
    }
}
           

3、在配置類上寫 @CompentScan 注解來進行包掃描

@Configuration
@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           

從容器中擷取bean

public class UserMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext acx = new AnnotationConfigApplicationContext(UserConfig.class);
        String[] beans = acx.getBeanDefinitionNames();
        for (String bean :beans){
            System.out.println("bean定義的資訊:"+bean);
        }
    }
}
           
Spring IOC 容器注解使用

排除用法 excludeFilters( 排除 @Controller 注解的 , 和UserService的bean )

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
@ComponentScan(basePackages = {"com.cym.componentscan"},
        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           
Spring IOC 容器注解使用

包含用法 includeFilters , 注意,若使用包含的用法, 需要把 useDefaultFilters 屬性設定為 false ( true 表 示掃描全部的)

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
@ComponentScan(basePackages = {"com.cym.componentscan"},
        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {

}
           
Spring IOC 容器注解使用

@ComponentScan.Filter type 的類型

注解形式的 FilterType.ANNOTATION @Controller @Service @Repository @Compent
指定類型的 FilterType.ASSIGNABLE_TYPE
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
           
aspectj 類型的 FilterType.ASPECTJ( 不常用 )
正規表達式的 FilterType.REGEX(不常用 )
自定義的 FilterType.CUSTOM
package org.springframework.context.annotation;

public enum FilterType {
    //注解形式 比如@Controller @Service @Repository @Compent
    ANNOTATION,

    //指定的類型
    ASSIGNABLE_TYPE,
    
    //aspectJ形式的
    ASPECTJ,
    
    //正規表達式的
    REGEX,

    //自定義的
    CUSTOM;

    private FilterType() {
    }
}
           

FilterType.CUSTOM 自定義類型如何使用

public class MyFilterType implements TypeFilter {

    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //擷取目前類的注解源資訊
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //擷取目前類的class的源資訊
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //擷取目前類的資源資訊
        Resource resource = metadataReader.getResource();
        //掃描UserController、UserService
        if (classMetadata.getClassName().contains("UserController") || classMetadata.getClassName().contains("UserService")) {
            return true;
        }
        return false;
    }
}
           

配置類

@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        excludeFilters = {@ComponentScan.Filter(type =  FilterType.ANNOTATION, value = {Controller.class}),
//                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
//        includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
@ComponentScan(basePackages = {"com.cym.componentscan"},
        includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = MyFilterType.class)}, useDefaultFilters = false)
public class UserConfig {

}
           

4、配置 Bean 的作用域對象   在不指定 @Scope 的情況下,所有的 bean 都是單執行個體的 bean, 而且是餓漢加載 ( 容器啟動執行個體就建立 好了 )

@Configuration
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}
           
@Setter
@Getter
public class User {

    public User() {
        System.out.println("在不指定@Scope的情況下,所有的bean都是單執行個體的bean,而且是餓漢加載(容器啟動執行個體就建立好了)");
    }

    private String userName;

    private String age;

    private String gender;

    private String phone;

    private String address;

    private Date birthday;

}
           

擷取容器中的bean

Spring IOC 容器注解使用

指定 @Scope 為 prototype 表示為多執行個體的,而且還是懶漢模式加載( IOC 容器啟動的時候,并不會建立對象,而是 在第一次使用的時候才會建立)

@Configuration
public class BeanConfig {

    @Bean
    @Scope(value = "prototype")
    public User user(){
        return new User();
    }
}
           
Spring IOC 容器注解使用

@Scope 指定的作用域方法取值  

singleton 單執行個體的 ( 預設 )
prototype 多執行個體的
request 同一次請求
session 同一個會話級别

5、Bean的懶加載@Lazy(主要針對單執行個體的bean 容器啟動的時候,不建立對象,在第一次使用的時候才會建立該對象)

@Configuration
public class BeanConfig {

    @Bean
    @Lazy
    public User user(){
        return new User();
    }
}
           

6、@Conditional進行條件判斷等(可以用于自動裝配)

場景,有二個元件Parents 和Childrents ,我的Childrents元件是依賴于Parents的元件

應用: 自己建立一個My Condition 的類 實作 Condition接口

public class MyCondition implements Condition {

    @Override
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        //判斷容器中是否有Parent的元件
        if (conditionContext.getBeanFactory().containsBean("parents")){
            return true;
        }
        return false;
    }
}
           

配置類

@Configuration
public class BeanConfig {

    @Bean
    public Parents parents() {
        return new Parents();
    }

    @Bean
    @Conditional(value = MyCondition.class)
    public Childrens childrens() {
        return new Childrens();
    }
}
           

7、往IOC 容器中添加元件的方式

通過 @CompentScan [email protected] @Service @Respository @compent,适用場景 : 針對我們自己寫的元件可以通過該方式來進行加載到容器中。
通過 @Bean 的方式來導入元件 ( 适用于導入第三方元件的類 )
通過 @Import 來導入元件 (導入元件的 id為全類名路徑),通過 @Import 的 ImportSeletor 類實作元件的導入 ( 導入元件的 id 為全類名路徑 )
通過實作 FacotryBean 接口來實作注冊 元件

通過@Import 的ImportSeletor類實作元件的導入 (導入元件的id為全類名路徑,如:com.cym.Import.compent.Parents)

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.cym.Import.compent.Dog"};
    }
}
           

 通過@Import的 ImportBeanDefinitionRegister導入元件 (可以指定bean的名稱)

public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata,  BeanDefinitionRegistry registry) {
        //建立一個bean定義對象
        RootBeanDefinition parents = new RootBeanDefinition(Parents.class);
        RootBeanDefinition childrens = new RootBeanDefinition(Childrens.class);
        RootBeanDefinition dog = new RootBeanDefinition(Dog.class);
        //把bean定義對象導入到容器中
        registry.registerBeanDefinition("parents",parents);
        registry.registerBeanDefinition("childrens",childrens);
        registry.registerBeanDefinition("dog",dog);
    }
}
           

 配置類

@Configuration
//@Import({Parents.class,Childrens.class, MyImportSelector.class})
@Import(MyBeanDefinitionRegister.class)
public class BeanConfig {

}
           
Spring IOC 容器注解使用

通過實作 FacotryBean 接口來實作注冊 元件

public class MyrFactoryBean implements FactoryBean<User> {
    //傳回bean對象
    @Override
    public User getObject() throws Exception {
        return new User();
    }
    //傳回bean的類型
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

    //是否為單例
    @Override
    public boolean isSingleton() {
        return false;
    }
           

配置類

@Configuration
public class BeanConfig {
    
    //注冊我們自己factoryBean
    @Bean
    public MyrFactoryBean myrFactoryBean(){
        return new MyrFactoryBean();
    }
}
           
Spring IOC 容器注解使用

7、Bean的初始化方法和銷毀方法

  • 什麼是bean的生命周期?bean的建立----->初始化----->銷毀方法由容器管理Bean的生命周期,我們可以通過自己指定bean的初始化方法和bean的銷毀方法。
public class User {

    public User() {
        System.out.println("我是構造器");
    }

    public void init(){
        System.out.println("我是bean的生命周期的初始化方法init");
    }

    public void destroy(){
        System.out.println("我是bean的生命周期的銷毀方法destroy");
    }
}


@Configuration
public class BeanConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public User user() {
        return new User();
    }
}


public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("user");
        System.out.println(user1);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用

針對單執行個體bean的話,容器啟動的時候,bean的對象就建立了,而且容器銷毀的時候,也會調用Bean的銷毀方法。針對多執行個體bean的話,容器啟動的時候,bean是不會被建立的而是在擷取bean的時候被建立,而且bean的銷毀不受 IOC容器的管理  

  • 通過 InitializingBean和DisposableBean 的二個接口實作bean的初始化以及銷毀方法
/**
 * @description:
 * @author: 通過 InitializingBean和DisposableBean 的二個接口實作bean的初始化以及銷毀方法
 * @date: 2020-04-12 12:27
 */
public class Person implements InitializingBean, DisposableBean {

    public Person() {
        System.out.println("我是Person構造器");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean的destroy()方法 ");
    }

    /**
     * 內建了InitializingBean這個類的在bean建立完成後會自動調用該該方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean的 afterPropertiesSet方法");
    }
}



@Configuration
public class BeanConfig {


    @Bean
    public Person person(){
        return new Person();
    }
}


/**
 * @description: 基于讀取配置類的形式定義Bean資訊
 * @author: 
 * @date: 2020-04-11 19:45
 */
public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("person");
        System.out.println(user1);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用
  • 通過JSR250規範 提供的注解@PostConstruct 和@ProDestory标注的方法
@Component
public class Dog {

    public Dog() {
        System.out.println("Dog構造方法");
    }
    @PostConstruct
    public void init() {
        System.out.println("Dog 的PostConstruct标志的方法");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("Dog 的PreDestory标注的方法");
    }
}

@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {

}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object dog = annotationConfigApplicationContext.getBean("dog");
        System.out.println(dog);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用
  • 通過Spring的BeanPostProcessor的 bean的後置處理器會攔截所有bean建立過程(postProcessBeforeInitialization在init方法之前調用 postProcessAfterInitialization 在 init 方法之後調用)
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor...postProcessBeforeInitialization:"+beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanPostProcessor...postProcessAfterInitialization:"+beanName);
        return bean;
    }
}


@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")
    public User user() {
        return new User();
    }

}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
//        Object dog = annotationConfigApplicationContext.getBean("cat");
//        System.out.println(dog);
        annotationConfigApplicationContext.close();
    }
}
           
Spring IOC 容器注解使用

8、通過@Value [email protected]來給元件指派

@Setter
@Getter
public class User {

    @Value("張三")//通過普通的方式
    private String userName;
    @Value("#{28-3}")//spel方式來指派
    private String age;
    @Value("${user.gender}")//通過讀取外部配置檔案的值
    private String gender;
    @Value("${user.phone}")//通過讀取外部配置檔案的值
    private String phone;
    @Value("${user.address}")//通過讀取外部配置檔案的值
    private String address;
    @Override
    public String toString() {
        return "User{" +
                "userName='" + userName + '\'' +
                ", age='" + age + '\'' +
                ", gender='" + gender + '\'' +
                ", phone='" + phone + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

@Configuration
@PropertySource(value = {"classpath:user.properties"})//指定外部檔案的位置
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        Object user1 = annotationConfigApplicationContext.getBean("user");
        System.out.println(user1);
    }
}
           
Spring IOC 容器注解使用

9、自動裝配@AutoWired的使用

@Component
public class UserDao {

    public void printUserName(String userName){
        System.out.println("my name is " + userName);
    }
}

@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public void printName(String userName){
        userDao.printUserName(userName);
    }
}

@Configuration
@ComponentScan(basePackages = {"com.cym.value"})
public class BeanConfig {

    @Bean
    public User user(){
        return new User();
    }
}

public class BeanMain {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
        UserService userService = (UserService) annotationConfigApplicationContext.getBean("userService");
        userService.printName("張三");
    }
}
           
Spring IOC 容器注解使用
  • 自動裝配首先時按照類型進行裝配,若在IOC容器中發現了多個相同類型的元件,那麼就按照 屬性名稱來進行裝配比如,我容器中有二個UserDao類型的元件 一個叫UserDao 一個叫UserDao2那麼我們通過@AutoWired 來修飾的屬性名稱時UserDao,那麼拿就加載容器的UserDao元件,若屬性名稱為UserDao2 那麼他就加載的時UserDao2元件。
  • 假設我們需要指定特定的元件來進行裝配,我們可以通過使用@Qualifier("UserDao")來指定裝配的元件或者在配置類上的@Bean加上@Primary注解
@Autowired 
@Qualifier("userDao") 
private UserDao userDao2;
           
  • 假設我們容器中即沒有userDao 和userDao2,那麼在裝配的時候就會抛出異常若我們想不抛異常 ,我們需要指定 required為false的時候可以了
    @Autowired(required = false)
     @Qualifier("userDao")
     private UsergDao userDao2;
               

10、@Resource(JSR250規範)

  • 功能和@AutoWired的功能差不多一樣,但是不支援@Primary 和@Qualifier的支援

11、@InJect(JSR330規範)

  • 需要導入jar包依賴功能和支援@Primary功能 ,但是沒有Require=false的功能
<dependency> 
    <groupId>javax.inject</groupId> 
    <artifactId>javax.inject</artifactId> 
    <version>1</version>
 </dependency>
           

12、通過@Profile注解 來根據環境來激活辨別不同的Bean

  • @Profile辨別在類上,那麼隻有目前環境比對,整個配置類才會生效,@Profile辨別在Bean上 ,那麼隻有目前環境的Bean才會被激活沒有标志為@Profile的bean 不管在什麼環境都可以被激活。
@Configuration
@PropertySource(value = {"classpath:ds.properties"})
public class MainConfig implements EmbeddedValueResolverAware {
    @Value("${ds.username}")
    private String userName;
    @Value("${ds.password}")
    private String password;
    private String jdbcUrl;
    private String classDriver;

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");
        this.classDriver = resolver.resolveStringValue("${ds.classDriver}");
    }//辨別為測試環境才會被裝配 

    @Bean
    @Profile(value = "test")
    public DataSource testDs() {
        return buliderDataSource(new DruidDataSource());
    }

    //辨別開發環境才會被激活 
    @Bean
    @Profile(value = "dev")
    public DataSource devDs() {
        return buliderDataSource(new DruidDataSource());
    }

    //辨別生産環境才會被激活
    @Bean
    @Profile(value = "prod")
    public DataSource prodDs() {
        return buliderDataSource(new DruidDataSource());
    }

    private DataSource buliderDataSource(DruidDataSource dataSource) {
        dataSource.setUserName(userName);
        dataSource.setPassword(password);
        dataSource.setClassDriver(classDriver);
        dataSource.setJdbcUrl(jdbcUrl);
        return dataSource;
    }
}
           

激活切換環境的方法 方法一 : 通過運作時 jvm 參數來切換 -Dspring.profiles.active=test|dev|prod 方法二 : 通過代碼的方式來激活

public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.getEnvironment().setActiveProfiles("test", "dev");
        ctx.register(MainConfig.class);
        ctx.refresh();
        Object testDs = ctx.getBean("testDs");
        System.out.println(testDs);
        Object devDs = ctx.getBean("devDs");
        System.out.println(devDs);
//        String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
//        for (String item : beanDefinitionNames){
//            System.out.println(item);
//        }
    }
}
           
Spring IOC 容器注解使用