Spring 常用註解說明

這篇文章主要用於說明在日常開發中經常使用到的註解。

1. @Configuration

 該註解用於類上,表明這是一個配置類。在Spring MVC中,項目的配置通常放在 xml文件中, 爲此需要先寫一個配置文件,例如在xml中使用bean標籤來註冊一些組件。如下:

    <bean id="person" class="com.zhao.springboot.bean.Person">
        <property name="age" value="22"></property>
        <property name="name" value="zhangsan"></property>
    </bean>

在註解式開發中 就用該註解 來替代 xml配置文件,在配置文件中註冊組件,就被替換成在配置類中添加組件。如下所示:

// 配置類==配置文件
@Configuration //告訴SpringBoot 這是一個配置類
public class MainConfig {
    @Bean  // 給容器中注入一個Bean ,類型爲返回值的類型,id默認是用方法名作爲id
    public Person person() {
        return new Person("lisi", 25);
    }
}

測試代碼如下:

ApplicationContext annoContext = new AnnotationConfigApplicationContext(MainConfig.class);
        Person person = annoContext.getBean(Person.class);
        System.out.println(person);

輸出結果如下:

注意: @Bean 向容器中注入Bean ,id默認爲方法的名稱,可以通過name改變 ,@Bean(name="person1")

 2. @ComponentScan

該註解用於指定包掃描,用於類上。 Spring MVC中通常用 <context:component-scan /> 來指定掃描範圍,只要標註了@Controller、@Service、@Repository、@Component就會被掃描到。註解式開發中使用 @ComponentScan 來替代。

 在剛纔的配置類上使用該註解進行標註:  value指定掃描的位置

測試如下:

 分別建立 controller、service、dao類。並用相關注解進行標識。

@Repository
public class BookDao {
}
.....
@Service
public class BookService {
}
.....
@Controller
public class BookController {
}

編寫測試代碼:

@Test
    public void test01() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
        String[] names = context.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
    }

輸出如下:

 說明: 配置類(MainConfig)自身也會被註冊到bean容器中(@Configuration中包含了一個@Component註解) ,此外@ComponentScan註解中還包含有其他可以用的功能,如掃描時只包含哪些(includeFilters)、只排除哪些(excludeFilters)。 

2.1 excludeFilters 

 eg :  排除 所有Controller和Service

@ComponentScan(value = "com.zhao.springboot",excludeFilters = {
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
})

測試結果:

2.2  includeFilters 

eg: 只包含 Controller和Service 

 

@ComponentScan(value = "com.zhao.springboot",includeFilters = {
        //type 指定根據什麼規則排除
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
},useDefaultFilters = false)

 測試結果:

注意: 使用 includeFilters 時需要禁用掉默認的過濾規則,即需要設置useDefaultFilters =false(默認爲true) 

2.3 @ComponentScans

@ComponentScan 還是一個重複註解(@Repeatable(ComponentScans.class)),在java8中,可以多次使用該註解來指定不同的掃描策略:

假如不是java8 ,也可以通過 @ComponentScans來達到想要的效果。

2.4  FilterType :

 @Filter中的type指定 根據某種規則過濾,

有以下幾種規則: 

public enum FilterType {
    ANNOTATION,  // 按照註解
    ASSIGNABLE_TYPE, //按照給定的類型
    ASPECTJ, // 使用ASPECTJ表達式,不是很常用
    REGEX,  // 使用正則表達式
    CUSTOM; // 自定義規則

    private FilterType() {
    }
}

 上面已經使用了 按照註解  FilterType.ANNOTATION 類型, 這裏重點說明 FilterType.ASSIGNABLE_TYPE 和FilterType.CUSTOM。

2.4.1  給定類型 FilterType.ASSIGNABLE_TYPE:使用如下

@ComponentScan(value = "com.zhao.springboot",includeFilters = {
        //type 指定根據什麼規則排除
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),
        @Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {BookService.class})
},useDefaultFilters = false)

測試結果:

2.4.2  自定義 類型

