侧边栏壁纸
  • 累计撰写 98 篇文章
  • 累计创建 85 个标签
  • 累计收到 9 条评论

Spring常用注解 - 祝你阅读源码更上一层楼

bearjun
2022-02-17 / 0 评论 / 0 点赞 / 492 阅读 / 10,345 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-11,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

Spring 5.x 那些常见的注解

说起spring5中那些常见的注解,你可肯定会说@Controller、@Service、@Repository.....这些常用的,但是当我们查看springboot源码的时候,我们会发现,原来spring给我们提供了这么多注解啊。

明白这些注解很有必要,不管你现在用不用得上。

下面我们一起来看看这些注解的的基本功能,后面将依次展开来说说。

注解功能
@Bean容器中注册组件,代替原来的<bean标签
@Configuration声明这是一个配置类,替换以前配置xml文件
@ComponentScan包扫描,扫描@Controller、@Service、@Repository、@Component注入spring容器中
@Conditional按条件注入
@Primary同类组件如果有多个,标注主组件
@Lazy组件懒加载(最后使用的时候才创建)
@Scope声明组件的作用范围(SCOPE_PROTOTYPE,SCOPE_SINGLETON)
@DependsOn组件之间声明依赖关系
@Component@Controller、@Service、@Repository
@Indexed加速注解,所有标注了 @Indexed 的组件,直接会启动快速加载
@Order数字越小优先级越高,越先工作
@Import导入第三方jar包中的组件,或定制批量导入组件逻辑
@ImportResource导入以前的xml配置文件,让其生效
@Profile基于多环境激活
@PropertySource外部properties配置文件和JavaBean进行绑定.结合ConfigurationProperties
@PropertySources@PropertySource组合注解
@Autowired自动装配
@Qualifier精确指定
@Value取值、计算机环境变量、JVM系统。@Value(“$”)
@Lookup单例组件依赖非单例组件,非单例组件获取需要使用方法

注意:@Indexed 需要引入依赖

org.springframework spring-context-indexer true

部分注解详解

@Configuration && @Bean

我们都知道,在早期spring的项目中,我们都需要建立一个springContext.xml来作为配置文件,然后通过bean来进行注入。

@configuration就相当于xml的配置文件,充当配置文件的配置类。而@Bean就相当于原来的bean标签。

<!--springContext.xml-->
<?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.bearjun.bean.User"></bean>

</beans>
class User {

    private Integer userId;
    private String username;
    private String password;
    
    // 省略get、set、toString
}

@Configuration
public class SpringConfig {

    @Bean("user1")
    User user1() {
        return new User(1, "bear", "12212");
    }
}

public class SpringTest {
    
    @Test
    public void test1() {
        // 通过配置文件的方式获取所有的bean
        ApplicationContext context = new ClassPathXmlApplicationContext("springContext.xml");
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println("xml=>" + beanDefinitionName);
        }
        // 最后打印:xml=>user


        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        String[] beanDefinitionName = annotationConfigApplicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionName) {
            System.out.println("annotation = > " + name);
        }
        // 最后打印去处默认配置后:
        // annotation = > springConfig
		// annotation = > user1
    }
}

注意:@Bean配置的时候,方法名为默认的id类型,当然也可以@Bean("id")这样来指定id。另外,@Bean注入的对象默认都是单例的。

@Lazy && @Scope

前面我们知道,通过@Bean可以直接把对象注册到容器中,那注册进去的对象怎么设置成原型呢?可以懒加载嘛?

// SpringConfig.class
/**
 * @Scope可选内容为:
 * ConfigurableBeanFactory#SCOPE_PROTOTYPE  原型模式
 * ConfigurableBeanFactory#SCOPE_SINGLETON  单例模式
 * org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request模式,同一次强求一个对象
 * org.springframework.web.context.WebApplicationContext#SCOPE_SESSION session模式,同一个session一个对象
 */
@Bean("user1")
@Scope
User user1() {
	return new User(1, "bear", "12212");
}

