【建议收藏】阿里P7总结的Spring注解笔记,把组件注册讲的明明白白

环境搭建

注解的方式是通过配置类的方式来注入组件,注解注入要比XML注入的方式简单,注解注入也需要在前者的基础上,添加一个spring-context的包,也是实际开发中常用的方式。

准备所需Jar包

Spring注解之组件注册

Spring提供了许多的注解配置,这样我们就可以通过注解的方式实现组件的注册,下图就是Spring中经常使用到的注解。


@ComponentScan和@Configurable

原先xml的方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 要扫描的包 -->
    <context:component-scan base-package="model"></context:component-scan>
</beans>

使用配置类
@Configurable来标注该类为Spring中的配置类,@ComponentScan(“model”)是为该配置类指定要去扫描的参数。

package config;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import model.Product;

/**
 * @Configurable: 该注解是标注该类是配置类
 * @ComponentScan:配置要扫描的包
 * @author GaoYang
 */
@Configurable
@ComponentScan("model")
public class MainConfig {

}

@Component

使用该注解就可以将Java对象@Component注册到Ioc容器中,@Component注解要是给属性赋值要配合@Value注解为属性赋值。

/**
    @Componnt可以指定该对象的id,也可以不用指定
    默认id为该类的类名首字母小写
 */
@Component("students")
public class Student {
    @Value("01")
    private int sid;
    @Value("侯宁宁")
    private String name;
    @Value("男")
    private String sex;

配置类

/**
 * @Configurable: 该注解是标注该类是配置类
 * @ComponentScan:配置要扫描的包
 * @author GaoYang
 */
@Configurable
@ComponentScan("model")
public class MainConfig {

}

使用@Configuration注入

@Component("students")
public class Student {
    @Value("01")
    private int sid;
    @Value("侯宁宁")
    private String name;
    @Value("男")
    private String sex;

    public Student() {
        super();
    }
    public Student(int sid, String name, String sex) {
        super();
        this.sid = sid;
        this.name = name;
        this.sex = sex;
    }
    public int getSid() {
        return sid;
    }
    public void setSid(int sid) {
        this.sid = sid;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Student [sid=" + sid + ", name=" + name + ", sex=" + sex + "]";
    }

}

测试

@Bean

使用@Bean注解该可以在我们的spring注册类里标注,创建对象的方法,可以通过一个返回值为该对象的方法去创建该对象,并通过构造器为该对象的属性进行赋值。

// 配置类
@Configurable
@ComponentScan("model")
public class MainConfig {
    // 默认id为方法名
    @Bean
    public Product product1() {
        return new Product("张三","hashd",1);
    }
    // 可以指定id
    @Bean("product2")
    public Product product2() {
        return new Product("张三","hashd",1);
    }

}

Java-Bean对象

public class Product {
    private String name;
    private String price;
    private int num;
    public Product() {
        super();
    }
    public Product(String name, String price, int num) {
        super();
        this.name = name;
        this.price = price;
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPrice() {
        return price;
    }
    public void setPrice(String price) {
        this.price = price;
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    @Override
    public String toString() {
        return "Product [name=" + name + ", price=" + price + ", num=" + num + "]";
    }

}

测试

@TypeFilter

@TypeFilter注解
是通过设置条件来过滤一些资源,我们可以过滤一些资源不让它加载到ioc容器中。它的使用要在@ComponentScan这个注解中国去使用,通过excludeFilters参数传值,excludeFilters是一个数组,可以设定多个@TypeFilter。

@TypeFilter语法

@Configurable
@ComponentScan(value = "model",excludeFilters = {
        // FilterType.ANNOTATION是通过注解的形式进行过滤
        @Filter(type = FilterType.ANNOTATION,classes = {Controller.class}),

        // FilterType.ASSIGNABLE_TYPE 是通过给定的类型
        @Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {Product.class}),

        // FilterType.ASPECTJ 根据正则表达式
        @Filter(type = FilterType.ASPECTJ,classes = {""}),

        // FilterType.CUSTOM 使用自定义规则
        @Filter(type = FilterType.CUSTOM,classes = {TypeFilterImp.class})

})
public class MainConfig {
    // @Bean == <bean></bean>

}

@FilterType.CUSTOM自定义规则

使用自定义规则,我们必须给它创建一个制定规则的类,这个类要去实现TypeFilter这个接口,并实现match这个方法,过滤器就会根据match方法的返回值加载,如果去ture就去过滤不满足条件的,如果为false则不会去加载!

/**
     * MetadataReader: 读取到的当前正在扫描的信息
     * MetadataReaderFactory:可以获取到其他任何类的信息
     */
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        // 获取当前类注解的信息
        AnnotationMetadata mr = metadataReader.getAnnotationMetadata();
        // 获取当前正在扫描的类的信息
        ClassMetadata classMetadata = metadataReader.getClassMetadata();
        // 获取当前类的资源信息
        Resource resource =  metadataReader.getResource();
        // 获取当前类的名字
        String className = classMetadata.getClassName();
        System.out.println("----"+className);
        // contains包含“er”
        if(className.contains("er")) {
            return true;
        }
        return false;
    }

}

