写点什么

小伙伴们在催更 Spring 系列,于是我写下了这篇注解汇总!!

用户头像
冰河
关注
发布于: 2021 年 06 月 15 日
小伙伴们在催更Spring系列,于是我写下了这篇注解汇总!!

写在前面

由于在更新其他专题的文章,Spring 系列文章有很长一段时间没有更新了,很多小伙伴都在公众号后台留言或者直接私信我微信催更 Spring 系列文章。


看来是要继续更新 Spring 文章了。想来想去,写一篇关于 Spring 中注解相关的文章吧,因为之前更新 Spring 系列的文章一直也是在更新 Spring 注解驱动开发。这篇文章也算是对之前文章的一个小小的总结吧,估计更新完这篇,我们会进入 Spring 的 AOP 章节的更新。


没有看过 Spring 其他文章的小伙伴,可以到【冰河技术】公号的【Spring系列】专题中进行阅读。


文章已收录到:


https://github.com/sunshinelyz/technology-binghe


https://gitee.com/binghe001/technology-binghe

xml 配置与类配置

1.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/sp  <bean id="person" class="com.binghe.spring.Person"></bean></beans>
复制代码


获取 Person 实例如下所示。


public static void main( String[] args ){  ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");  System.out.println(ctx.getBean("person"));}
复制代码

2.类配置

@Configurationpublic class MainConfig {    @Bean    public Person person(){      return new Person();    }}    
复制代码


这里,有一个需要注意的地方:通过 @Bean 的形式是使用的话, bean 的默认名称是方法名,若 @Bean(value="bean 的名称")那么 bean 的名称是指定的 。


获取 Person 实例如下所示。


public static void main( String[] args ){  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);  System.out.println(ctx.getBean("person"));}
复制代码

@CompentScan 注解

我们可以使用 @CompentScan 注解来进行包扫描,如下所示。


@Configuration@ComponentScan(basePackages = {"com.binghe.spring"})  public class MainConfig {}  
复制代码

excludeFilters 属性

当我们使用 @CompentScan 注解进行扫描时,可以使用 @CompentScan 注解的 excludeFilters 属性来排除某些类,如下所示。


@Configuration@ComponentScan(basePackages = {"com.binghe.spring"},excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {PersonService.class})})public class MainConfig {}
复制代码

includeFilters 属性

当我们使用 @CompentScan 注解进行扫描时,可以使用 @CompentScan 注解的 includeFilters 属性将某些类包含进来。这里需要注意的是:需要把 useDefaultFilters 属性设置为 false(true 表示扫描全部的)


@Configuration@ComponentScan(basePackages = {"com.binghe.spring"},includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class, PersonService.class})},useDefaultFilters = false)public class MainConfig {}
复制代码

@ComponentScan.Filter type 的类型

  • 注解形式的 FilterType.ANNOTATION @Controller @Service @Repository @Compent

  • 指定类型的 FilterType.ASSIGNABLE_TYPE @ComponentScan.Filter(type =FilterType.ASSIGNABLE_TYPE,value = {Person.class})

  • aspectj 类型的 FilterType.ASPECTJ(不常用)

  • 正则表达式的 FilterType.REGEX(不常用)

  • 自定义的 FilterType.CUSTOM