自定義過濾類型,需要實現TypeFilter (org.springframework.core.type.filter.TypeFilter)

用法如下:

public class MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader        讀取到的當前正在掃描的類的信息
     * @param metadataReaderFactory 可以獲取到其他任何類的信息
     * @return
     * @throws IOException
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 獲取當前類的註解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 獲取當前正在掃描的類的信息 ,類型,接口是什麼......
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 獲取當前類資源(類的路徑)
        Resource resource = metadataReader.getResource();
        String className = classMetadata.getClassName();
        String superClassName = classMetadata.getSuperClassName();
        System.out.println("當前掃描類的類名---> " +className);
       // System.out.println("當前掃描類的父類---> " +superClassName);
        if (className.contains("er")) { // 類名中包含er,就包含進去
            return true;
        }
        return false;
    }
}

測試結果:

3. @Scope

在IOC容器中,我們在IOC容器中添加的實例默認都是單實例的 。也可以通過 @Scope 來指定它的作用範圍。

@Scope 取值有 以下幾種:

 

 

prototype 多實例
singleton 單實例 (默認)
request :同一次請求創建一個實例
session: 同一個session創建一個請求

 後兩種並不常用,所以這裏僅僅說明前兩種。

3.1. singleton 

默認爲單實例的(@Scope("singleton")),測試如下:

@Bean("person")
@Scope
public Person person() {
    System.out.println("給容器中添加Person");
    return new Person("張三", 25);
}
    @Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器創建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);

    }

運行結果如下:

 另外,從測試結果還能看出 在單實例情況下,當IOC容器創建時,同時也會調用方法創建對象並放到IOC容器中,以後每次獲取,就是直接從容器中拿。

 3.1.1 懶加載 @Lazy

 由於單實例bean默認在容器啓動時創建組件實例,可以設置懶加載,使容器啓動時不創建實例,而在第一次使用(獲取)實例時創建,並初始化,注意:懶加載僅僅適用於單實例。

測試如下:

@Configuration
public class MainConfig3 {
    @Bean("person")
    @Lazy
    public Person person() {
        System.out.println("給容器中添加Person");
        return new Person("張三", 25);
    }
}
   @Test
    public void test03() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("IOC容器創建完成......");

//        Object bean1 =context.getBean("person");
//        Object bean2 =context.getBean("person");
//        System.out.println(bean1 ==bean2);

    }

 測試結果:

 在測試如下:

 @Test
    public void test03() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("IOC容器創建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);

    }

 測試結果如下:

3.2  prototype

當@Scope的值設置爲 prototype 時,則添加的組件在容器中是多實例的,IOC容器創建時,並不會調用方法創建該組件的實例,而是每當使用到該組件時,就去創建一個實例。

 測試如下: 

    @Bean("person")
    @Scope("prototype")
    public Person person() {
        System.out.println("給容器中添加Person");
        return new Person("張三", 25);
    }
 @Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
//        System.out.println("IOC容器創建完成......");
//
//        Object bean1 =context.getBean("person");
//        Object bean2 =context.getBean("person");
//        System.out.println(bean1 ==bean2);

    }

執行測試方法,會發現控制檯並沒有輸出  創建Person的實例的語句(給容器中添加Person)。

在進行測試如下:

@Test
    public void test02() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器創建完成......");

        Object bean1 =context.getBean("person");
        Object bean2 =context.getBean("person");
        System.out.println(bean1 ==bean2);
    }

輸出如下:

 4. @Conditional

 該條件用於 按照一定的條件進行判斷,滿足條件進行判斷,滿足條件給容器中註冊bean

其值是一個 Condition數組, Condition是一個包含一個方法的接口:

boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

context : 判斷條件能使用到的上下文(環境) ,  metadata 註釋信息。

使用如下:

@Configuration
public class MainConfig4 {
  
    @Bean("Windows")
    public Person person01() {
        return new Person("Windows", 50);
    }

    @Bean("Linux")
    public Person person02() {
        return new Person("Linux", 40);
    }
}