@Scope

Spring创建对象默认是单例的,使用@Scope来描述也就是scope=“singleton”,另外scope还有prototype、request、session、global session作用域。

各作用域的的作用

  • singleton单例模式,全局有且仅有一个实例。(默认值)
  • prototype原型模式,每次获取Bean的时候会有一个新的实例。
  • request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,配置实例:
    request、session、global session使用的时候首先要在初始化web的web.xml中做如下配置:
    如果你使用的是Servlet 2.4及以上的web容器,那么你仅需要在web应用的XML声明文件web.xml中增加下述ContextListener即可:
<web-app>
 ...
  <listener>
      <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
  </listener>
 ...
</web-app>

  • session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
  • global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个 portlet web应用的各种不同的portlet所共享。在global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用global session作用域来标识bean,那么web会自动当成session类型来使用。

案例演示

singleton

@Configurable
@ComponentScan("model")
public class MainConfig {
   /**
    * @Scope
    * prototype: 多实例的  @Scope("prototype")
    * singleton: 单实例的  @Scope("person")
    * request: 一次请求创建一个实例
    * session: 同一个session创建一个实例
    * @return
    */
   @Scope("singleton")
   @Bean
   public Product product() {
    System.out.println("该实例已被创建");
    return new Product("张三","hashd",1);
   }
}

测试代码

public class text {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
        System.out.println("Ioc容器已创建完成!");
        Product bean1 = applicationContext.getBean(Product.class);
        Product bean2 = applicationContext.getBean(Product.class);
        System.out.println(bean1== bean2);

    }
}

从下图可以看到,bean1 == bean2


Layz-bean

@Layz赖加载主要是针对的是单例模式下,单例模式下ioc容器初始化时,就将bean对象注入到了容器中,@Layz注解可以让容器创建时不去注册容器,而是等到第一次调用时才去注册bean对象。此时,创建的对象依然是单例模式!

使用语法

// 配置类
@Configurable
@ComponentScan("model")
public class MainConfig {
    /**
     * 懒加载:
     *      针对的是单实例的bean,默认在容器启动的时候创建对象
     *      赖加载:容器启动时不创建对象,当第一次被调用时被创建
     * 
     */
    @Lazy
    @Bean
    public Product product() {
        System.out.println("该实例已被创建");
        return new Product("张三","hashd",1);
    }   

测试

@Conditional

@Conditional注解是根据制定条件来进行注册,需要我创建配置条件的配置类,如果条件满足就进行注册,不满足就不去注册。

语法

配置类

@Configurable
public class MainConfig {

    @Conditional({winCondition.class})
    @Bean("wind")
    public Product wind() {
        System.out.println("该实例已被创建");
        return new Product("张三","wind",1);
}   

条件类必须去实现Condition接口,并添加为实现的方法!

public class winCondition implements Condition{

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata arg1) {
        Environment environment = context.getEnvironment();
        // 获取当前操作系统的名字
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")) {
            return true;
        }
        return false;
    }
}

案例

需求根据当前操作系统去注册组件。

// 配置类
@Configurable
@Import(Hero.class)
public class MainConfig {
    // Windows系统
    @Conditional({winCondition.class})
    @Bean("wind")
    public Product wind() {
        System.out.println("该实例已被创建");
        return new Product("张三","wind",1);
    }
    // Linux系统  
    @Conditional({linuxCondition.class})
    @Bean("linux")
    public Product linux() {
        return new Product("李四","linux",2);

    }

}

条件配置类

public class winCondition implements Condition{
    // Windows系统,返回true
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata arg1) {
        Environment environment = context.getEnvironment();
        String property = environment.getProperty("os.name");
        if(property.contains("Windows")) {
            return true;
        }
        return false;
    }

}

public class linuxCondition implements Condition{
    /**
     * ConditionContext: 判断条件能使用上下文环境
     * AnnotatedTypeMetadata: 注释信息
     */

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 是否Linux系统
        // 1、能获取到ioc使用的bean工厂
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        // 2、获取类加载器
        ClassLoader clLoader = context.getClassLoader();
        // 3、获取当前环境信息
          Environment environment = context.getEnvironment();
          String property = environment.getProperty("os.name");