public enum FilterType {    //注解形式 比如@Controller @Service @Repository @Compent    ANNOTATION,    //指定的类型    ASSIGNABLE_TYPE,    //aspectJ形式的    ASPECTJ,    //正则表达式的    REGEX,    //自定义的    CUSTOM}
复制代码

FilterType.CUSTOM 自定义类型

public class CustomFilterType implements TypeFilter {@Overridepublic boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {    //获取当前类的注解源信息    AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();    //获取当前类的class的源信息    ClassMetadata classMetadata = metadataReader.getClassMetadata();    //获取当前类的资源信息    Resource resource = metadataReader.getResource();   return classMetadata.getClassName().contains("Service");}    @ComponentScan(basePackages = {"com.binghe.spring"},includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,value = CustomFilterType.class)},useDefaultFilters = false)public class MainConfig {}
复制代码

配置 Bean 的作用域对象

不指定 @Scope

在不指定 @Scope 的情况下,所有的 bean 都是单实例的 bean,而且是饿汉加载(容器启动实例就创建好了)


@Beanpublic Person person() {  return new Person();}  
复制代码

@Scope 为 prototype

指定 @Scope 为 prototype 表示为多实例的,而且还是懒汉模式加载(IOC 容器启动的时候,并不会创建对象,而是在第一次使用的时候才会创建)


@Bean@Scope(value = "prototype")public Person person() {    return new Person();}
复制代码

@Scope 取值

  • singleton 单实例的(默认)

  • prototype 多实例的

  • request 同一次请求