假如在配置類中向容器中注入兩個Person實例,分別爲 Windows和Linux ,想要實現根據操作系統(Windows或Linux)不同而分別注入不同的實例。那麼,可以這樣做:

首先編寫兩個類實現Condition類,並實現  matches 方法。WindowsCondition和 LinuxCondition(與WindowsCondition代碼基本一致,不再重複貼)

public class WindowsCondition implements Condition {
   
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 1. 能獲取ioc 使用的beanFactory ,裝配的工廠。
        ConfigurableListableBeanFactory factory = context.getBeanFactory();
        // 2. 獲取類加載器
        ClassLoader loader = context.getClassLoader();
        //3. 獲取當前環境信息,包括環境變量、虛擬機變量等
        Environment environment = context.getEnvironment();
        //4. 獲取到bean定義的註冊類
        BeanDefinitionRegistry registry = context.getRegistry();

        String operationName = environment.getProperty("os.name");
//        System.out.println(operationName);
        if (operationName.contains("Windows")) {
            return true;
        }
        return false;
    }

然後在配置類中使用 @Conditional 註解引入。

   @Conditional({WindowsCondition.class})
    @Bean("Windows")
    public Person person01() {
        return new Person("Windows", 50);
    }

    @Conditional({LinuxCondition.class})
    @Bean("Linux")
    public Person person02() {
        return new Person("Linux", 40);
    }

編寫測試類如下:

 @Test
    public void test04() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);
        System.out.println("IOC容器創建完成......");
        Environment environment = context.getEnvironment();
        String operationName =environment.getProperty("os.name");
        System.out.println("操作系統名稱:"+operationName);

        Map<String, Person> beans = context.getBeansOfType(Person.class);
        System.out.println(beans);
    }

輸出結果如下:

另外 @Conditional 也可以標註在類上 ,那麼此時的含義爲滿足當前條件,該類中配置的所有bean註冊纔會生效。

@Configuration
@Conditional({LinuxCondition.class})
public class MainConfig2 {
    @Bean("person")
    public Person person() {
        System.out.println("給容器中添加Person");
        return new Person("張三", 25);
    }
}

 編寫測試方法:

@Test
    public void test04() {
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
        System.out.println("IOC容器創建完成......");
        Environment environment = context.getEnvironment();
        String operationName =environment.getProperty("os.name");
        System.out.println("操作系統名稱:"+operationName);

        Map<String, Person> beans = context.getBeansOfType(Person.class);
        System.out.println(beans);
    }

測試結果:

 5. 向容器中註冊組件相關注解

給容器註冊組件除了 上面的

   包掃描+組件標註註解(@Controller /@Service...) ,

   @Bean[導入的第三方包裏面的組件]

也可以通過 @Import 快速給容器中導入一個組件 ,下面說明@Import用法。

5.1 @Import (要導入到容器中的組件)

  該方式會自動向容器中註冊組件,id默認爲全類名

@Configuration
public class MainConfig5 {
}
....
public class Color {
}
.....
public class Red {
}

編寫測試代碼:

private void printBeans(ApplicationContext context) {
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String name:beanDefinitionNames) {
            System.out.println(name);
        }
    }

@Test
public void test05(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);
        printBeans(context);
   }

輸出結果如下:

由於此時 配置類中並沒有註冊 Red 和Color ,所以並沒有打印出其名稱。修改配置類MainConfig5 ,加上@Import註解:

@Configuration
@Import({Color.class, Red.class})
public class MainConfig5 {
}

測試結果:

5.2   ImportSelector

  ImportSelector 導入選擇器, 是一個接口,裏面包含一個  selectImports 方法, 通過實現該方法能實現返回 需要導入類的全類名,進而向容器註冊組件。SpringBoot中該方式用的也比較多。

 用法: 編寫類實現 ImportSelector接口,實現裏面的 selectImports:

public class MyImportSelector implements ImportSelector {

    // 返回值,就是導入到容器中的組件全類名
    // AnnotationMetadata : 當前標註 @Import註解的類的所有註解信息
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{"com.zhao.springboot.bean.Blue","com.zhao.springboot.bean.Yellow"};
        //方法不要返回null
    }
}