        // 5、bean注册类
          BeanDefinitionRegistry registry = context.getRegistry();
          if(property.contains("Linux")) {
              return true;
          }

        return false;
}

测试…


@import

  • @Import只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
  • 加入IOC容器的方式有很多种,@Import注解就相对很牛皮了,@Import注解可以用于导入第三方包 ,当然@Bean注解也可以,但是@Import注解快速导入的方式更加便捷
  • @Import注解有三种用法

第一种用法:直接填class数组

直接填对应的class数组,class数组可以有0到多个。对应的import的bean都将加入到spring容器中,这些在容器中bean名称是该类的全类名 ,比如com.yc.类名

@Import({ 类名.class , 类名.class... })
public class TestDemo {

}

第二种用法:ImportSelector方式【重点】

这种方式的前提就是一个类要实现ImportSelector接口,假如我要用这种方法,目标对象是Myclass这个类,分析具体如下:
创建Myclass类并实现ImportSelector接口

public class Myclass implements ImportSelector {
//既然是接口肯定要实现这个接口的方法
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[0];
    }
}
// 分析实现接口的selectImports方法中的:

// 1、返回值: 就是我们实际上要导入到容器中的组件全类名【重点 】
// 2、参数: AnnotationMetadata表示当前被@Import注解给标注的所有注解信息【不是重点】
// 需要注意的是selectImports方法可以返回空数组但是不能返回null,否则会报空指针异常!

以上分析完毕之后,具体用法步骤如下:
第一步:创建Myclass类并实现ImportSelector接口,这里用于演示就添加一个全类名给其返回值

public class Myclass implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        return new String[]{"com.yc.Test.TestDemo3"};
    }
}

第二步:编写TestDemo 类,并标注上使用ImportSelector方式的Myclass类

@Import({TestDemo2.class,Myclass.class})
public class TestDemo {
        @Bean
        public AccountDao2 accountDao2(){
            return new AccountDao2();
        }

}

第三步:编写打印容器中的组件测试类

**
 * 打印容器中的组件测试
 */
public class AnnotationTestDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(TestDemo.class);  //这里的参数代表要做操作的类

        String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
        for (String name : beanDefinitionNames){
            System.out.println(name);
        }

    }
}

第三种用法:ImportBeanDefinitionRegistrar方式

同样是一个接口,类似于第二种ImportSelector用法,相似度80%,只不过这种用法比较自定义化注册,具体如下:

public class Myclass2 implements ImportBeanDefinitionRegistrar {
//该实现方法默认为空
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {

    }
}
// 参数分析:

// 第一个参数:annotationMetadata 和之前的ImportSelector参数一样都是表示当前被@Import注解给标注的所有注解信息
// 第二个参数表示用于注册定义一个bean

第二步:编写代码,自定义注册bean

public class Myclass2 implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        //指定bean定义信息(包括bean的类型、作用域...)
        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(TestDemo4.class);
        //注册一个bean指定bean名字(id)
        beanDefinitionRegistry.registerBeanDefinition("TestDemo4444",rootBeanDefinition);
    }
}

第三步:编写TestDemo 类,并标注上使用ImportBeanDefinitionRegistrar方式的Myclass2类

@Import({TestDemo2.class,Myclass.class,Myclass2.class})
public class TestDemo {

        @Bean
        public AccountDao2 accountDao222(){
            return new AccountDao2();
        }

}

@FactoryBean

编写配置类

// 标记这是一个Spring配置类
@Configuration
public class SpringConfiguration {
    // 如果没有@Bean注解,则注入到容器中的id就是方法名(也就是myFactoryBean),但是如果显示的给了值,那么注入到容器中的就是factoryBean
    @Bean("factoryBean")
    public MyFactoryBean myFactoryBean(){
        return new MyFactoryBean();
    }
}

测试类

public class SpringDemo {

    @Test
    public void springTest01() throws Exception {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        // 容器中获取的Bean,实际上就是工厂Bean(MyFactoryBean通过getObject()方法返回的对象)
        Object factoryBean01 = context.getBean("factoryBean");
        System.out.println("实际上注入到容器中的类型是:" + factoryBean01.getClass());

        Object factoryBean02 = context.getBean("factoryBean");
        System.out.println("注入到容器内的对象是否是单例:" + (factoryBean01 == factoryBean02));

        Object factoryBean03 = context.getBean("&factoryBean");
        System.out.println("如果想获取到MyFactoryBean的对象,使用&前缀:" + factoryBean03);

        // 输出打印Spring中的所有Bean名称
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            System.out.println(beanDefinitionName);
        }
    }
}

最后

感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278