// SpringTest.class
User user1 = (User) annotationConfigApplicationContext.getBean("user1");
User user2 = (User) annotationConfigApplicationContext.getBean("user1");
/**
 * 当@Scope("singleton")时,为false
 * 当@Scope("prototype")时,为true
 */
System.out.println(user1 == user2); 

@Lazy只是在单例模式下使用,让对象需要创建的时候才注入到容器。

@ComponentScan

最早用spring项目的时候,@Controller、@Service、@Repository是不会添加到spring容器的。这个时候,我们通常使用一个包扫描的配置

<context:component-scan base-package="com.xxx 注解来使这些类加载到spring容器中。

@ComponentScan就相当于context:component-scan

// 1、添加Controller、Service、Repository
@Controller
public class UserController {
}

@Service
public class UserService {
}

@Repository
public class UserDao {
}

// 2、通过上面的test1方法,发现没有注入到spring容器

// 3、在springContext.xml中添加
<context:component-scan base-package="com.bearjun"></context:component-scan>
Controller、Service、Dao注入到spring容器中了
    
// 4、在SpringConfig的类上添加 @ComponentScan(value = "com.bearjun")
Controller、Service、Dao也注入到spring容器中了

@ComponentScan提供了很多包扫描的属性

// 当你想扫描多个包的时候,你可以配置value为一个数组
@ComponentScan(value = {"com.bearjun.controller", "com.bearjun.service"})

什么?你不想要某个包或者注解被扫描?

excludeFilters中Filter的type有很多类型供我们选择,常见的如下

  • FilterType.ANNOTATION 根据注解类型
  • FilterType.ASSIGNABLE_TYPE 按照给定的类型
  • FilterType.CUSTOM 自定义
// FilterType.ANNOTATION类型。扫描com.bearjun整包,排除@Controller注解
@ComponentScan(value = {"com.bearjun"}, excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)
})

// FilterType.ASSIGNABLE_TYPE类型。扫描com.bearjun整包,排除UserDao类
@ComponentScan(value = {"com.bearjun"}, excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = UserDao.class)
})

// FilterType.CUSTOM类型。
// 自定义一个类,实现 org.springframework.core.type.filter.TypeFilter;
public class MyFilter implements TypeFilter {

    /**
     * @param metadataReader 当前正在扫描的类 ComponentScan value的值的信息
     * @param metadataReaderFactory 获取其他类的信息
     * @return
     */
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) {

        // 当前类的注解信息
        AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
        // 当前的扫描的类信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 当前类资源
        Resource resource = metadataReader.getResource();

        String className = classMetadata.getClassName();

        // 所有的 controller 返回 true
        if (className.toLowerCase().contains("controller")) {
            return true;
        }
        return false;
    }
}

// 配置。扫描com.bearjun整包,但是满足 MyFilter 返回值为 true 的过滤掉
@ComponentScan(value = {"com.bearjun"}, excludeFilters = {
        @ComponentScan.Filter(type = FilterType.CUSTOM, value = MyFilter.class)
})

@Conditional

@Conditional按照一定的条件判断,满足条件才注入到spring容器中。

@Configuration
public class SpringConfig {

    /**
     *
     * @return
     */
    @Bean("bear")
    User bear() {
        return new User(1, "bear", "123123");
    }

    @Bean("bearjun")
    @Conditional(MyCondition.class)
    User bearjun() {
        return new User(2, "bearjun", "321321");
    }

}

public class MyCondition implements Condition {
    /**
     * @param context  上下文(可以获取各种需要的信息)
     * @param metadata 注释信息
     * @return
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        /**
         * 获取bean的注册信息,可以新增,移除,判断。。bean
         */
        BeanDefinitionRegistry registry = context.getRegistry();
        /**
         * 获取项目运行的环境
         */
        Environment environment = context.getEnvironment();
        /**
         * 获取bean工厂
         */
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        /**
         * 获取类加载器活资源加载器
         */
        ClassLoader classLoader = context.getClassLoader();
        ResourceLoader resourceLoader = context.getResourceLoader();

