在springboot的开发中,是比较推荐使用注解开发的,而它的底层,有很多地方都用到了spring的东西,所以我觉得有必要学习一下spring的注解开发及其相关原理。以下均属于个人学习笔记,在此记录,以便日后之用,希望对正在阅读本篇博客的你有所帮助。现在主要讲讲组件注册:

一、创建maven工程,导入相关依赖,如下是我的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qxf</groupId>
    <artifactId>spring-annotation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

</project>

二、通过@Bean方式注册

1.创建实体类Person,如下:

package com.qxf.pojo;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.pojo
 */
public class Person {
    private String name;
    private Integer age;

    public Person(){

    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.创建配置类ComponentRegistryConfig,如下

package com.qxf.config;

import com.qxf.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.config
 */
@Configuration     //表示这是一个配置类
public class ComponentRegistryConfig {
    @Bean
    public Person person123(){
        return new Person("张三",22);
    }
}

3.创建测试类ComponentRegistryConfigTest,如下:

package com.qxf.test;

import com.qxf.config.ComponentRegistryConfig;
import com.qxf.pojo.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.test
 */
public class ComponentRegistryConfigTest {
    //读取配置文件,获取配置信息
    private ApplicationContext context = new AnnotationConfigApplicationContext(ComponentRegistryConfig.class);

    @Test
    public void testBeanAnnotation(){
        Person person = (Person) context.getBean("person123");
        System.out.println("testBeanAnnotation---"+person);
    }
}

4.运行测试方法testBeanAnnotation(),可在控制台看到如下结果:

spring动态注册mappper_自定义

说明测试通过,组件注册成功的。

关于获取bean的方式,主要有2种:

(1)根据名称获取:这个名称是唯一的,例如上面测试类的,就是根据名称获取

Person person = (Person) context.getBean("person123");

(2)根据类型获取,使用这种方法,有一个注意点,一个类的bean实例可能不止一个,如果有多个就会报错,建议使用根据名称获取,这个当做了解

Person bean = context.getBean(Person.class);

好处就是,如果容器中该类只有一个bean的时候,可以不同进行类型强制转换,写起来方便一点点。

5.几个注解的作用

(1)@Configuration,表示这是一个配置类,在spring配置中,相当于以前的applicationContext.xml文件,其实这个注解,也可以用在其他配置信息的配置类上,比如mybatis配置,shiro配置,都可以用这个注解来代替以前的xml配置文件,主要还是用于读取配置信息。

(2)@Bean,这个注解就表示向spring容器注册组件,其作用相当于在applicationContext.xml文件中的<bean>标签,bean的名称默认是方法名,也可以自定义,如@Bean("person11"),那么bean的名称不再是方法名称,而是person11。

6.@Scope注解,修改bean作用域

默认情况下,spring容器的bean是单例的:

spring动态注册mappper_自定义_02

可以看到,打印结果为true,2个bean,其实是同一个bean。如果你不想使用单例的效果,有其他的需求,则可以通过@Scope注解,来修改bean的作用域,其有4种取值,这里就不演示了,对象的创建时机略有不同:

(1)singleton,单例,默认值,在创建IOC容器时就创建了对象,可以通过@Lazy实现懒加载,这样就会推出对象的创建时机,在第一次使用对象的时候才创建并初始化

(2)prototype,多例,在创建IOC容器时,并不会创建对象,而是在每次获取bean对象的时候,才创建对象

(3)request,web环境中使用,每次http请求创建对象

(4)session,web环境中使用,每个会话中只有一个对象

三、包扫描+组件注解(ComponentScan+Controller||Service||Repository||Component)注册

1.在(二)的基础上,新建以下几个类:

(1)PersonDao

package com.qxf.dao;

import org.springframework.stereotype.Repository;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.dao
 */
@Repository
public class PersonDao {
}

(2)PersonService

package com.qxf.service;

import org.springframework.stereotype.Service;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.service
 */
@Service
public class PersonService {
}

(3)PersonController

package com.qxf.controller;

import org.springframework.stereotype.Controller;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.controller
 */
@Controller
public class PersonController {
}

(4)修改后的配置类ComponentRegistryConfig(添加了一个包扫描)

package com.qxf.config;

import com.qxf.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.config
 */
@Configuration     //表示这是一个配置类
@ComponentScan(basePackages = "com.qxf")   //包扫描
public class ComponentRegistryConfig {
    @Scope("prototype")  //默认为singleton
    @Bean
    public Person person123(){
        return new Person("张三",22);
    }
}

(5)测试方法

@Test
    public void testComponentScan(){
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        System.out.println("下面是已定义的bean:");
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }
    }

(6)测试结果

spring动态注册mappper_注解_03

可以看到,spring容器中,除了自定义的bean之外,还有一些内部的bean。

在web开发中用得比较多的@Repository,@Service,@Controller注解,其bean的默认名称为类名首字母小写。

如果你不想扫描全部的注解,可以采用以下方法:

(1)使用excludeFilters,指定要排除的

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