測試:

@Configuration
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig5 {
}

測試結果如下:

注意:MyImportSelector 並不會被註冊到容器中,該類實現了 ImportSelector,僅僅是把方法返回值中的全類名註冊到容器中。

5.3  ImportBeanDefinitionRegistrar

 也可以手動註冊組件,通過實現  ImportBeanDefinitionRegistrar中的 registerBeanDefinitions 方法來註冊 ,用法如下:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     *
     * @param importingClassMetadata :當前類的註解信息
     * @param registry : BeanDefinition註冊類,把所有需要添加到容器中的bean
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean redDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Blue");
        boolean blueDefinition = registry.containsBeanDefinition("com.zhao.springboot.bean.Yellow");

        if (redDefinition && blueDefinition){
            // 指定bean定義信息,(Bean 類型..等)
            BeanDefinition definition = new RootBeanDefinition(RainBow.class);
//            definition.setScope();
            // 指定bean名稱
              // String beanName, BeanDefinition beanDefinition
             // 註冊一個bean
            registry.registerBeanDefinition("rainBow",definition);
        }
    }
}

測試如下:

 5.4 使用Spring 提供的FactoryBean (工廠bean)註冊

使用方式如下:

public class ColorFactoryBean  implements FactoryBean<Color> {

    // 返回一個Color對象,這個對象將會被添加到容器中
    @Override
    public Color getObject() throws Exception {
        System.out.println("ColorFactoryBean........getObject....");
        return new Color();
    }

