Spring学习之Spring Bean与注解

140 阅读6分钟

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 接口有两个实现类: SmsServiceImpl1SmsServiceImpl2,且它们都已经被 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,如果同时指定nametype属性(不建议这么做)则注入方式为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 属性来显式指定名称。

参考链接:javaguide.cn/system-desi…