在 Spring中 创建 JavaBean
Spring 框架的最核心功能之一是 DI (Dependency Injection), 也就是依赖注入。
DI 的底层原理是反射技术,对 JavaBean 的属性进行赋值,从而达到 A 到 B 模块的解耦。Spring 提供 DI 容器,对需要关联的 JavaBean、不需要关联的 JavaBean 的创建、销毁都要进行统一的调度和管理。
在我们的程序中,我们将不使用 new 关键字的创建对象,而是交给 DI 容器管理生命周期,以及多个 JavaBean 之间的注入关系。从技术上说,就是使用反射将接口和实现相分离。
但在此之前,我们来看看如何使用 Spring 框架,以及几种创建 JavaBean 的方式。
1. 不使用 Spring 框架的传统方式
先创建一个 Java Project,创建一个保存操作的类 Save
package save;
public class Save {
public void save() {
System.out.println("save 方法!");
}
}
一个测试类 Test
package test;
import save.Save;
public class Test {
private Save save = new Save();
public Save getSave() {
return save;
}
public void setSave(Save save) {
this.save = save;
}
public static void main(String[] args) {
Test test = new Test();
test.getSave().save();
}
}
运行结果:
2. 使用 xml 声明法创建对象
在 src 中创建 applicationContext.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="userinfo1" class="entity.Userinfo"></bean>
</beans>
<bean> 标签告诉 Spring 要创建 entity 包下的 Userinfo 类的对象,放在 DI 容器里,在容器里的 id 是 userinfo1。Userinfo 如下:
package entity;
public class Userinfo {
public Userinfo() {
System.out.println("类 Userinfo 被实例化 = " + this);
}
}
测试类 Test:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
运行结果:
如果 applicationContext.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="userinfo1" class="entity.Userinfo"></bean>
<bean id="userinfo2" class="entity.Userinfo"></bean>
</beans>
如果通过 getBean(Userinfo.class) 方法获取对象的话就会抛出 NoUniqueBeanDefinitionException 异常,因为 Spring 找到了多个对象,不知道返回哪一个。
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import entity.Userinfo;
public class TestNoUnique {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Userinfo userinfo = context.getBean(Userinfo.class);
}
}
抛出以下异常 :
如何解决呢? 不要使用 getBean(Userinfo.class); , 使用 getBean(String id); 这样就可以获得 id 对应的唯一对象,而解决异常。
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import entity.Userinfo;
public class Test1_1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Userinfo userinfo1 = (Userinfo)context.getBean("userinfo1");
Userinfo userinfo2 = (Userinfo)context.getBean("userinfo2");
System.out.println(userinfo1);
System.out.println(userinfo2);
}
}
运行结果, 可以看到通过 id 获取的对象和 Spring 创建的对象是同一个对象:
总结: getBean(Userinfo.class) 适用于只有一个 Userinfo 类实例对象的情况,而有多个 Userinfo 类对象的时候就需要使用 getBean(String id) 方法。
3. 使用 xml 和 Annotation 注解混合的方式来创建对象
使用 <context:component-scan base-package=""> 创建对象
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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="entity"></context:component-scan>
</beans>
使用 @Component 注解,这样 Userinfo 能被 <context:component-scan base-package="entity"> 识别并创建实例化对象。
package entity;
import org.springframework.stereotype.Component;
@Component
public class Userinfo {
public Userinfo() {
System.out.println("Userinfo 构造方法执行了:" + this);
}
}
测试类 Test 类,运行结果:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
getBean(Userinfo.class) 的方式运行:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import entity.Userinfo;
public class Test2 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Userinfo userinfo = (Userinfo)context.getBean(Userinfo.class);
System.out.println("main run " + userinfo);
}
}
我们也可以看到通过 id 获取的对象和 Spring 创建的对象是同一个对象。
4. 使用全注解创建出对象
全注解配置法也称 JavaConfig 配置法。注解 @Configuration 起着和 <beans> 标签一样的全局配置作用,这样我们就不需要 xml 配置方式就能创建 JavaBean 了。而注解 @Bean 就和 <bean> 标签的功能是一样的,用来声明 JavaBean。
@Bean 注解声明创建对象的方法是名称可以是任意的,但必须有返回值。
package tools;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import entity.Userinfo;
@Configuration
public class CreateBean {
@Bean
public Userinfo getUserinfo() {
Userinfo userinfo1 = new Userinfo();
System.out.println("创建 userinfo1 = " + userinfo1);
return userinfo1;
}
@Bean
public Userinfo createUserinfo() {
Userinfo userinfo2 = new Userinfo();
System.out.println("创建 userinfo2 = " + userinfo2);
return userinfo2;
}
}
package entity;
import org.springframework.stereotype.Component;
public class Userinfo {
public Userinfo() {
System.out.println("Userinfo 构造方法执行了:" + this);
}
}
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("tools");
}
}
运行结果:
使用全注解发生 NoUniqueBeanDefinitionException 异常,以及解决方法
将 Test4 类的代码加上一行代码
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import entity.Userinfo;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("tools");
Userinfo userinfo = context.getBean(Userinfo.class);
}
}
解决办法是给 @Bean 添加一个 name
package tools;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import entity.Userinfo;
@Configuration
public class CreateBean {
@Bean(name = "userinfo1")
public Userinfo getUserinfo() {
Userinfo userinfo1 = new Userinfo();
System.out.println("创建 userinfo1 = " + userinfo1);
return userinfo1;
}
@Bean(name = "userinfo2")
public Userinfo createUserinfo() {
Userinfo userinfo2 = new Userinfo();
System.out.println("创建 userinfo2 = " + userinfo2);
return userinfo2;
}
}
然后通过 getBean(String id) 方法获得对象,和 xml 方式是一样的:
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import entity.Userinfo;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("tools");
Userinfo userinfo1 = (Userinfo)context.getBean("userinfo1");
Userinfo userinfo2 = (Userinfo)context.getBean("userinfo2");
System.out.println("main userinfo1 = " + userinfo1);
System.out.println("main userinfo2 = " + userinfo2);
}
}
运行结果:
使用 @ComponentScan 注解的 basePackages = “” 创建对象,它和 <context:component-scan base-package=""> </context:component-scan>
作用是相同的,可以进行类扫描,并且创建对象。
扫描 entity 包的类 CreateBean
package tools;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "entity")
public class CreateBean {
@Bean
public Date getUserinfo() {
Date nowDate = new Date();
System.out.println("创建 nowDate = " + nowDate);
return nowDate;
}
}
package entity;
import org.springframework.stereotype.Component;
@Component
public class Userinfo {
public Userinfo() {
System.out.println("Userinfo 构造方法执行了:" + this.hashCode());
}
}
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tools.CreateBean;
public class Test {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(CreateBean.class);
}
}
运行结果:
我们可以看到 entity 下的 Userinfo 类创建了一个实例对象。
使用 @ComponentScan(basePackages = "") 扫描多个包
package tools;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = {"entity1" , "entity2"})
public class CreateBean {
@Bean
public Date createDate() {
Date nowDate = new Date();
System.out.println("createDate " + nowDate.getTime());
return nowDate;
}
}
package entity1;
import org.springframework.stereotype.Component;
@Component
public class Userinfo {
public Userinfo() {
System.out.println("Userinfo 构造方法执行了:" + this);
}
}
package entity2;
import org.springframework.stereotype.Repository;
@Repository
public class Bookinfo {
public Bookinfo() {
System.out.println("Bookinfo 的构造方法被调用了 " + this);
}
}
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tools.CreateBean;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(CreateBean.class);
}
}
运行结果:
使用 @ComponentScan(basePackageClasses = "") 扫描多个类
package tools;
import java.util.Date;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import entity1.entity2.Entity2222;
import entity1.entity2.entity3.Userinfo;
@Configuration
//@ComponentScan(basePackageClasses = { Entity2222.class, Userinfo.class})
@ComponentScan(basePackageClasses = { Userinfo.class})
@ComponentScan(basePackageClasses = { Entity2222.class})
public class CreateBean {
@Bean
public Date createDate() {
Date nowDate = new Date();
System.out.println("createDate " + nowDate.getTime());
return nowDate;
}
}
package entity1.entity2;
import org.springframework.stereotype.Repository;
@Repository
public class Entity2222 {
public Entity2222() {
System.out.println("Entity2222 构造方法执行了:" + this.hashCode());
}
}
package entity1.entity2.entity3;
import org.springframework.stereotype.Repository;
@Repository
public class Bookinfo {
public Bookinfo() {
System.out.println("Bookinfo 的构造方法被调用了 " + this.hashCode());
}
}
package entity1.entity2.entity3;
import org.springframework.stereotype.Component;
@Component
public class Userinfo {
public Userinfo() {
System.out.println("Userinfo 构造方法执行了:" + this.hashCode());
}
}
package test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tools.CreateBean;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext(CreateBean.class);
}
}
运行结果:
可以分行写,也可以写成一个数组
//@ComponentScan(basePackageClasses = { Entity2222.class, Userinfo.class})
@ComponentScan(basePackageClasses = { Userinfo.class})
@ComponentScan(basePackageClasses = { Entity2222.class})
如果只写 @ComponentScan, 那么扫描的将是 @Configuration 注解配置类所在包及其子包下的所有组件。
@Component 注解代表都是一个可以扫描的组件, @Repository 注解代表也是一个组件,一般代表的是 DAO 层的组件,也就是数据访问层的组件。它们俩都可以被 @ComponentScan 组件扫描。
总结:这篇博客的例子基本来自 JavaEE 核心框架第二版。以前并没有记录 Spring 创建 JavaBean 的方式,所以记录下来,方便以后学习。