Spring框架(2) -- Spring中的Bean

25 篇文章 1 订阅
2 篇文章 0 订阅

Spring框架中的Bean

1、Bean的配置

Spring类似于一个工厂,生产和管理Spring容器中的Bean。开发中,最常使用XML文件来注册和管理Bean之间的依赖管理(简称XML配Bean)。

XML配置文件的根元素是,一个可以有多个,每一个定义了一个Bean,并描述了该Bean如何被装配到Spring容器中。

元素的常用属性和子元素

  • id:Bean的唯一标识符。
  • name:为Bean指定多个别名,每个别名之间用逗号或者分号隔开。
  • class:指定Bean的实现类,必须使用类的全限定名,即“包名+类名”。
  • scope:设定Bean的作用域,常用属性有singleton(单例)、prototype(原型)。
  • constructor-arg:元素的子元素,使用此元素传入构造参数进行实例化。
  • property:元素的子元素,用于调用Bean的setter()方法完成依赖注入。
  • ref:指定Bean工厂中某个Bean实例的引用。
  • value:直接给定一个常量值。
  • list:用于封装List或者数据属性的依赖注入。
  • set:用于封装Set类型属性的依赖注入。
  • map:用于封装Map类型属性的依赖注入。
  • entry:元素的子元素,用于设置键值对。

在applicationContext.xml配置文件中定义Bean的方式如下:

<!--	在Spring中创建对象,这些对象都称为Bean	-->
<bean id="helloWorld" class="com.zrt.pojo.HelloWorld" name="h1, h2, h3; h4">
    <property name="str" value="Spring, HelloWorld"/>
</bean>

HelloWorld实体类的定义如下:

package com.zrt.pojo;

public class HelloWorld {
    private String str;
    
    //getter、setter、toString……
}
传统的创建类:
    类型 变量名 = new 类型();
    HelloWorld helloWorld = new HelloWorld();
----------------------------------------------
    bean	-->		对象(HelloWorld)
    id		-->		变量名(helloWorld)
    class	-->		new的对象(new HelloWorld)
    property-->		str = "Spring, HelloWorld",相当于给对象中的属性赋值
    h1--h4	-->		变量名,即helloWorld的别名,有多个

2、Bean的作用域

Spring中Bean的实例定义了7中作用域,分别为singleton、prototype、request、session、globalSession、application以及websocket。

常用的两个为singleton(单例)和prototype(原型)。

2.1、singleton作用域

singleton是Spring容器默认的作用域,当Bean的作用域为singleton时,Spring容器中只会存在一个共享的Bean实例。

对于某个Bean的所有请求,只要id与该Bean的id属性相匹配,就会返回同一个Bean的实例。

也就是说,单例模式是把对象放在pool中,需要时取出,所以使用的都是同一个对象。

还是以Hello World为例:

<bean id="helloWorld" class="com.zrt.pojo.HelloWorld" scope="singleton">
    <property name="str" value="Spring, HelloWorld"/>
</bean>
@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");

    System.out.println(context.getBean("helloWorld", HelloWorld.class));
    System.out.println(context.getBean("helloWorld", HelloWorld.class));
}
-----
执行结果:
com.zrt.pojo.HelloWorld@401e7803
com.zrt.pojo.HelloWorld@401e7803

2.2、prototype作用域

在使用prototype作用域时,对于某个Bean的所有请求,Spring容器都会创建一个新的实例。

也就是说,原型模式每次从容器中取出的时候,都会产生一个新对象。

<bean id="helloWorld" class="com.zrt.pojo.HelloWorld" scope="prototype">
    <property name="str" value="Spring"/>
</bean>
相同的测试代码
    
执行结果:
com.zrt.pojo.HelloWorld@401e7803
com.zrt.pojo.HelloWorld@10dba097

3、Bean的装配方式

Bean的装配可以连接为依赖关系注入,Bean的装配方式就是Bean依赖注入的方式,主要有3种方式:

  • 基于XML的装配
  • 基于Annotation(注解)的装配
  • 自动装配

3.1、基于XML的装配

Spring提供了两种基于XML的装配方式:

  • 构造器注入
  • setter注入
3.1.1、构造器注入

构造器注入的Bean必须提供无参构造器和有参构造器。

使用定义构造器的参数,其属性index表示索引,0为第一个参数。

实体类User:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private String password;
}

applicationContext.xml配置文件:

<bean id="user" class="com.zrt.pojo.User">
    <constructor-arg index="0" value="admin"/>    
    <constructor-arg index="1" value="admin"/>