    @Override
    public Class<?> getObjectType() {
        return Color.class;
    }
    /*
       是否是單例? true:單例,在容器中保存一份,
                   false: 多實例, 每次獲取都會創建一個新的bean
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}

 將 ColorFactoryBean 註冊到容器中:

 @Bean
    public FactoryBean colorFactoryBean() {
        return new ColorFactoryBean();
    }

測試:

   @Test
    public void test06(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);

        // 工廠bean 獲取的是 調用 getObject創建的對象
        Object colorFactoryBean = context.getBean("colorFactoryBean");
        Object colorFactoryBean1 = context.getBean("colorFactoryBean");
        System.out.println(colorFactoryBean.getClass().getName());
        System.out.println(colorFactoryBean == colorFactoryBean1);
        // 如果想獲取 工廠bean本身 ,可以使用 &
        Object colorFactoryBean3 = context.getBean("&colorFactoryBean");
        System.out.println(colorFactoryBean3.getClass().getName());
    }

測試結果:

 6. Bean生命週期相關注解

 容器管理bean的生命週期,我們可以自定義初始化和銷燬方法,容器在bean進行到當前生命週期的時候來調用我們自定義的初始化和銷燬方法。

1) 指定初始化和銷燬方法;

通過@Bean指定init-method和destory-method 。具體用法如下:

public class Car {
    public Car() {
        System.out.println("Car...constructor.....");
    }

    public void init(){
        System.out.println("Car.....init.....");
    }

    public void destroy() {
        System.out.println("Car......destroy....");
    }
}
.....
@Configuration
public class MainConfigOfLifeCycle {

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

編寫測試方法:

注意: 單實例的銷燬發生在容器關閉的時候,而當設置爲多實例時,容器不會管理這個bean,容器不會調用銷燬方法。

 

   @Bean(initMethod ="init",destroyMethod ="destroy")
    @Scope("prototype")
    public Car car() {
        return new Car();
    }
......
@Test
    public void test01() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
        System.out.println("容器創建完成.......");
        context.getBean("car");
        context.close();
    }

測試結果:

2) 通過讓Bean 實現 InitializingBean(定義初始化邏輯), DisposableBean(定義銷燬邏輯)

public class Cat implements InitializingBean, DisposableBean {
    public Cat() {System.out.println("Cat...constructor.....");}
    @Override
    public void destroy() {
        System.out.println("Cat......destroy....");
    }
    @Override
    public void afterPropertiesSet() {
        System.out.println("Cat.....afterPropertiesSet.....");
    }
}
......
@Bean
public Cat cat(){
   return  new Cat();
}

測試結果:

 3) 可以使用 JSR250 

@PostConstruct :在bean創建完成並屬性賦值完成,來執行初始化方法。
@PreDestroy:在容器銷燬bean之前,通知我們進行銷燬工作。

使用如下:

public class Dog {
    public Dog() {
        System.out.println("Dog...constructor");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("Dog......@PreDestroy....");
    }
    @PostConstruct
    public void init() {
        System.out.println("Dog.....@PostConstruct.....");
    }
}
.....
 @Bean
 public Dog dog() {
    return new Dog();
 }

測試結果如下:

4) 使用 BeanPostProcessor【Interface】 

 bean的後置處理器。在bean初始化前後進行一些處理工作。即在afterPropertiesSet 或init-method 前後進行一系列的操作。

 有以下兩個api :

// 在初始化之前工作
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;    
//在初始化之後工作
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;  

使用如下:

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         // bean : the new bean instance
        System.out.println("postProcessBeforeInitialization...." + beanName+">= " +bean);
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization......" + beanName+">= " +bean);
        return bean;
    }
}

注意:要將實現類加入到bean容器。測試結果如下:

debug查看該執行流程,發現   執行初始化方法的時候(invokeInitMethods)  ,先調用 applyBeanPostProcessorsBeforeInitialization,執行完初始化方法後又執行了 applyBeanPostProcessorsAfterInitialization:

執行流程: 

doCreateBean -->  createBeanInstance--> initializeBean--> applyBeanPostProcessorsBeforeInitialization -->invokeInitMethods--> applyBeanPostProcessorsAfterInitialization ...(AbstractAutowireCapableBeanFactory)

7. @Value

@Value 爲屬性賦值 ,  支持 : 1) 基本數值  2) 可以寫 SpEL ;#{ }  3) 可以寫${} ;取出配置文件的值(在環境變量裏面的值)

使用如下:

public class Student {

    @Value("李四")
    private  String name;
    @Value("#{25-5}")
    private Integer age;

   // ... 省略 getter 、setter、constructor 、toString
}

打印出該容器中所有bean ,結果如下:

若需要使用 ${}取出配置文件中的值,需要使用到另一個註解: @PropertySource ,該註解用於指定配置文件的位置,從而可以使用${} 取出配置文件中的值。下面說明用法:

給Student類中添加一個nickName的字段, 此時由於沒有賦值,重新測試,打印出容器中的bean ,顯示該字段爲null:

寫一個配置文件(Student.properties):

student.nickName=小灰灰

在配置類上進行修改:

@Configuration
@PropertySource(value={"classpath:/student.properties"}) // 指定配置文件的位置
public class MainConfigOfPropertyValues {
    @Bean
    public Student student() {
        return new Student();
    }
}

測試:

ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("student.nickName");

8. 自動裝配的相關注解

 8.1  @Autowired :自動注入

用法如下:

@Service
public class BookService {
    @Autowired
    private BookDao bookDao;
// toString 省略
}
.....
@Repository
public class BookDao {
    private String label = "1";
   // getter、setter、toString省略
}
.....
@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {
}

 測試代碼:

  @Test
    public void test01() {
        BookService bookService = context.getBean(BookService.class);
        System.out.println(bookService);  
    }

 測試結果:

注意:

(1) 默認優先按照類型去容器中找對應的組件:applicationContext.getBean(BookDao.class);找到就賦值
(2)如果找到了多個相同類型的組件,在將屬性的名稱作爲組件的id去容器中查找
             applicationContext.getBean("bookDao");
(3) 使用 @Qualifier("bookDao"):使用@Qualifier指定需要裝配的組件的id,而不是使用屬性名
(4) 自動裝配默認一定要將屬性賦值好,沒有就會報錯,也可以使用 @Autowired(required =false),設置不必須裝配
(5) @Primary:讓spring 進行自動裝配的時候,默認使用首選的bean,也可以繼續使用@Qualifier指定需要裝配的bean的名字

測試如下:

 1)容器中存在兩個bean

@Configuration
@ComponentScan({"com.zhao.springboot.controller", "com.zhao.springboot.service", "com.zhao.springboot.dao"})
public class MainConfigOfAutowired {

    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao dao = new BookDao();
        dao.setLabel("2");
        return dao;
    }
}

運行測試方法進行測試:

2) 使用@Primary指定默認裝配的bean

    @Primary
    @Bean("bookDao2")
    public BookDao bookDao() {
        BookDao dao = new BookDao();
        dao.setLabel("2");
        return dao;
    }

 測試結果:

3)@Qualifier指定 要裝配的組件的id 

  在 2)的基礎上進入修改:  

@Service
public class BookService {
    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;
// toString 省略
}

測試結果:

8.2  Spring 還支持使用 @Resource (JSR250) 和@Inject(JSR330)[JAVA規範的註解]

 @Resource可以和 @Autowired 一樣實現自動裝配功能,默認是安裝組件名稱進行裝配的。但是沒有能支持@Primary,也沒有支持@Autowired(required=false)的功能。其使用如下:

@Service
public class BookService {
   // @Autowired
   // @Qualifier("bookDao")
    @Resource(name ="bookDao2")
    private BookDao bookDao;
// toString 省略
}

測試結果:

如果想要使用@Inject, 還需要引入一個依賴:

<dependency>
     <groupId>javax.inject</groupId>
     <artifactId>javax.inject</artifactId>
     <version>1</version>
</dependency>

 使用如下:

@Service
public class BookService {
   // @Autowired
   // @Qualifier("bookDao")
   // @Resource(name ="bookDao2")
    @Inject
    private BookDao bookDao;
// toString 省略
}

測試如下:

引入了 配置類中使用@Bean注入的實例,是因爲 該實例使用了@Primary註解,也即 @Inject 和@Autowired功能一樣,但是@Inject沒有 @Autowired的required=false的功能。

8.3 @Autowired 標註的位置

@Autowired 可以標註在:  構造器、參數、方法、 屬性  ,這些都是從容器中獲取組件參數的值。

1) 標註在方法位置:用的最多的就是 @Bean  +方法參數,參數從容器中自動獲取,默認不寫@Autowired
2) 標註在構造器上: 如果組件只有一個有參構造器,那麼這個有參構造器的@AutoWired可以省略,參數位置的組件還是可以從容器中自動獲取
3) 標註在方法中的參數上
@Component
public class Boss {
//    @Autowired
    private Car car;
//    @Autowired  // 可以直接標註在方法上
    public void setCar(Car car) {
        this.car = car;
    }
   // 省略 getter 和 toString方法
   // @Autowired  // 可以標註在構造器上
  //public Boss(@Autowired Car car) { // 如果組件只有一個有參構造器,那麼這個有參構造器的@Autowired可以省略,參數位置的組件還是可以從容器中自動獲取
    public Boss( Car car){
        this.car = car;
    }
}
  @Bean
//  public Boss boss(@Autowired Car car) {  //Car只有一個有參構造器,所以可以省略 @Autowired
    public Boss boss( Car car) {
        return new Boss(car);
    }

8.4  自定義組件想要使用Spring 容器底層的一些組件(ApplicationContext,BeanFactory,xxxx) 

如果想要使用Spring容器底層組件,只需要讓自定義組件 實現xxxAware即可。在創建對象的時候,會調用接口規定的方法注入相關組件。頂層 接口爲 Aware(org.springframework.beans.factory)

使用如下:

public class Cup implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    @Override
    public void setBeanName(String name) {
        System.out.println("當前bean的名稱: " + name);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("傳入的IOC 容器: " + applicationContext);
        this.applicationContext = applicationContext;
    }
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) { //解析字符串
        String resolveStringValue = resolver.resolveStringValue("你好,${os.name} 我是 #{20*18}");
        System.out.println("解析的字符串:" + resolveStringValue);
    }
}
.......
   @Bean
    public Cup cup() {  // 向容器中注入 bean
        return new Cup();
    }

編寫測試代碼:

    @Test
    public void test02(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAutowired2.class);
        System.out.println(context);
    }

測試結果如下:

9. @Profile

@Profile 是Spring爲我們提供的,具有可以根據當前環境,動態的激活和切換一系列組件的功能。該註解指定組件在哪個環境的情況下才能被註冊到容器中,若不指定,任何環境下都能註冊這個組件。

 1) 加了環境標識的bean,只有這個環境被激活的時候才能註冊到容器中,默認是default環境

 2) 標註在 配置類上,只有在指定的環境的時候,整個配置類裏面的所有配置才能開始生效

使用如下:(以多數據源爲例。引入c3p0依賴)

@Configuration
@PropertySource("classpath:/dbConfig.properties")
public class MainConfigOfProfile implements EmbeddedValueResolverAware {
    @Value("${db.user}")
    private String user;
    private String driverClass;
    private StringValueResolver resolver;

      @Profile("default")
//    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("db.password") String password) throws Exception {
        return getDataSource(password,"jdbc:mysql://localhost:3306/test");
    }
    
    //@Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("db.password") String password) throws Exception {
        return getDataSource(password,"jdbc:mysql://localhost:3306/dev");
    }
    @Profile("prod")
    @Bean("prodDataSource")
    public DataSource dataSourceProd(@Value("db.password") String password) throws Exception {
        return getDataSource( password ,"jdbc:mysql://localhost:3306/prod");
    }
    private DataSource getDataSource(String password, String url) throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(url);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        this.resolver = resolver;
        this.driverClass = resolver.resolveStringValue("${db.driverClass}");
    }
}

該配置類寫的有點複雜,也是爲了練習前面的註解 ,測試代碼如下:

   @Test
    public void test01() {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
        String[] names = context.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
   }

測試結果如下:

激活prod環境測試如下:

AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext();
        context.getEnvironment().setActiveProfiles("prod"); //允許設置多個活動的環境
        context.register(MainConfigOfProfile.class);
        context.refresh();
        String[] names = context.getBeanNamesForType(DataSource.class);
        for (String name : names) {
            System.out.println(name);
        }
.....

測試如下:

 注意:出現devDataSource 數據源是因爲,配置類上該數據源並沒有指定環境,所以默認任何環境下都註冊了。

也可以通過 指定虛擬機參數的方法激活環境  , 加上 -Dspring.profiles.active=prod

 10. 切面相關注解

前置通知(@Before) :在目標方法執行前執行
後置通知(@After): 目標方法運行結束後執行(無論正常還是異常結束)
返回通知(@AfterReturning) : 在目標方法正常返回後執行
異常通知(@AfterThrowing): 在目標方法出現異常後執行
環繞通知(@Around):動態代理,手動推進目標方法運行(joinPoint.procced())
@Aspect :告訴spring這是一個切面類
@EnableAspectJAutoProxy :開啓基於註解的aop模式

使用方法步驟如下:

 假如有需求如下: 做除法運算,並返回結果。 並且 在業務邏輯執行的時候將日誌進行打印(方法運行前、運行結束、出現異常)

1)首先定義一個業務類(默認已經導入了aop相關依賴):

public class MathCalculator {
    public int div(int i, int j) {
        System.out.println("MathCalculator..div...");
        return i / j;
    }
}

2)定義日誌切面

@Aspect//指明這個一個切面類
public class LogAspects {

    // @Before 在目標方法之前切入:切入點表達式(指定在哪個方法切入)
//    @Before("com.zhao.springboot.aop.MathCalculator.div(int ,int )")
//    @Before("com.zhao.springboot.aop.MathCalculator.*(..)")
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) { // 注意:當方法有多個參數時,JoinPoint必須放置第一位置
        Object[] args = joinPoint.getArgs();
        System.out.println(joinPoint.getSignature().getName() + " 除法運行...參數列表: {" + Arrays.asList(args) + "}");
    }
    @After("com.zhao.springboot.aop.LogAspects.pointCut()")
    public void logEnd(JoinPoint joinPoint) {
        System.out.println(joinPoint.getSignature().getName() + " 除法結束");
    }
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(Object result) {
        System.out.println("除法正常返回。。。運行結果: {" + result + "}");
    }
    @AfterThrowing(value = "pointCut()", throwing = "ex")
    public void logException(Exception ex) {
        System.out.println("除法異常。。。異常信息: {" + ex + "}");
    }
    // 抽取公共的切入點表達式
    // 1. 本類引用  pointCut()
    //2. 其他的切面引用 com.zhao.springboot.aop.LogAspects.pointCut()
    @Pointcut("execution(public  int com.zhao.springboot.aop.MathCalculator.*(..))")
    public void pointCut() {
    }
}

3) 將切面類和業務邏輯類加入到容器中,並開啓基於註解的aop模式

@EnableAspectJAutoProxy // 開啓註解
@Configuration
public class MainConfigOfAop {
    @Bean
    public MathCalculator mathCalculator() {
        return new MathCalculator();
    }
    @Bean
    public LogAspects logAspects() {
        return new LogAspects();
    }
}

測試代碼:

   @Test
    public void test01() {
        // 1. 不要自己創建對象 ,需要使用spring創建的實例,才能使用到spring的切面
//        MathCalculator calculator =new MathCalculator();
//        calculator.div(1,1);

        MathCalculator calculator = context.getBean(MathCalculator.class);
//        calculator.div(1, 1);
        calculator.div(1, 1);
    }

測試結果如下:

 注意:切面類上必須加上 @Aspect 表名該類爲一個切面類,並且 配置類上也需要使用 @EnableAspectJAutoProxy

開啓基於註解的aop模式,此時切面纔會生效。 除外次,要使用Spring容器創建的bean實例,纔會觸發切面。

11  與事務相關

@Transactional  //標註在方法上,表示當前方法是一個事務方法
@EnableTransactionManagement  // 開啓事務管理功能

使用步驟如下:

1. 配置數據源、JdbcTemplate(Spring 提供的簡化數據庫操作的工具)操作數據
2. 給方法上標註@Transactional 表示當前方法是一個事務方法
3. 開啓事務管理功能。
4. 往容器中 配置一個事務管理器,並管理 dataSource

 代碼使用如下:

@Configuration
@PropertySource("classpath:/dbConfig.properties")
@ComponentScan("com.zhao.springboot.config.tx")
@EnableTransactionManagement   //開啓事務管理功能
public class TxConfig {
    @Value("${db.user}")
    private String user;
    @Value("${db.password}")
    private String password;
    @Value("${db.driverClass}")
    private String driverClass;

    @Bean
    public DataSource comboPooledDataSource() throws Exception {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
    // 配置JdbcTemplate
    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception { 
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(comboPooledDataSource());
        return jdbcTemplate;
    }
    // 註冊事務管理器
    @Bean  // PlatformTransactionManager 爲事務管理器的頂層接口
    public PlatformTransactionManager transactionManager() throws Exception {
        return new DataSourceTransactionManager(comboPooledDataSource());
    }
}
 ....  dao層
@Repository
public class UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public void insert() {
        String sql = "INSERT INTO `tbl_user` ( `username`, `age`) VALUES (?, ?);";
        String username = UUID.randomUUID().toString().substring(0, 5);
        jdbcTemplate.update(sql, username, 19);
    }
}
.... service層
@Service
public class UserService {
    @Autowired
    private UserDao userDao;

    @Transactional   // 標註 這個一個事務方法
    public void addUser(){
        userDao.insert();
        System.out.println("插入成功");
        int i=1/0;
    }
}

測試代碼:

 @Test
    public void test01() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
        UserService userService = context.getBean(UserService.class);
        userService.addUser();
    }

測試前數據庫數據:

 測試後:

數據庫數據:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章