 其中,type = FilterType.ANNOTATION,表示根据注解类型进行排除


而,classes = {Controller.class, Service.class },则表示要排除的注解,这里表示要排除掉@Service,@Controller注解的bean


(2)使用includeFilters,指定要留下的

@ComponentScan(value = "com.qxf",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {
                Controller.class
        }),
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {
                PersonService.class
        })
},useDefaultFilters = false)

与排除规则不同的是,要将默认过滤规则设置为false:useDefaultFilters = false,否则不起效果。

(3)自定义过滤规则

@ComponentScan(value = "com.qxf",includeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
},useDefaultFilters = false)

其中,MyTypeFilter要实现TypeFilter接口,具体例子如下:

package com.qxf.config;

import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;

import java.io.IOException;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/7
 * @Description: com.qxf.config
 */
public class MyTypeFilter implements TypeFilter{
    //返回值为true,表示匹配成功,false,表示不匹配
    @Override
    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();
        System.out.println("当前类--->"+className);
        //如果当前类中包含"service",则返回true
        if(className.contains("service")){
            return true;
        }
        return false;
    }
}

四、@Import快速导入外部类到容器

     @Import注解在springboot中运用比较多,所以也一起介绍一下。

1.新建如下类(包括修改的类)

(1)Student

package com.qxf.pojo;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.pojo
 */
public class Student {
}

(2)Grade

package com.qxf.pojo;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.pojo
 */
public class Grade {
}

(3)Teacher

package com.qxf.pojo;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.pojo
 */
public class Teacher {
}

(4)MyImportSelector,自定义bean选择器,实现ImportSelector接口,返回要创建的bean的全类名的数组

package com.qxf.condition;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: 自定义导入bean选择器
 */
public class MyImportSelector implements ImportSelector{
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {

        return new String[]{
                "com.qxf.pojo.Student"
        };
    }
}

(5)MyImportBeanDefinitionRegistrar,根据条件手动注册bean,要实现ImportBeanDefinitionRegistrar接口

package com.qxf.condition;

import com.qxf.pojo.Grade;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: 手动注册bean
 */
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        //通过BeanDefinitionRegistry手动控制bean注册到IOC容器中
        boolean b = registry.containsBeanDefinition("com.qxf.pojo.Student");
        //如果IOC容器中有com.qxf.pojo.Student的bean
        if(b){
            //就将成绩注册为bean
            RootBeanDefinition beanDefinition = new RootBeanDefinition(Grade.class);
            registry.registerBeanDefinition("grade",beanDefinition);
        }
    }
}

(6)修改配置类,添加@Import注解,导入bean

package com.qxf.config;

import com.qxf.condition.MyImportBeanDefinitionRegistrar;
import com.qxf.condition.MyImportSelector;
import com.qxf.pojo.Person;
import com.qxf.pojo.Student;
import com.qxf.pojo.Teacher;
import org.springframework.context.annotation.*;

/**
 * @Auther: qiuxinfa
 * @Date: 2019/12/15
 * @Description: com.qxf.config
 */
@Configuration     //表示这是一个配置类
@ComponentScan(basePackages = "com.qxf")   //包扫描
@Import({Teacher.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class ComponentRegistryConfig {
    @Scope("prototype")  //默认为singleton
    @Bean
    public Person person123(){
        return new Person("张三",22);
    }
}

(7)测试结果

spring动态注册mappper_注解_04

可以看到,容器中多了3个bean