</bean>

测试类:

@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");

    System.out.println(context.getBean("user", User.class).toString());
}

执行结果:
User(username=admin, password=admin)
3.1.2、setter注入

在Spring实例化Bean的过程中,会先调用无参构造器来实例化Bean对象,再通过反射的方式调用setter方法来注入属性值。

因此,相比较于构造器注入,setter注入的Bean必须满足以下两个要求:

  1. Bean类有一个默认的无参构造器。
  2. Bean类必须有对应注入属性的setter方法。

实体类Student:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String name;
    private User user;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> game;
    private Properties info;
    private String wife;
}

applicationContext.xml配置文件:

<bean id="student" class="com.zrt.pojo.Student">
    <!--常量-->
    <property name="name" value="Spring"/>

    <!--引用注入-->
    <property name="user" ref="user"/>

    <!--数组-->
    <property name="books">
        <array>
            <value>Spring实战</value>
            <value>Spring Boot实战</value>
            <value>Spring Cloud实战</value>
        </array>
    </property>

    <!--list集合-->
    <property name="hobbies">
        <list>
            <value>打代码</value>
            <value>谈恋爱</value>
            <value>玩游戏</value>
        </list>
    </property>

    <!--Map键值对-->
    <property name="card">
        <map>
            <entry key="studentId" value="12315"/>
            <entry key="studentClass" value="IoT192"/>
        </map>
    </property>

    <!--set集合-->
    <property name="game">
        <set>
            <value>CF</value>
            <value>QQ飞车</value>
            <value>DNF</value>
        </set>
    </property>

    <!--空指针-->
    <property name="girlFriend">
        <null></null>
    </property>

    <!--properties注入-->
    <property name="info">
        <props>
            <prop key="studentId">12315</prop>
            <prop key="studentClass">IoT192</prop>
        </props>
    </property>
</bean>

测试类:

@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");

    System.out.println(context.getBean("student", Student.class).toString());
}

执行结果:
Student(name=Spring, 
        user=User(username=admin, password=admin), 
        books=[Spring实战, Spring Boot实战, Spring Cloud实战], 
        hobbies=[打代码, 谈恋爱, 玩游戏], 
        card={studentId=12315, studentClass=IoT192}, 
        game=[CF, QQ飞车, DNF], 
        info={studentId=12315, studentClass=IoT192}, 
        girlFriend=null)
3.1.3、拓展注入

导入xml约束

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

applicationContext.xml配置文件:

<!--p命名空间:setter注入,可以直接注入属性的值,类似<property>元素-->
<bean id="user01" class="com.zrt.pojo.User" p:username="root" p:password="root">
</bean>

<!--c命名空间:构造器注入,可以写入无参或者有参构造器,类似<construct-args>元素-->
<bean id="user02" class="com.zrt.pojo.User" c:username="admin" c:password="admin">
</bean>

测试类:

@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");

    System.out.println(context.getBean("user01", User.class).toString());
    System.out.println(context.getBean("user02", User.class).toString());
}

执行结果:
User(username=root, password=root)
User(username=admin, password=admin)

3.2、基于Annotation的装配

使用XML配Bean会导致项目的XML配置文件过于臃肿,Spring提供了对注解的支持。

Spring的常用注解:

  • @Component:描述Spring中的Bean,但它是一个泛化的概念,仅仅表示一个组件(Bean),可用于任何层次。
  • @Repository:功能与@Component相同,将数据访问层(dao层)的类标识为Spring的Bean。
  • @Service:功能与@Component相同,将业务层(service层)的类标识为Spring的Bean。
  • @Controller:功能与@Component相同,将控制层(controller层)的类标识为Spring的Bean。
  • @Autowired:用于对Bean的属性变量、属性的setter方法和构造器进行标注,完成Bean的自动配置工作。
    • 默认按Bean的类型(byType)进行装配。
  • @Qualifier:与@Autowired配合使用。
    • 当@Autowired不能按类型唯一装配时,需要使用到@Qualifier。
  • @Resource:用于对Bean的属性变量、属性的setter方法和构造器进行标注,完成Bean的自动配置工作。
    • 默认按Bean的实例名称(byName)进行装配。
    • 匹配不到时,再按Bean的类型(byType)进行装配。
  • @value:用于对Bean的属性变量、属性的setter方法进行注入。
  • @Nullable:标记的字段可以为空。
  • @Scope:配置Bean的作用域,默认是singleton(单例)。
  • @Bean:用于方法上,标识该方法可以返回一个Bean。
  • @Configuration:用于定义配置类,可替换XML配置文件,类似于元素。

