JavaEE程序设计【三】

三、Spring-bean


转载:

Spring Bean详细讲解

spring中bean配置和bean注入

Spring:Bean的实例化配置及依赖注入

Spring-浅谈依赖注入

Spring源码分析之—— Bean的实例化和依赖注入细节

SpringMVC 源代码深度解析 IOC容器(Bean实例化和依赖注入)

Spring 源码解析之 bean 的加载过程介绍

1、Spring-Bean简介

Bean配置信息定义了Bean的实现及依赖关系,Spring容器根据各种形式的Bean配置信息在容器内部建立Bean定义注册表,然后根据注册表加载、实例化Bean,并建立Bean和Bean的依赖关系,最后将这些准备就绪的Bean放到Bean缓存池中,以供外层的应用程序进行调用。

什么是Bean?

Spring Bean是被实例的,组装的及被Spring 容器管理的Java对象。

Spring 容器会自动完成@bean对象的实例化。

创建应用对象之间的协作关系的行为称为:装配(wiring),这就是依赖注入的本质。

Bean的本质就是Java中的类,而Spring中的Bean其实就是对实体类的引用,来生产Java类对象,从而实现生产和管理Bean 。

2、Bean 三种配置方案

  • 基于xml配置Bean
  • 使用注解定义Bean
  • 基于java类提供Bean定义信息

推荐方式:基于xml配置Bean、使用注解定义Bean

①默认命名空间:它没有空间名,用于Spring Bean的定义;

②xsi命名空间:这个命名空间用于为每个文档中命名空间指定相应的Schema样式文件,是标准组织定义的标准命名空间;

③aop命名空间:这个命名空间是Spring配置AOP的命名空间,是用户自定义的命名空间。

命名空间的定义分为两个步骤:第一步指定命名空间的名称;第二步指定命名空间的Schema文档样式文件的位置,用空格或回车换行进行分分隔。

例如:

1
2
3
4
5
6
7
<?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">

</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">

(1)基于xml配置Bean

在配置文件中,通常一个普通的Bean只需要定义id(或name)和class 两个属性即可

如果在Bean中未指定id和name,则Spring会将class值当作id使用。

例如:

1
2
3
4
5
6
7
8
<?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="bean1" class= "cn.edu.Bean1" />
<bean name="bean2" class= "cn.edu.Bean2" />
</beans>
①scope 作用域

在上表7种作用域中,singleton和prototype是最常用的两种作用域。

  • singleton
    • Spring容器默认的作用域
    • Spring容器就只会存在一个共享的Bean实例
    • singleton作用域对于无会话状态的Bean(如Dao 组件、Service组件)来说,是最理想的选择
  • prototype
    • 对需要保持会话状态的Bean应该使用prototype作用域。在使用prototype作用域时,Spring容器会为每个对该Bean的请求都创建一个新的实例。
    • 但是和new不同,new是从磁盘中将class文件装到内存中,在生成,而prototype类似于克隆,除了第一次外,会从内存中复制。
②生命周期

singleton作用域:Spring容器可以管理singleton作用域的Bean的生命周期,在此作用域下,Spring能够精确的知道该Bean何时被创建,何时初始化完成,以及何时被销毁。

prototype作用域:prototype作用域的Bean,Spring只负责创建,当容器创建了Bean实例后,Bean的实例就交给客户端代码来管理,Spring容器将不再跟踪其生命周期。

(2)使用注解定义Bean

标注为常用

@Scope()定义作用域

注解分为两类:

1、一类是注册Bean,@Component , @Repository , @Service , @ Controller ,@Configration这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中,等你要用的时候,它会和上面的@Autowired , @Resource配合到一起,把对象、属性、方法完美组装。

2、一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;

①定义Bean [@Component , @Repository , @Service , @ Controller]
1
2
3
4
5
6
7
package com.baobaotao.anno;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
//①通过Repository定义一个DAO的Bean
@Component("userDao")
public class UserDao {
}

在①处,我们使用@Component注解在UserDao类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean。

它和以下的XML配置是等效的:

1
<bean id="userDao" class="com.baobaotao.anno.UserDao"/>

