Spring Bean
简单来说,Bean代指的就是那些被IoC容器所管理的对象,我们要告诉IoC容器帮助我们管理哪些对象,我们可以通过XML文件或者注解+配置类来配置。
Spring有两种Bean,一种普通Bean,另外一种就是工厂Bean(FactoryBean),那么这两种Bean有什么区别呢?
普通Bean:在配置文件中定义的Bean类型就是返回类型。
工厂Bean:在配置文件中定义的Bean类型可以和返回的类型不一样。
举个例子:
首先我们在XML文件中配置一个需要给IoC容器管理的对象
<bean id="mybean" class="com.syshine.spring5.factorybean.MyBean"></bean>
如果我们要实现FactoryBean就要在MyBean这个类中实现FactoryBean接口的方法
public class MyBean implements FactoryBean<Course> {
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
//定义返回bean 我们在这里返回Couse类型
@Override
public Course getObject() throws Exception {
Course course = new Course();
course.setCname("abc");
return course;
}
@Override
public Class<?> getObjectType() {
return null;
}
}
测试代码:
@Test
public void test3(){
ApplicationContext context =
new ClassPathXmlApplicationContext("bean3.xml");
//注意这里要写Course.class
Course course = context.getBean("mybean", Course.class);
System.out.println(course);
}
通过测试我们会发现它不是返回MyBean类型了,而是Course类型
Bean作用域
- singleton:IoC容器中只有唯一的bean实例。Spring中的bean默认都是单例的,是对单例设计模式的应用。
- prototype:每次获取都会创建一个新的bean实例。也就是说,连续getbean()两次,得到的是不同的Bean实例。
- request(仅Web应用可用):每一次HTTP请求都会产生一个新的bean(请求bean),该bean仅在当前HTTPrequest内有效。
- session(仅Web应用可用):每一次来自新session的HTTP请求都会产生一个新的bean(会话bean),该bean仅在当前HTTP session内有效。
- application/global-session(仅Web应用可用):每个Web应用在启动时创建一个Bean(应用Bean),该bean仅在当前应用启动时间内有效。
- websocket(仅Web应用可用):每一次WebSocket会话产生一个新的bean。
配置bean作用域的两种方式:
xml方式:
<bean id="..." class="..." scope="prototype"></bean>
注解方式:
@Bean @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public Person personPrototype() {
return new Person();
}
Bean生命周期
-
Bean 容器找到配置文件中 Spring Bean 的定义。
-
Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
-
如果涉及到一些属性值 利用
set()方法设置一些属性值。 -
如果 Bean 实现了
BeanNameAware接口,调用setBeanName()方法,传入 Bean 的名字。 -
如果 Bean 实现了
BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 -
如果 Bean 实现了
BeanFactoryAware接口,调用setBeanFactory()方法,传入BeanFactory对象的实例。 -
与上面的类似,如果实现了其他
*.Aware接口,就调用相应的方法。 -
如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessBeforeInitialization()方法 -
如果 Bean 实现了
InitializingBean接口,执行afterPropertiesSet()方法。 -
如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
-
如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor对象,执行postProcessAfterInitialization()方法 -
当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean接口,执行destroy()方法。 -
当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
注解
将一个类声明为Bean的注解有:
-
@Component:通用的注解,可标注任意类为Spring组件。如果一个 Bean 不知道属于哪个层,可以使用@Component注解标注。 -
@Repository: 对应持久层即 Dao 层,主要用于数据库相关操作。 -
@Service: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 -
@Controller: 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
@Component 和 @Bean的区别:
-
@Component注解作用于类,而@Bean注解作用于方法。 -
@Component通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。 -
@Bean注解比@Component注解的自定义性更强,而且很多地方我们只能通过@Bean注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring容器时,则只能通过@Bean来实现。
@Bean注解使用示例:
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
注入Bean的注解:Spring 内置的 @Autowired 以及 JDK 内置的 @Resource 和 @Inject 都可以用于注入 Bean。Autowired 属于 Spring 内置的注解,默认的注入方式为byType(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个接口存在多个实现类的话,byType这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService 就是我这里所说的名称,这样应该比较好理解了吧。
// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;
举个例子,SmsService 接口有两个实现类: SmsServiceImpl1和 SmsServiceImpl2,且它们都已经被 Spring 容器所管理。
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
我们还是建议通过 @Qualifier 注解来显式指定名称而不是依赖变量的名称。
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。
public @interface Resource {
String name() default "";
Class<?> type() default Object.class;
}
如果仅指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定name 和type属性(不建议这么做)则注入方式为byType+byName。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
简单总结一下:
@Autowired是 Spring 提供的注解,@Resource是 JDK 提供的注解。Autowired默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为byName(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired和@Resource都需要通过名称才能正确匹配到对应的 Bean。Autowired可以通过@Qualifier注解来显式指定名称,@Resource可以通过name属性来显式指定名称。