Tips:

  1. @Resource的一些小细节

    1. @Resource有两个重要的属性,即name和type。
    2. Spring将name解析为Bean的实例名称,将type解析为Bean的实例类型。
    3. 若是指定name属性,则按Bean实例进行名称装配;若指定type属性,则按Bean的实例类型进行装配。
    4. 若都不指定,则默认按照先按Bean的实例名称进行装配,不能匹配时再按Bean的实例类型进行装配。
    5. 若都无法匹配,则抛出NoSuchBeanDefinitionException异常。
  2. @Bean、@Component和@Autowired之间的区别

    1. @Component(@Controller、@Service、@Repository):自动创建一个Bean实例并装配到Spring容器中(IOC)。

    2. @Bean:手动创建一个Bean实例,并装配到Spring容器中。

      @Bean的好处是,麻烦一点,但自定义性更强。例如当我们引用第三方库中的类需要装配到Spring容器时,不可能在他的源代码的类上加上一个@Component注解,所以则只能通过@Bean来实现。

    3. @Autowired:织入

      假设Spring容器中已有实例,@Autowired只是取出来对Bean进行装配,大白话就是@Autowired说“请给我一个该类的实例”。

  3. @Configuration注解

    用于定义配置类,可替换XML配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被扫描,并用于构建Bean定义,初始化Spring容器。@Configuration注解的配置类有如下要求:

    1. @Configuration不可以是final类型。
    2. @Configuration不可以是匿名类。

创建UserDao接口以及实现类:

public interface UserDao {
    public void save();
}
@Repository("userDao")
public class UserDaoImpl implements UserDao {
    @Override
    public void save() {
        System.out.println("UserDao的save方法");
    }
}

使用@Repository(“userDao”)注解将UserDaoImpl类标识为Spring中的Bean,其写法相当于XML配置文件:

<bean id="userDao" class="com.zrt.dao.impl.UserDaoImpl">

创建UserService接口以及实现类:

public interface UserService {    public void save();}
@Service("userService")
public class UserServiceImpl implements UserService {

    @Value("admin")
    private String username;
	
    /*
    此处将@Resource(name = "userDao")换成@Autowired也可以达到同样的效果,即
    	@Autowired
    	@Qualifier(value = "userDao")
    */
    @Resource(name = "userDao")	
    //为了与userDao区分,正确的命名是userDao
    private UserDao userServiceDao;

    @Override
    public void save() {
        System.out.println("username属性的值:" + username);
        this.userServiceDao.save();
        System.out.println("UserService的save方法");
    }
}

使用@Repository(“userService”)注解将UserServiceImpl类标识为Spring中的Bean,再使用@Resource(name=“userDao”)注解标注在属性userServiceDao上,其写法相当于XML配置文件:

<bean id="userService" class="com.zrt.service.impl.UserServiceImpl">
	<property name="userServiceDao" ref="userDao"
</bean>

值得一提的是,此处@Recource换成@Autowired也是可以实现同样的,倘若@Autowired的自动装配环境比较复杂的话,无法通过一个注解完成的时候,可以使用@Qualifier(value="")取配合使用,指定一个唯一的id对象。

applicationContext.xml配置文件

先导入context约束,再配置注解的支持:

xmlns:context="http://www.springframework.org/schema/context":<context:annotation-config/>
<!--使用context命名空间在配置文件中开启相应的注解处理器-->
<context:annotation-config/>

<bean id="userDao" class="com.zrt.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.zrt.service.Impl.UserServiceImpl"/>

测试类:

@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");

    context.getBean("userDao", UserDao.class).save();
    context.getBean("userService", UserService.class).save();
}

执行结果:
UserDao的save方法
username属性的值:admin
    
UserDao的save方法
UserService的save方法

接着再说一下@Component和@Bean。

假设有一个Person实体类:

//这个注解说明此类被Spring接管了,注册到容器中了。
@Component
public class Person {
    //这里的@Value置于属性上
    //相当于<property name="name" value="zrt"/>
    @Value("zrt")
    private String name;
    
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
    
    //这里的@Value置于setter方法上
    @Value("20")
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在config包下创建一个PersonConfig配置类:

//@Configuration标识这是一个配置类,类似于<beans>标签
//这个也会被注册到Spring容器中,因为@Configuration就是一个@Component
@Configuration
@ComponentScan("com.zrt.pojo")  //开启扫描
public class PersonConfig {

