文章目录
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必须满足以下两个要求:
- Bean类有一个默认的无参构造器。
- 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:
-
@Resource的一些小细节
- @Resource有两个重要的属性,即name和type。
- Spring将name解析为Bean的实例名称,将type解析为Bean的实例类型。
- 若是指定name属性,则按Bean实例进行名称装配;若指定type属性,则按Bean的实例类型进行装配。
- 若都不指定,则默认按照先按Bean的实例名称进行装配,不能匹配时再按Bean的实例类型进行装配。
- 若都无法匹配,则抛出NoSuchBeanDefinitionException异常。
-
@Bean、@Component和@Autowired之间的区别
-
@Component(@Controller、@Service、@Repository):自动创建一个Bean实例并装配到Spring容器中(IOC)。
-
@Bean:手动创建一个Bean实例,并装配到Spring容器中。
@Bean的好处是,麻烦一点,但自定义性更强。例如当我们引用第三方库中的类需要装配到Spring容器时,不可能在他的源代码的类上加上一个@Component注解,所以则只能通过@Bean来实现。
-
@Autowired:织入
假设Spring容器中已有实例,@Autowired只是取出来对Bean进行装配,大白话就是@Autowired说“请给我一个该类的实例”。
-
-
@Configuration注解
用于定义配置类,可替换XML配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被扫描,并用于构建Bean定义,初始化Spring容器。@Configuration注解的配置类有如下要求:
- @Configuration不可以是final类型。
- @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框架的笔记,希望对您有些许的帮助。
注:此文章为个人学习笔记,如有错误,敬请指正。