  • session 同一个会话级别

懒加载

Bean 的懒加载 @Lazy(主要针对单实例的 bean 容器启动的时候,不创建对象,在第一次使用的时候才会创建该对象)


@Bean@Lazypublic Person person() {  return new Person();}
复制代码

@Conditional 条件判断

场景,有二个组件 CustomAspect 和 CustomLog ,我的 CustomLog 组件是依赖于 CustomAspect 的组件应用:自己创建一个 CustomCondition 的类 实现 Condition 接口


public class CustomCondition implements Condition {/****@param context* @param metadata* @return*/    @Override    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {        //判断容器中是否有CustomAspect的组件        return context.getBeanFactory().containsBean("customAspect");    }  } 
public class MainConfig { @Bean public CustomAspect customAspect() { return new CustomAspect(); } @Bean @Conditional(value = CustomCondition.class) public CustomLog customLog() { return new CustomLog(); }}
复制代码

向 IOC 容器添加组件

(1)通过 @CompentScan +@Controller @Service @Respository @compent。适用场景: 针对我们自己写的组件可以通过该方式来进行加载到容器中。


(2)通过 @Bean 的方式来导入组件(适用于导入第三方组件的类)


(3)通过 @Import 来导入组件 (导入组件的 id 为全类名路径)


@Configuration@Import(value = {Person.class})public class MainConfig {}
复制代码


通过 @Import 的 ImportSeletor 类实现组件的导入 (导入组件的 id 为全类名路径)


public class CustomImportSelector implements ImportSelector {      @Override    public String[] selectImports(AnnotationMetadata importingClassMetadata) {      return new String[]{"com.binghe.spring"};    }} Configuration@Import(value = {Person.class}public class MainConfig {}
复制代码


通过 @Import 的 ImportBeanDefinitionRegister 导入组件 (可以指定 bean 的名称)


public class DogBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {    @Override    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {        //创建一个bean定义对象        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Dog.class);        //把bean定义对象导入到容器中        registry.registerBeanDefinition("dog",rootBeanDefinition);    }} @Configuration@Import(value = {Person.class, Car.class, CustomImportSelector.class, DogBeanDefinitionRegister.class})public class MainConfig {}
复制代码


通过实现 FacotryBean 接口来实现注册 组件


public class CarFactoryBean implements FactoryBean<Car> {    @Override    public Car getObject() throws Exception {      return new Car();    }     @Override    public Class<?> getObjectType() {      return Car.class;    } 
@Override public boolean isSingleton() { return true; }}
复制代码

Bean 的初始化与销毁

指定 bean 的初始化方法和 bean 的销毁方法

由容器管理 Bean 的生命周期,我们可以通过自己指定 bean 的初始化方法和 bean 的销毁方法


@Configurationpublic class MainConfig {    //指定了bean的生命周期的初始化方法和销毁方法.@Bean(initMethod = "init",destroyMethod = "destroy")    public Car car() {      return new Car();    }}
复制代码


针对单实例 bean 的话,容器启动的时候,bean 的对象就创建了,而且容器销毁的时候,也会调用 Bean 的销毁方法


针对多实例 bean 的话,容器启动的时候,bean 是不会被创建的而是在获取 bean 的时候被创建,而且 bean 的销毁不受 IOC 容器的管理

通过 InitializingBean 和 DisposableBean 实现

通过 InitializingBean 和 DisposableBean 个接口实现 bean 的初始化以及销毁方法


@Componentpublic class Person implements InitializingBean,DisposableBean {    public Person() {      System.out.println("Person的构造方法");    }     @Override    public void destroy() throws Exception {      System.out.println("DisposableBean的destroy()方法 ");    }     @Override    public void afterPropertiesSet() throws Exception {      System.out.println("InitializingBean的 afterPropertiesSet方法");    }}
复制代码

通过 JSR250 规范

通过 JSR250 规范 提供的注解 @PostConstruct 和 @ProDestory 标注的方法


@Componentpublic class Book {    public Book() {      System.out.println("book 的构造方法");    }     @PostConstruct    public void init() {      System.out.println("book 的PostConstruct标志的方法");    }     @PreDestroy    public void destory() {      System.out.println("book 的PreDestory标注的方法");    }}
复制代码

通过 BeanPostProcessor 实现

通过 Spring 的 BeanPostProcessor 的 bean 的后置处理器会拦截所有 bean 创建过程


  • postProcessBeforeInitialization 在 init 方法之前调用

  • postProcessAfterInitialization 在 init 方法之后调用


@Componentpublic class CustomBeanPostProcessor implements BeanPostProcessor {    @Override    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {      System.out.println("CustomBeanPostProcessor...postProcessBeforeInitialization:"+beanName);       return bean;    }     @Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        System.out.println("CustomBeanPostProcessor...postProcessAfterInitialization:"+beanName);        return bean;    }}  
复制代码

BeanPostProcessor 的执行时机

populateBean(beanName, mbd, instanceWrapper)initializeBean{    applyBeanPostProcessorsBeforeInitialization()    invokeInitMethods{    isInitializingBean.afterPropertiesSet()    自定义的init方法}applyBeanPostProcessorsAfterInitialization()方法}
复制代码

通过 @Value +@PropertySource 来给组件赋值

public class Person {    //通过普通的方式    @Value("独孤")    private String firstName;    //spel方式来赋值    @Value("#{28-8}")    private Integer age;    通过读取外部配置文件的值    @Value("${person.lastName}")    private String lastName;} @Configuration@PropertySource(value = {"classpath:person.properties"}) //指定外部文件的位置public class MainConfig {    @Bean    public Person person() {        return new Person();    }}
复制代码

自动装配

@AutoWired 的使用

自动注入


@Repositorypublic class CustomDao {} @Servicepublic class CustomService {    @Autowired    private CustomDao customDao;
复制代码


结论:(1)自动装配首先时按照类型进行装配,若在 IOC 容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配


@Autowiredprivate CustomDao customDao;
复制代码


比如,我容器中有二个 CustomDao 类型的组件 一个叫 CustomDao 一个叫 CustomDao2 那么我们通过 @AutoWired 来修饰的属性名称时 CustomDao,那么拿就加载容器的 CustomDao 组件,若属性名称为 tulignDao2 那么他就加载的时 CustomDao2 组件


(2)假设我们需要指定特定的组件来进行装配,我们可以通过使用 @Qualifier("CustomDao")来指定装配的组件或者在配置类上的 @Bean 加上 @Primary 注解


@Autowired@Qualifier("CustomDao")private CustomDao customDao2
复制代码


(3)假设我们容器中即没有 CustomDao 和 CustomDao2,那么在装配的时候就会抛出异常


No qualifying bean of type 'com.binghhe.spring.dao.CustomDao' available
复制代码


若我们想不抛异常 ,我们需要指定 required 为 false 的时候可以了


@Autowired(required = false)@Qualifier("customDao")private CustomDao CustomDao2;
复制代码


(4)@Resource(JSR250 规范)功能和 @AutoWired 的功能差不多一样,但是不支持 @Primary 和 @Qualifier 的支持


(5)@InJect(JSR330 规范)需要导入 jar 包依赖,功能和支持 @Primary 功能 ,但是没有 Require=false 的功能


<dependency>    <groupId>javax.inject</groupId>    <artifactId>javax.inject</artifactId>    <version>1</version></dependency>
复制代码


(6)使用 @Autowired 可以标注在方法上


  • 标注在 set 方法上


//@Autowiredpublic void setCustomLog(CustomLog customLog) {  this.customLog = customLog;}
复制代码


  • 标注在构造方法上


@Autowiredpublic CustomAspect(CustomLog customLog) {  this.customLog = customLog;}
复制代码


标注在配置类上的入参中(可以不写)


@Beanpublic CustomAspect CustomAspect(@Autowired CustomLog customLog) {    CustomAspect customAspect = new CustomAspect(customLog);    return ustomAspect;}
复制代码

XXXAwarce 接口

我们自己的组件 需要使用 spring ioc 的底层组件的时候,比如 ApplicationContext 等我们可以通过实现 XXXAware 接口来实现


@Componentpublic class CustomCompent implements ApplicationContextAware,BeanNameAware {    private ApplicationContext applicationContext;    @Override    public void setBeanName(String name) {      System.out.println("current bean name is :【"+name+"】");    }     @Override    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {      this.applicationContext = applicationContext;    }}
复制代码

@Profile 注解

通过 @Profile 注解 来根据环境来激活标识不同的 Bean


  • @Profile 标识在类上,那么只有当前环境匹配,整个配置类才会生效

  • @Profile 标识在 Bean 上 ,那么只有当前环境的 Bean 才会被激活

  • 没有标志为 @Profile 的 bean 不管在什么环境都可以被激活


@Configuration@PropertySource(value = {"classpath:ds.properties"})public class MainConfig implements EmbeddedValueResolverAware {    @Value("${ds.username}")    private String userName;    @Value("${ds.password}")    private String password;    private String jdbcUrl;    private String classDriver;    @Override    public void setEmbeddedValueResolver(StringValueResolver resolver) {        this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");        this.classDriver = resolver.resolveStringValue("${ds.classDriver}");    }     @Bean    @Profile(value = "test")    public DataSource testDs() {       return buliderDataSource(new DruidDataSource());    }    @Bean    @Profile(value = "dev")    public DataSource devDs() {      return buliderDataSource(new DruidDataSource());    }     @Bean    @Profile(value = "prod")    public DataSource prodDs() {      return buliderDataSource(new DruidDataSource());    }     private DataSource buliderDataSource(DruidDataSource dataSource) {        dataSource.setUsername(userName);        dataSource.setPassword(password);        dataSource.setDriverClassName(classDriver);        dataSource.setUrl(jdbcUrl);      return dataSource;    }}
复制代码


激活切换环境的方法


(1)运行时 jvm 参数来切换


 -Dspring.profiles.active=test|dev|prod  
复制代码


(2)通过代码的方式来激活


public static void main(String[] args) {    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();    ctx.getEnvironment().setActiveProfiles("test","dev");    ctx.register(MainConfig.class);    ctx.refresh();    printBeanName(ctx);}
复制代码


好了,今天就到这儿吧,我是冰河,大家有啥问题可以在下方留言,也可以加我微信:sun_shine_lyz,我拉你进群,一起交流技术,一起进阶,一起牛逼~~

发布于: 2021 年 06 月 15 日阅读数: 47
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

Mykit系列开源框架发起者、核心架构师和开发者,《海量数据处理与大数据技术实战》与《MySQL开发、优化与运维实战》作者。【冰河技术】微信公众号作者。

评论

发布
暂无评论
小伙伴们在催更Spring系列,于是我写下了这篇注解汇总!!