Spring的常用注解使用

本文总结了一些Spring 中常用的注解。 以及用法。

@ComponentScan

  • value指定要扫描的包。
  • excludeFilters 排除哪些不要扫描的类 @ComponentScan.Filter 指定排除哪些包。 type 指定扫描的时候按照什么样的规则排除。 classes 排除哪些
  • includeFilters 指定扫描的时候只需要包含哪些组件
    • FilterType.ANNOTATION: 按照注解
    • FilterType.ASSIGNABLE_TYPE: 按照给定的类型
    • FilterType.ASPECTJ: 使用 ASPECTJ表达式
    • FilterType.REGEX: 正则表达式
    • FilterTYpe.CUSTOM: 使用自定义规则
@ComponentScan(value = "cn.fllday",excludeFilters =
        {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class, Service.class})
        }, includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Repository.class, Component.class}),
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class})
        },useDefaultFilters = false)

那么如何自定Type FIlter呢?
只需要实现 TypeFilter 接口中的 match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)

MyCustomFilter.java

public class MyCustomFilter 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();
        if (className.contains("er")){
            return true;
        }
        return false;
    }
}

使用方式 @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyCustomFilter.class})

往容器中添加 组件的几种方式
@Component , @Service , @Controller , Repository 这一组就是直接标注到 类上面 , 利用 @ComponentScan 扫描就可以注入到 ioc 容器中了。
@Bean

  • initMethod 指定初始化方法
  • destroyMethod 指定销毁方式
@Configuration
@ComponentScan({"cn.fllday.beans"})
public class MainConfigOfLifeCycle {
    @Bean(initMethod = "init",destroyMethod = "destory")
    public Car car(){
        return new Car();
    }
}

@Scope

- prototype : 多实例 : ioc 启动的时候不会调用方法创建方法放入容器中,而是每次获取bean 的时候。才会调用方法,创建对象。
- singleton: 单实例 (默认值) : ioc 容器启动会调用 方法创建对象放到 ioc 容器中。以后每次获取都是从容器中拿
- request: 同一个请求创建一个实例
- sesson: 同一个session 创建一个实例
    @Scope("prototype")
    @Bean(value = "person",initMethod = "init",destroyMethod = "destory")
    public Person person(){
        System.out.println("给容器中添加 Person...");
        return new Person("张三",25);
    }

当我们通过ApplicationContext ioc 容器进行获取这个Person 的时候。 使用 prototype ,会 每次获取,都会执行一次 person() 方法。 使用 singleton 是 ioc 容器启动后就会调用方法创建对象 ioc 容器中。 以后每次获取都是从容器中拿。

    @Test
    public void test1(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
		Person person1 =  applicationContext.getBean(Person.class);
		Person person2 =  applicationContext.getBean(Person.class);
		applicationContext.close();
    }

通过这个方法你可以 查看 使用 prototype 和 singleton 分别打印了 几次 给容器中添加 Person, 还有 initMethod 中 的 init 方法。
init()destory() 是定义在Person 类中的方法。 分别打印了 一句话。 调用 init , 和 调用 destory
调用 applicationContext.close() 方法后,会发现 prototype 只会执行 init , destory 并不会执行。 而 singleton 在容器初始化的时候,调用一次init,关闭的时候 调用一次 destory

我们通过 实现 InitializingBean 自定义初始化逻辑和 实现 DisposableBean 自定义销毁逻辑。 实现和上述方式一样的效果

@Component
public class Cat implements InitializingBean, DisposableBean {
    public Cat() {
        System.out.println("cat 构造器");
    }
    public void destroy() throws Exception {
        // TODO bean 实例销毁调用
        System.out.println("cat destroy");
    }
    public void afterPropertiesSet() throws Exception {
        // TODO bean 实例初始化完成操作
        System.out.println("cat afterOrioertiesSet ");
    }
}

通过 ApplicationContext.getBean() 方法来查看获取 bean 实例的时候的效果。

通过使用 JSR 250 来实现初始化和销毁信息

  • @PostConstruct: 在bean 创建完成并且属性赋值完成,来执行初始化方法
  • @PreDestroy: 在容器销毁 bean 之前通知我们进行清理工作
@Component
public class Dog {
    public Dog() {
        System.out.println("Dog 构造器");
    }
    // 对象创建并赋值之后调用
    @PostConstruct
    public void init(){
        System.out.println("Dog...@PostConstruct");
    }
    @PreDestroy
    public void destory(){
        System.out.println("Dog...@PreDestroy");
    }
}

通过 getBean 方法来查看效果

还有一种比较重要的方式
通过 BeanProcessor Bean 的 后置处理器, 在Bean的初始化前后进行一些处理工作

  • postProcessBeforeInitialization : 在初始化之前执行
  • postProcessAfterInitialization : 在初始化之后执行
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization:" + beanName +" bean : " + bean);
        return bean;
    }
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization:" + beanName +" bean : " + bean);
        return bean;
    }
}

