一篇文章,让你告别xml(Spring 注解开发)


在学习spring中的注解开发之前,必须了解java中的反射和注解:

1. 组件添加相关注解

当一个项目很大的时候,xml 文件就会有很多,配置起来就稍微有点麻烦,所以,可以使用注解来代替在xml的相关配置。

1.1 @Configuration

用在类上,指明这个类是配置类,相当于是一个只有名称空间的xml文件,有了配置类,就可以不用写xml文件了。

1.2 @Bean

用在方法上,给IOC容器中注册一个bean,id 默认为方法名,也可以使用value属性指定,类型是方法的返回值类型。相当于在xml文件中写<bean id=" " class=" "/></bean>。如果方法中有参数,参数默认从容器中获取。
`

@//表示这个类是配置类,代替xml文件
@Configuration
public class MainConfig {
    @Bean //在IOC容器中注册bean,类型是返回值类型,id默认是方法名
    public Person person(){
        return new Person("张三",20);
    }
}

1.3 @ComponentScan

用在配置类上,开启包扫描,开启之后,类只要标注了@Controller、@Service、@repository、@Componmente
这四个注解中的任何一个,都会在容器中注册,相当于在xml文件中使用<context:componment-scan value=" ">

  • 常用属性:
  • value:指定要扫描的包
  • excludeFilters :指定扫描的时候按照什么规则排除哪些组件,这个属性的类型是一个Filter注解类型的数组,Filter注解中,有type(按照什么类型进行排除)、classes(排除type类型的类)等属性
@ComponentScan(value="ewen",
       excludeFilters = {
       排除@Repository注解
       @Filter(type=FilterType.ANNOTATION,classes={Repository.class})
})
  • includeFilters:指定扫描的时候只包含那些类型的组件,使用这个属性的时候,需要让useDefaultFilters=false(@CoponentScan的属性),来禁用默认的过滤规则,默认使用的是不扫描哪些些组件
@ComponentScan(value="ewen",
includeFilters ={
        //只要@Service注解
        @Filter(type=FilterType.ANNOTATION, classes={Service.class, Repository.class})
},  useDefaultFilters = false)

@Filter这个注解中,type的类型是FilterType枚举类,枚举类中有以下几个值

  • FilterType.ANNOTATION:按照注解(常用),classes是注解.class
  • FilterType.ASSIGNABLE_TYPE:按照给定的类型(常用),classes是类名.class
  • FilterType.ASPECTJ:使用ASPECTJ表达式
  • FilterType.REGEX:使用正则指定
  • FilterType.CUSTOM:使用自定义规则(自定义类,实现FilterType接口),classes为自定义类.class

自定义过滤规则,就是写一个类,实现FilterType接口,实现match方法(过滤规则),当扫描一个类时,会执行match方法,如果返回true,表示,符合规则,将这个类在IOC容器中注册,返回false,不在容器中注册。

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

自定义规则(只要含有 “er” 的类)

public class MyTypeFilter implements TypeFilter {
    /**
     * @param metadataReader:读取到的当前正在扫描的类信息
     * @param metadataReaderFactory:可以获取到其他类的任何信息
     * @return 当进行包扫描时,使用自定义过滤规则,扫描到的类来与这个方法中定义的规则匹配
     *          如果返回true,代表符合规则,将这个类注入到容器中,返回false,代表不符合规则
     * @throws IOException
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        //获得当前正在扫描类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        //获取当前正在扫描的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        //获取当前正在扫描类的路径
        Resource resource = metadataReader.getResource();
        String name = classMetadata.getClassName();
        if(name.contains("er")){
            return true;
        }
        return false;
    }
}

1.4 @Scope

指定bean的作用范围,有如下四个属性:

  • singleton:单实例的,默认值,会在IOC容器启动的时候,创建对象(执行@Bean下的方法),并且,只创建一次,每一次拿到的是同一个对象
  • prototype:多实例的,从容器中获取对象的时候创建,每次获取,都会执行@Bean下的方法,得到不同的对象
  • request:同一次请求创建一个实例(很少用)
  • session:同一个session创建一个实例(很少用)

1.5 @Lazy

懒加载,容器启动时,不创建对象,获取时才会创建,只有当是bean是单例的(singleton)时才有用

1.6 @Conditional

用在方法上,按照一定条件进行判断,满足条件给容器注册bean,注意需要和@Bean一起使用。也可以把这个注解放在类上,类中的bean满足条件才可以在容器中注册
需要定义一个条件类,实现Condition接口,实现match方法,如果这个方法返回true,则符合条件,注册bean

	//如果是Windows操作系统,给容器中注册bill
    @Conditional(WindowsCondition.class)
    @Bean
    public Person person01(){
        return new Person("bill", 60);
    }
	

自定义条件类

//判断当前操作系统是不是Windows
public class WindowsCondition implements Condition {
    /**
     *
     * @param conditionContext:判断条件能使用的上下文环境
     * @param annotatedTypeMetadata:注释信息
     * @return 如果返回true,代表符合条件,返回false,表示不符合条件
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        /*通过conditionContext可以获得很多有用的信息, 可以使用这些信息,来设置很多条件
        //1.获取IOC使用的工厂BeanFactory
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        //2.获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
          //4.获取bean定义的注册类,通过这个类可以注册、查询、移除IOC容器中的bean
        BeanDefinitionRegistry registry = conditionContext.getRegistry();
        */
        //3. 获取运行时的环境信息
        Environment environment = conditionContext.getEnvironment();
         //获取当前的操作系统名字
        String osName = environment.getProperty("os.name");
      
        //判断当前操作系统是不是Windows 10
        if(osName.contains("Windows")){
            return true;
        }

        return false;
    }
}

1.7 @Import

给容器中注册组件(bean),前面已经写了两种:

  • 包扫描+组件标注注解(@Service / @Controller / @Repository / @Componment),这种方式比较局限,只能用在自己写的类上
  • @Bean 可以注册第三方包里面的组件,使用类的构造函数完成

@Import(类名.class) 快速给容器中导入一个组件,id为类的全路径名,用在类上,属性为class类型的数组
比如,在容器中注册Color、Red类

@Configuration
//@Import:快速在容器中注册组件,id为类的全路径名
@Import({Color.class, Red.class})
public class MainConfig02 {
	。。。}
  • @Import中,还可以写一个选择器ImportSelector。选择器需要自己写一个类,实现ImportSelector接口,实现selectImports方法,这个方法返回一个String的数组,数组中就是要注册的bean的全路径名。
@Configuration
/* @Import:快速在容器中注册组件,id为类的全路径名
       类名.class
       选择器类.class
 */
@Import({Color.class, Red.class, MyImportSelector.class})
public class MainConfig02 {
	。。。}

自定义选择器类

//选择器类
public class MyImportSelector implements ImportSelector {
    /**
     * @param importingClassMetadata:可以获得@Import标注的类的所有注解信息
     * @return 返回一个String数组,里面要注册的bean的全路径名,id是全路径名
     */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"ewen.bean.Blue"};
    }
}

  • @Import 中还可以写一个注册器类,该类实现ImportBeanDefinitionRegistrar接口,实现registerBeanDefinitions方法,在方法中可以手工注册bean,并且设置bean的id。
@Configuration
/* @Import:快速在容器中注册组件,id为类的全路径名
       类名.class
       选择器类.class:实现ImportSelector接口的类
       注册类.class:实现ImportBeanDefinitionRegistrar接口的类
 */
@Import({Color.class, Red.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig02 {
	。。。}

注册类

//注册类
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     AnnotationMetadata:@Import标注类的注解信息
     BeanDefinitionRegistry:bean的注册类,通过这个类,调用registerBeanDefinition方法
                            可以手动的在容器中注册bean。也可以用这个类中的方法进行查询、移除bean
     */
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //先判断IOC容器中是否有蓝色和红色,如果都有,就注册一个RainBow
        boolean b1 = registry.containsBeanDefinition("ewen.bean.Blue");
        boolean b2 = registry.containsBeanDefinition("ewen.bean.Red");
        if(b1 && b2){
            //指定bean的定义信息(bean的类型、作用域等等)
            RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
            //指定bean的id
            registry.registerBeanDefinition("rainBow",beanDefinition);
        }

    }
}

1.8 FactoryBean

使用spring提供的FactoryBean(工厂bean)也可以给容器注册bean。FactoryBean 是一个接口,里面有三个方法,getObject,获取对象,getObjectType,获取对象类型,isSingleton,是否是单例模式。定义一个类实现FactoryBean接口,实现上面三个方法,然后在配置类中,使用@Bean注解来注册这个类,需要注意的是,通过getBean方法获取的是在getObject中生成的对象,而不是FactoryBean对象,可以在name前面加一个“&”获取FactoryBean 对象。

//自定义的FactoryBean,通过这个FactoryBean来给容器注册Bean
public class ColorFactoryBean implements FactoryBean<Color> {
    public Color getObject() throws Exception {
        return new Color();
    }

    public Class<?> getObjectType() {
        return Color.class;
    }
    //返回true,是单例,返回false不是单例
    public boolean isSingleton() {
        return true;
    }
}

配置类

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

2. Bean 的声明周期

bean创建—初始化—销毁的过程

  • bean的创建:单实例:在容器启动的时候创建对象。多实例:在每次获取的时候创建对象。
  • 初始化:对象创建完成,并且赋值好,调用初始化方法。
  • 销毁:单实例:容器关闭的时候。多实例:容器不会管理这个bean,容器不会调用销毁的方法。

2.1 容器管理bean的生命周期

我们可以自定义初始化和销毁方法,容器在bean进行到当前生命周期的时候来调用自定义的初始化和销毁方法。
指定bean的初始化和销毁方法

  • 通过@Bean注解,指定initMethod和destoryMethod 属性
  • 通过让Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑)接口;
  • 使用@PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化方法
    @PreDestroy:在容器销毁bean之前通知我们进行清理工作
  • BeanPostProcessor【interface】:bean的后置处理器,在bean初始化前后进行一些处理工作,需要定义一个类实现该接口,实现下面两个方法
    postProcessorBeforeInitialization:在初始化之前工作
    postProcessAfterInitialization:在初始化之后工作

BeanPostProcessor 原理

  • 调用populateBean方法给bean赋好值,执行initializeBean方法
  • nitializeBean方法中主要执行applyBeanPostProcessorsBeforeInitialization 遍历得到容器中所有的BeanPostProcessor;挨个执行beforeInitialization,一但返回null,跳出for循环,不会执行后面的postProcessBeforeInitialization方法
  • 执行完applyBeanPostProcessorsBeforeInitialization ,执行初始化方法,再执行applyBeanPostProcessorsAfterInitialization方法

Spring底层对 BeanPostProcessor 的使用
bean赋值,注入其他组件,@Autowired,生命周期注解功能,@Async等都使用的是xxx BeanPostProcessor。

3. 属性注入相关注解

3.1 @Value

xml中是在标签下使用进行属性注入的,可以使用@Value注解代替,@Value中,可以写:

  • 普通字符串
  • SpEL(Spring Expression Language):#{ },里面可以进行运算
  • 使用${}从配置文件中获取,必须在配置类中使用@PropertySource注解读取外部配置文件的k/v并保存到运行的环境变量中

3.2 @Autowired

自动注入,默认优先根据类型在容器中找对应的组件,将组件赋给@Autowired下的属性,如果容器中有多个相同类型的组件,会将属性名作为id在容器中查找组件。

  • @Qualifier:使用@Qualifier注解来指定,要注入的bean的id,而不是使用属性值作为id
  • @Primary:使用@Primary来指定首选的bean,这样,spring自动注入的时候,会将首选的bean注入

如果容器中没有相同类型的bean或者指定id的bean,会报错,可以修改required属性为false(默认为true),就不报错了。
@Autowired 还可以用在构造器、方法、参数上,参数默认从容器中获取。

3.3 @Resouce 和 @Inject

@Resouce 和 @Inject 都可以自动注入,@Resource(name="") 是根据bean的id自动注入的,不支持@Primary来指定首选bean。使用@Inject 要导入javax.inject包,注解里没有属性。这两个注解式java规范的,@Autowired是spring定义的。

3.4 XXXAware

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx),自定义组件类可以实现xxxAware;在创建对象的时候,会调用接口规定的方法注入相关组件;Aware,把Spring底层一些组件注入到自定义的Bean中;
xxxAware:底层使用xxxAwareProcessor, 如ApplicationContextAware ==》ApplicationContextAwareProcessor。在bean中获取applicationContext。

//实现ApplicationContextAware接口,可以在Color类中获得applicationContext
//实现BeanNameAware接口,可以获得bean的id
//实现EmbeddedValueResolverAware接口,可以解析字符串
@Component
public class Color implements ApplicationContextAware, BeanNameAware,EmbeddedValueResolverAware {
    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    public void setBeanName(String name) {
        System.out.println("bean 的name:" + name);
    }

    //StringValueResolver解析字符串
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        String s = resolver.resolveStringValue("你好${os.name} #{18 * 20}");
        System.out.println("解析的字符串:" + s);
    }

3.5 @Profile

指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
比如,在tes环境下,将dataSourceTest注册到容器,开发dev环境下,将dataSourceDev注册到容器

参考:
Spring注解驱动教程

猜你喜欢

转载自blog.csdn.net/weixin_43691723/article/details/106551946
今日推荐