    //注册一个@Bean,相当于我们之前写的一个Bean标签
    //这个方法的方法名getPerson --->    bean标签中的id属性
    //这个方法的返回值Person    --->    bean标签中的class属性
    //等价于<bean id="getPerson" class="com.zrt.pojo.Person">
    @Bean
    public Person getPerson() {
        return new Person();
    }
}

补充,@Configuration的部分源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component	//简单说,@Configuration就是一个@Component
public @interface Configuration {
    
}

测试类:

@Test
public void myTest() {
    //因为我们使用配置类的方式
    //这里从AnnotationConfigApplicationContext获取配置类的class字节文件加载
    ApplicationContext context =
        new AnnotationConfigApplicationContext(PersonConfig.class);

    Person person = context.getBean("getPerson", Person.class);
    System.out.println(person.getName());
    System.out.println(person.getAge());
}

执行结果:
zrt
20

3.3、自动装配

Spring中的元素包含一个autowire属性,我们可以通过设置autowire的属性值来自动装配Bean,所谓自动装配,就是将一个Bean自动注入其他Bean的property中。

autowire属性的5个值:

  • default(默认值):由的上级标签的default-autowire属性值确定。例如,,则该元素中的autowire属性值为byType。
  • constructor:根据构造器参数的数据类型进行byType模式的自动装配。
  • byName:根据名称进行自动装配。
  • byType:根据数据类型进行自动装配。
  • no:在默认情况下,不使用自动装配,Bean依赖必须通过ref元素定义。
3.3.1、byType

假设在pojo包中创建一个Cat类、一个Dog类和一个People类。

public class Cat {
    @Value("小喵咪")
    public String name;

    public void shut() {
        System.out.println("喵喵喵~我叫" + name);
    }
}
public class Dog {
    @Value("小旺财")
    public String name;

    public void shut() {
        System.out.println("汪汪汪~我叫" + name);
    }
}
public class People {
    @Value("Spring")
    private String name;
    private Cat cat;
    private Dog dog;

    public void shut() {
        System.out.println("我叫" + name);
        System.out.println("我有两个宠物," + cat.name + "和" + dog.name);
        cat.shut();
        dog.shut();
    }

    //有参构造器
    public People(Cat cat, Dog dog) {
        this.cat = cat;
        this.dog = dog;
    }
    
    //getter、setter……
}

applicayionContext.xml配置文件:

<bean id="cat001" class="com.zrt.pojo.Cat"/>
<bean id="dog001" class="com.zrt.pojo.Dog"/>

<!--
    byType会在容器中自动查找和自己对象属性相同的Bean
    例如,Dog dog:那么就会找pojo中的Dog类,然后进行自动装配。
-->
<bean id="people" class="com.zrt.pojo.People" autowire="byType"/>
<bean id="userDao" class="com.zrt.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.zrt.service.Impl.UserServiceImpl"/>

测试类:

@Test
public void myTest() {
    ApplicationContext context = new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    context.getBean("people", People.class).shut();;
}

执行结果:
我叫Spring
我有两个宠物,小喵咪和小旺财
喵喵喵~我叫小喵咪
汪汪汪~我叫小旺财
3.3.2、byName

更改appicationContext.xml配置文件:

<bean id="cat" class="com.zrt.pojo.Cat"/>
<bean id="dog" class="com.zrt.pojo.Dog"/>

<!--
    byName会在容器中自动查找
		和自己对象setter方法后面的值对应的id
         和自己对象有参构造器参数名称对应的id
    例如,setDog(),取后面的字符作为id,也就是说要id = dog才进行自动装配。
    再如,public People(Cat cat, Dog dog)中的Dog dog,id = dog才进行自动装配。
-->
<bean id="people" class="com.zrt.pojo.People" autowire="byName"/>

测试结果同上。

Tips:这里我说一下自己的理解

尽管是自动装配,但是依赖注入的方式依旧还是上面基于XML装配的两种的注入方式,即构造器注入或者是setter注入。以上面的例子来说,我们需要在People类中提供带参的构造器,或者是提供setter方法。若是两者都没有的话,此例子可能会抛出NullPointerException异常。


至此,Spring框架中Bean装配大概就完了,这一块很重要,尽管比较麻烦和复杂,在后面Spring Boot框架中,Bean的配置会变得更加的简便。

其实博主也是初学者,最近在使用Spring Boot做项目时,一直小问题不断,因此整理了当时学习Spring框架的笔记,希望对您有些许的帮助。

注:此文章为个人学习笔记,如有错误,敬请指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

窝在角落里学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值