调用 getBean 发现这个和之前的不太一样了。 因为这个并不是指定某一个 bean 初始化的时候处理。而是每一个bean 在初始化前后 都会进行一个 处理。

Spring 底层 根据这个 后置处理器。 做了一些操作比如说 , 我们之前用的 给一个 组件类 拿到IOC 容器。 通过实现 ApplicationContextAware 将ApplicationContext 参数赋值给 自己定义的成员变量,那么我们就拿到了 IOC 容器。

public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext var1) throws BeansException;
}

我们通过搜索 还发现 与之对应的还有一个 ApplicationContextAwareProcessor 这么一个类。
里面的在初始化 Bean 对这个 bean 做一些操作。有兴趣的可以自己去看一下源码。

@Lazy

 * 懒加载:
 *      单实例bean : 默认在容器启动的时候创建对象
 *      懒加载: 容器启动不创建对象,第一次使用 (获取bean) 的时候,创建对象,并初始化

@Condition
按照一定的条件进行判断,满足条件给容器中注册bean。 我们点开这个注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[] value();
}

发现 value 是一个数组, 并这个是 实现Codition 这个接口的类

public interface Condition {
    boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}

当我们实现这个接口。 并且返回值问 true 的时候, 才会 给容器中 注入这个 bean. 如果根据条件判断 为 false 那么 就不往这个 ioc 容器中注入 bean

我们注册一个 Person Bean , 判断如果 是 window 系统就注入 id 为 bill 的bean, 如果不是 就不注入。

实现 Condition 这个接口

 public class WindowsCondition implements Condition {
    /**
     *  ConditionContext 判断
     * @param conditionContext 判断条件能使用的上下文环境
     * @param annotatedTypeMetadata 注释信息
     * @return
     */
    public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
        // TODO 是否 windows 系统
        // 获取到 ioc 的 bean 工厂
        ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
        // 获取类加载器
        ClassLoader classLoader = conditionContext.getClassLoader();
        // 获取当前运行环境信息
        Environment environment = conditionContext.getEnvironment();
        // 获取 bean 定义的注册类
        BeanDefinitionRegistry beanDefinitionRegistry = conditionContext.getRegistry();
        // 查看ioc容器中是否存在 person 这个bean
        boolean person = beanDefinitionRegistry.containsBeanDefinition("person");
        String osName = environment.getProperty("os.name");
        if (osName.toLowerCase() .contains("windows".toLowerCase())){
            return true;
        }
        return false;
    }
}

注入 bean 的方法

    @Conditional({WindowsCondition.class})
    @Bean("bill")
    public Person person01(){
        return new Person("Bill Gates",66);
    }

我们启动程序。 通过 getBean 发现这个 bean 已经被注入进去了。

我们修改虚拟机参数。 通过 -Dos.name=linux 。 来启动虚拟机。 发现 getBean 并没有获取到这个 bean。

@Import 快速给容器导入一个组件。

  1. @Import(要导入组建的类名) 容器中就会自动注册这个组件,id 默认为这个类的全类名
@Import({Red.class})

就会给 ioc 容器中添加 Red 这么一个组件,id 为 包名+类名

  1. ImportSelector: 实现这个接口。返回需要导入的组件的全类名数组。
/**
 *  自定义逻辑返回需要导入的组件。
 */
public class MyImportSelector implements ImportSelector {
    /**
     * @param annotationMetadata    当前标注 @Import 注解的类的所有注解信息
     * @return 导入到容器中的组件全类名
     */
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
//         可以返回空数组,不能返回null
        return new String[]{"cn.fllday.beans.Color","cn.fllday.beans.Red"};
    }
}

通过返回的 String 数组。来导入这些 bean。

还记得我之前的一篇 SpringBoot 的自动装配原理吗。 里面的 EnableAutoConfiguration 注解中也有 一个 @Import 注解, 然后通过这个注解 引入了 AutoConfigurationImportSelector 这个类。 这个类实现了 ImportSelector 的子接口 DeferredImportSelector 通过 selectImports 方法 返回了一个 String 数组。 来创建那么多Bean的。
原文放在这里。有兴趣可以看一下
SpringBoot的自动配置原理

通过Spring提供的BeanFactory 也可以往 IOC 容器中添加一个bean

比如我们现在往 ioc 容器中添加一个 Color 的 bean

/**
 *  创建Spring 定义的 FactoryBean
 */
public class ColorFactoryBean implements FactoryBean<Color> {
    /**
     *  返回一个 color 对象,这个对象会添加到容器中
     * @return
     * @throws Exception
     */
    public Color getObject() throws Exception {
        System.out.println("创建 color ");
        return new Color(new Car());
    }
    public Class<?> getObjectType() {
        return Color.class;
    }
    /**
     * 控制是否单例
     * @return true 这个bean 是单实例,在容器中保存一份
     *          false 这个 bean 是多实例。每次获取都会创建一份新实例
     */
    public boolean isSingleton() {
        return false;
    }
}