除了@Component以外,Spring提供了3个功能基本和@Component等效的注解,它们分别用于对DAO、Service及Web层的Controller进行注解,所以也称这些注解为Bean的衍型注解:(类似于xml文件中定义Bean<bean id=" " class=" "/>

  • @Repository:用于对DAO实现类进行标注;
  • @Service:用于对Service实现类进行标注;
  • @Controller:用于对Controller实现类进行标注;

之所以要在@Component之外提供这三个特殊的注解,是为了让注解类本身的用途清晰化,此外Spring将赋予它们一些特殊的功能。

②使用注解配置信息启动spring容器

Spring提供了一个context的命名空间,它提供了通过扫描类包以应用注解定义Bean的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!--①声明context的命名空间-->
<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-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
<!--②扫描类包以应用注解定义的Bean-->
<context:component-scan base-package="com.baobaotao.anno"/>
<bean class="com.baobaotao.anno.LogonService"></bean>
<!-- context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ -->
<!-- context:component-scan base-package="com.baobaotao">
<context:include-filter type="regex" expression="com\.baobaotao\.anno.*Dao"/>
<context:include-filter type="regex" expression="com\.baobaotao\.anno.*Service"/>
<context:exclude-filter type="aspectj" expression="com.baobaotao..*Controller+"/>
</context:component-scan -->
</beans>

在①处声明context命名空间,在②处即可通过context命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里的所有类,并从类的注解信息中获取Bean的定义信息。

如果仅希望扫描特定的类而非基包下的所有类,你们可以使用resource-pattern属性过滤特定的类,如下所示:

< context:component-scan base-package="com.baobaotao" resource-pattern="anno/*.class"/ >

这里我们将基类包设置为com.baobaotao,默认情况下resource-pattern属性的值为”*/.class”,即基类包里的所有类。这里我们设置为”anno/*.class”,则Spring仅会扫描基包里anno子包中的类。

(3)基于java类提供Bean定义

不常用,请查看

3、Bean 的实例化和依赖注入

依赖注入(Dependency Injection,DI):在运行期,由外部容器 动态地将依赖对象注入到组件中。

(0)实例化和依赖注入的关系

  • 构造器方式,解析XML,用BeanDefinition存储构造信息,包含哪些属性作为构造器的参数,到时候一并构造;
  • setter方式,解析XML,用BeanDefinition存储PropertyValues,在Bean实例化后通过set方式进行注入;
  • 注解方式,首先要开启注解功能,注入是通过注解后置处理器来完成的;

(1)先List<String> beanDefinitionNames获取BeanName,然后通过BeanName 获取Map<String, RootBeanDefinition> RootBeanDefinition,然后判断是否为单例、不是抽象、是为Lazy-init=false 然后调用getBean方法。

​ (2)如果BeanName是别名,先转换为原来的BeanName不是别名,然后如果缓存中有单例Bean时,就从缓存获取,如果没有的话,就创建一个

​ (3)先检查是否父工厂存在,如果有的话,获取父工厂

​ (4)判断Bean依赖对象所有的名称,如果有的话,获取依赖对象的名称,然后循环getBean();

​ (5)检查Bean的作用域,Scope 作用域、原型作用域、Request等,来实例化Bean ,是由FactoryBean是创建创建对象的工厂Bean,通过调用这个对象的 getObject 方法就能获取用户自定义产生的对象,从而为 Spring 提供了很好的扩展性。Spring 获取 FactoryBean 本身的对象是在前面加上 & 来完成的。

​ (6)封装Bean,用 BeanWrapper是对Bean的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装bean的属性描述器,由于BeanWrapper接口是PropertyAccessor的子接口,因此其也可以设置以及访问被包装对象的属性值。BeanWrapper大部分情况下是在IOC内部进行使用,通过BeanWrapper,IOC容器可以用统一的方式来访问Bean的属性。

​ (7)根据BeanName去容器缓存中获取已经Bean的包装好的BeanWrapper,如果没有就调用createBeanInstance(beanName, mbd, args),实例化Bean

​ (8)获取BeanWrapper里已经实例好的Bean,然后放到缓存,这样下次已经有了,就不用在创建。

​ (9)设置Bean实例的属性,对需要转换的属性进行转换,例如数组等,如果属性是引用类型,如果引用Bean没实例化,要在实例化,把bean实例对象封装好依赖注入在注入进来。

getBean(beanName);对bean进行实例化并对属性依赖进行注入过程。前面解析的代码不重要,主要是入口点,接下来才是重点, 创建bean时,如果缓存中有单例Bean时,就从缓存获取,如果没有就新建,通过匿名的内部类来创建Bean时根据Bean的作用域来创建的,有单实例作用域、原型作用域、Request等。

个人认为:实例化过程中,发现依赖,会先初始化相应依赖才能进行依赖注入,这是个递归过程,直到找到最终的依赖,实例化后不断返回,依次实例化,最后实例化本Bean

(1)实例化

①构造器实例化

构造器实例化是指Spring容器通过Bean对应的类中默认的构造函数来实例化Bean。

1
2
3
package cn.edu.instance.constructor;
public class Bean1{
}
1
2
<bean id="bean1" class= " cn.edu.instance.constructor.Bean1" />
</beans>
1
2
3
4
5
6
7
8
public class InstanceTest1 {
public static void main(String[] args) {
String xmlPath = "beans1.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
Bean1 bean = (Bean1) applicationContext.getBean("bean1");
System.out.println(bean);
}
}
②静态工厂方式实例化

静态工厂是实例化Bean的另一种方式。该方式要求自己创建一个静态工厂的方法来创建Bean的实例。

1
2
3
4
public class MyBean2Factory {
public static Bean2 createBean(){
return new Bean2(); }
}
1
<bean id="bean2" class="cn.edu.ujn.static_factory.MyBean2Factory" factory-method="createBean" />
1
2
3
4
5
6
public class InstanceTest2 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans2.xml");
System.out.println(applicationContext.getBean("bean2"));
}
}
③实例工厂方式实例化

实例工厂是采用直接创建Bean实例的方式,在配置文件中,通过factorybean属性配置一个实例工厂,然后使用factory-method属性确定使用工厂中的哪个方法

1
2
3
4
public class MyBean3Factory {
public Bean3 createBean(){
return new Bean3(); }
}
1
2
<bean id="myBean3Factory" class="cn.edu.ujn.instance.factory.MyBean3Factory" />
<bean id="bean3" factory-bean="myBean3Factory" factory-method="createBean" />
1
2
3
4
5
6
public class InstanceTest3 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans3.xml");
System.out.println(applicationContext.getBean("bean3"));
}
}

(2)依赖注入(装配)

①基于XML的装配

  • 设值注入

    属性注入即通过setXxx()方法注入Bean的属性值或依赖对象,由于属性注入方式具有可选择性和灵活性高的优点,因此属性注入是实际应用中最常采用的注入方式。

    属性注入要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认构造函数实例化Bean对象,然后通过反射的方式调用Setter方法注入属性值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    package com.baobaotao.anno;

    import org.springframework.beans.factory.BeanNameAware;

    public class LogonService implements BeanNameAware{

    private LogDao logDao;

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }

    public void setLogDao(LogDao logDao) {
    this.logDao = logDao;
    }

    public LogDao getLogDao() {
    return logDao;
    }
    public UserDao getUserDao() {
    return userDao;
    }

    public void setBeanName(String beanName) {
    System.out.println("beanName:"+beanName);
    }

    public void initMethod1(){
    System.out.println("initMethod1");
    }
    public void initMethod2(){
    System.out.println("initMethod2");
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?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-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd"
    default-autowire="byName"
    >
    <bean id="logDao" class="com.baobaotao.anno.LogDao"/>
    <bean id="userDao" class="com.baobaotao.anno.UserDao"/>
    <bean class="com.baobaotao.anno.LogonService">
    <property name="logDao" ref="logDao"></property>
    <property name="userDao" ref="userDao"></property>
    </bean>
    </beans>
  • 构造方法注入

    使用构造函数注入的前提是Bean必须提供带参数的构造函数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package com.baobaotao.anno;

    import org.springframework.beans.factory.BeanNameAware;

    public class LogonService implements BeanNameAware{

    public LogonService(){}

    public LogonService(LogDao logDao, UserDao userDao) {
    this.logDao = logDao;
    this.userDao = userDao;
    }

    private LogDao logDao;

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
    }

    public void setLogDao(LogDao logDao) {
    this.logDao = logDao;
    }

    public LogDao getLogDao() {
    return logDao;
    }
    public UserDao getUserDao() {
    return userDao;
    }

    public void setBeanName(String beanName) {
    System.out.println("beanName:"+beanName);
    }

    public void initMethod1(){
    System.out.println("initMethod1");
    }
    public void initMethod2(){
    System.out.println("initMethod2");
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd"
    default-autowire="byName">

    <bean id="logDao" class="com.baobaotao.anno.LogDao"/>
    <bean id="userDao" class="com.baobaotao.anno.UserDao"/>
    <bean class="com.baobaotao.anno.LogonService">
    <constructor-arg ref="logDao"></constructor-arg>
    <constructor-arg ref="userDao"></constructor-arg>
    </bean>

    </beans>
  • 通过修改两种工厂的方法这种方式注入,少见

②基于注解(Annotation)的装配

1.@Autowired注入是按照类型注入的,只要配置文件中的bean类型和需要的bean类型是一致的,这时候注入就没问题。但是如果相同类型的bean不止一个,此时注入就会出现问题,Spring容器无法启动。

2.@Resourced标签是按照bean的名字来进行注入的,如果我们没有在使用@Resource时指定bean的名字,同时Spring容器中又没有该名字的bean,这时候@Resource就会退化为@Autowired即按照类型注入,这样就有可能违背了使用@Resource的初衷。所以建议在使用@Resource时都显示指定一下bean的名字@Resource(name=”xxx”)

  • XML的配置

    • 中通过bean元素来配置Bean

      1
      2
      <context:annotation-config />
      <bean id="userDao" class="cn.edu.annotation.UserDaoImpl" />
    • 除了可以像示例中通过元素来配置Bean外,还可以通过包扫描的形式来配置一个包下的所有Bean:

      1
      <context:component-scan base-package="cn.edu.annotation" />

      对于扫描到的类,Spring默认命名为首字母小写。

      1
      2
      3
      4
      5
      6
      7

      @Controller
      public class LoginController{
      }

      等价于
      <bean id="loginController" class="XXX.LoginController"/>
  • @Resource

    其可以写在变量定义前,也可以写在setXXX方法前。@Resource默认按照ByName自动注入。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。

    使用注解时,若@Resource注解写在变量定义前,那么setXXX方法可以不写。
    但若是在xml文件中采用<bean>标签的set注入,需要实现setXXX方法。

    @Resource装配顺序:

    • step1:如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
    • step2:如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
    • step3:如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
    • step4:如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
  • @Autowired

    基本等价于 @Resource。@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用

  • @Resource与@Autowired的区别

    @Resource的作用相当于@Autowired,只不过@Autowired默认按照byType自动注入。@Resource默认按 byName自动注入

    构造器注入时,只能使用@Autowired。
    Set注入时,可以使用@Autowired或者@Resource(推荐)。

③自动装配

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

1
2
3
<bean id="userDao" class="cn.edu.annotation.UserDaoImpl" />
<bean id="userService" class="cn.edu.annotation.UserServiceImpl" autowire="byName" />
<bean id="userController" class="cn.edu.annotation.UserController" autowire="byName" />

4、Spring的Bean管理的方式的比较

5、Junit测试环境

1
2
3
@RunWIth(SpringJunit4ClassRunner.class)

@ContextConfiguration(locations = {"classpath:applicationContext.xml"}

例如:

1
2
3
4
5
6
7
8
9
10
@RunWIth(SpringJunit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"}
public class MyTest
{
@Test
public void hehe()
{
//.......
}
}

这种写法是为了让测试在Spring容器环境下执行。

-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~