        /**
         * 如果项目中是否存在一个bean叫bear
         */
        return context.getRegistry().containsBeanDefinition("bear");
    }
}

@Test
public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

    Map<String, User> beansOfType = context.getBeansOfType(User.class);
    beansOfType.forEach((key, value)->{
        System.out.println(key + " ==== " + value);
    });
}
// 最后输出的结果为:
bear ==== User{userId=1, username='bear', password='123123'}
bearjun ==== User{userId=2, username='bearjun', password='321321'}

// 如果我们把@Bean("bear")换成@Bean("bear1")
最后的结果为:bear1 ==== User{userId=1, username='bear', password='123123'}

注意:@Conditional注解也可以还在类上,使用和加载方法上一样

@Import

根据前面的学习,我们现在知道,往spring中注册组件:

  • @ComponentScan + @Controller / @Service / @Repository

  • @Bean

  • @Import :默认的id为全类名

@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {String.class, Math.class})
public class SpringConfig {

}

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    String[] beanDefinitionName = context.getBeanDefinitionNames();
    for (String name : beanDefinitionName) {
        // 打印存在
        // annotation = > java.lang.String
        // annotation = > java.lang.Math
    	System.out.println("annotation = > " + name);
	}
}
  • @Import(value="ImportSelector.class")
@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {MyImportSelector.class})
public class SpringConfig {

}

public class MyImportSelector implements ImportSelector {

    /**
     * @param importingClassMetadata 获取标注了当前@Import注解的所以注解信息
     * @return
     */
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"java.lang.String"};
    }
}

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    String[] beanDefinitionName = context.getBeanDefinitionNames();
    for (String name : beanDefinitionName) {
        // 打印存在
        // annotation = > java.lang.String
    	System.out.println("annotation = > " + name);
	}
}
  • @Import(value="ImportBeanDefinitionRegistrar.class")
@Configuration
@ComponentScan(value = "com.bearjun")
@Import(value = {ImportBeanDefinitionRegistrar.class})
public class SpringConfig {

}

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    /**
     * @param importingClassMetadata 当前类的注解信息
     * @param registry               BeanDefinition注册类
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // bean注册器中是否存在bearjun的组件
        if (registry.containsBeanDefinition("bearjun")) {
        	// 往组件中添加id为string的组件
        	BeanDefinition definition = new RootBeanDefinition("java.lang.String");
            registry.registerBeanDefinition("string", definition);
        }
    }
}

@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    String[] beanDefinitionName = context.getBeanDefinitionNames();
    for (String name : beanDefinitionName) {
        // 打印存在
        // annotation = > string
    	System.out.println("annotation = > " + name);
	}
}
  • FactoryBean

spring系列笔记 - 第⼋章 Spring工厂创建复杂对象(3种方式)

@Lookup

单例组件依赖非单例组件,非单例组件获取需要使用方法。

// Car.class
@Component
@Scope("prototype")
public class Car {
    private String brand;
    
    // 省略get/set/toString
}

// User.class
@Component
public class User {

    private Integer userId;
    private String username;
    private String password;
    private Car car;

    @Lookup
    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    // 省略其他的get/set/toString
}

// SpringTest#test3
@Test
public void test3() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

    User user1 = (User) context.getBean("user");
    User user2 = (User) context.getBean("user");
    System.out.println(user1 == user2);
    // 打印:true

    Car car1 = user1.getCar();
    Car car2 = user2.getCar();
    System.out.println(car1 == car2);
    // 如果user#getCar不加@Lookup注解,打印为true
    // 加@Lookup注解,打印为false
}

需要注意的是:

  • @Lookup需要加在get方法上才生效
  • 单例模式的组件(上述为User)不能是从配置类中放回的bean,否则 @Lookup 不生效

And please remember that lookup methods won't work on beans returned from methods in configuration classes

请记住,查找方法不适用于从配置类中的方法返回的 bean

0

评论区