注册 bean

    /**
     *  实际上注册bean 并不是 ColorFactoryBean 而是Color 类型的。
     * @return
     */
    @Bean
    public ColorFactoryBean colorFactoryBean(){
        return new ColorFactoryBean();
    }

如果我们需要 获取到 ColorFactoryBean 那么我们这样子写。
Object obj2 = context.getBean("&colorFactoryBean");
获取到的就是 ColorFactoryBean 这个类的对象了。

@Autowired : 自定注入 ,

  • 默认优先按照类型去容器中查找对应的组件,applicationContext.getBean(BookDao.class);
  • 如果找到多个类型相同的Bean, 那么在将属性的名称作为组件的 id 去容器中查找 applicationContext.getBean(“bookDao”)
  • @Qualifier(“bookDao”) 使用@Qualifier 找到 BookDao 的 id 为 bookDao的 对象
  • @Autowired(required = false) 使用required 如果不存在bean 也不会报错
  • @Primary 默认使用首选的 bean。 BookDao 可能有两个或者多个 bean 对象。 那么使用@Primary 就会默认装备@Primary 指定的 bean 对象了 和 @Qualifier 不冲突

Spring 还支持使用@Resource(JSR250) 和 @Inject (JSR303) 注解

  1. @Resource 可以和 @Autowired 一样实现自动装配功能,默认是按照 属性名称装配的
    没有能够支持 @Primary 功能 没有支持 @Autowired (required = false)
  2. @Inject
    需要导入 javax.inject 的包, 和 Autowired 的功能一样, 但是没有 required 的功能

@Autowired : 可以使用在 构造器, 参数, 方法, 属性

  1. 标注在方法位置 方法的参数会从 ioc 容器中获取
  2. 标注在构造器上 构造器的参数会从 ioc 容器中获取, 如果组件中只有一个有参构造器,这个有参构造器的 @Autowired 可以省略,参数位置的组件还是可以自动从容器中获取
  3. 标注在参数上。 参数的值是从 ioc 容器中获取
  4. @Bean 标注的方法创建对象的时候,方法的参数的值从容器中获取

@Profile: 指定组件在哪个环境的情况下才能被注册到容器中, 不指定, 任何环境下都能注册这个组件

  1. 加了环境表示的 bean, 只有这个环境被激活的时候才能注册到容器中, 默认是 default 环境
  2. 写在配置类上, 只有是指定的环境的时候, 整个配置类里面的所有配置才能生效
  3. 没有标注环境表示的bean,在任何环境下都会加载。
@Profile("test")
@Configuration
public class MainConfigOfProfile {

    // 开发环境
    @Profile("dev")
    @Bean
    public Car devCar(){
        return new Car();
    }

    @Profile("test")
    @Bean
    public Car testCar(){
        return new Car();
    }

    @Profile("prod")
    @Bean
    public Car prodCar(){
        return new Car();
    }
}

启动方式 设置 运行环境

    // 命令行参数 在虚拟机参数位置加载 -Dspring.profiles.active=test
    @Test
    public void test1(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 1.创建一个applicationContext
        // 2 设置需要激活的环境
        context.getEnvironment().setActiveProfiles("dev","test");
        context.register(MainConfigOfProfile.class);
        context.refresh();
        printBeans(context);
    }
    private void printBeans(ApplicationContext context){
	    String[] beanNames = context.getBeanDefinitionNames();
	    for (String name : beanNames)
	    {
	        System.out.println(name);
	    }
    }

@Value 使用@Value 赋值

  • 基本数值
  • 可以写SpEL 使用 #{}
  • 可以写 ${}: 取出配置文件中的值, (在运行环境变量里的值)
    @Value("gss")
    private String name;
    @Value("#{2-1}")
    private Integer age;
    @Value("${person.nickName}")
    private String nickName;
	/**
	 * 省略 getter/setter 
	 * 
	 */ 

${person.nickName} 需要配置 properties 文件才行。

在类路径下创建一个 person.properties

person.nickName=xzs

需要加载的配置类

@Configuration
// 通过@PropertySource 读取配置文件中的 kv 保存到运行中的环境变量中。 加载完外部的配置文件,以后使用${} 取出配置文件的值
@PropertySource(value = "classpath:/person.properties")
public class MainConfigOfPropertyValue {
    @Bean
    public Person person(){
        return new Person();
    }
}

测试程序中 获取 person 这个 bean 打印值
·
Person{name=‘gss’, age=1, nickName=‘xzs’}

也可以使用

Environment environment = context.getEnvironment();
String nickName = enviroment.getProperty("person.nickName");

获取到 nickName 的值。 context 是 ioc 容器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值