Javaspring的灵魂,就在于bean的灵活运用。作为Spring核心机制的依赖注入,改变了传统的编程习惯,对组件的实例化不再由应用程序完成,转而交由Spring容器完成,在需要时注入应用程序中,从而对组件之间依赖关系进行了解耦。这一切都离不开Spring配置文件中使用的 <bean> 元素。这一节对应教程的7-13节,主要是关于bean的相关知识。
Spring Bean的配置及常用属性
spring容器可以看作是一个大工厂,里面放着各种各样的实例,而bean就相当于该工厂的产品,如果需要生产管理bean,就需要告诉spring容器具体的要求,而要求就需要用到配置文件,spring的配置文件主要有两种格式:XML文件和Properties文件。一般情况下spring会选择XML文件作为配置文件,通过读取XML文件中的配置来管理bean。
上一节已经利用过XML文件实现了第一个Javaspring程序,所以这一节我们详细地看一看这个文件的内部写法。XML文件和HTML文件的形式很像,都是一个靠标签来实现的文件,而XML文件的根元素是<beans>,内部使用<bean>来具体定义实例化对象。示例代码如下:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 使用id属性定义person1,其对应的实现类为com.mengma.person1 -->
<bean id="person1" class="com.mengma.damain.Person1" />
<!--使用name属性定义person2,其对应的实现类为com.mengma.domain.Person2-->
<bean name="Person2" class="com.mengma.domain.Person2"/>
</beans>
我们需要编写的是bean标签的部分,其余都是一些约束性代码,不需要我们来修改,bean里面包含很多的属性,常用的属性如下:
目前我们已经用到了id、name、class,并且在依赖注入的示例代码中也用到了property和ref,剩下的一边学一边理解即可。这里需要记录一下,之前使用property依赖注入的时候,name的含义推测了好久都没搞清楚,这里多尝试了几次,name并不是名字,而是注入的对象的构造方法的名称,这个name前面加上set就是构造方法,而ref则对应工厂中的引用的实例的id。
Spring实例化Bean的三种方法
spring是个工厂,工厂里存放的是实例化后的对象,所以如何实例化bean就是一个很关键的问题,在一般的面向对象编程中,实例化的方法都是new一个,而在Javaspring中,我们有三种实例化bean的方法:
①构造器实例化
构造器实例化是指Spring容器通过Bean对应的类中默认的构造函数实例化Bean。到现在为止,我们使用的都是这种实例化方法,这种方法不需要额外编写什么,因为利用的是默认的构造函数。
演示这种实例化方法,首先新建一个springDemo02项目,这个项目在这一节里面通用,引入五个相应的jar包,这就完成了项目的配置。
在项目的src下创建一个名为com.mengma.instance.constructor的包,创建一个实体类Person1,这个类不需要实现什么,空着即可:
package com.mengma.instance.constructor;
public class Person1 {
}
之后配置XML文件,只需要实例化这一个bean,所以代码也很简单:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="person1" class="com.mengma.instance.constructor.Person1" />
</beans>
我们自己编写的只有bean的那一行,让实例化的对象名称为person1,class实例化这个对象的类的路径,由于我们使用的是默认的构造方法,这个构造方法的位置就在定义person的类里面,即person1的位置,直接填入。之后利用junit配置一个测试类即可:
package com.mengma.instance.constructor;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest1 {
@Test
public void test() {
// 定义Spring配置文件的路径
String xmlPath = "com/mengma/instance/constructor/applicationContext.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 通过容器获取id为person1的实例
System.out.println(applicationContext.getBean("person1"));
}
}
运行结果如下:
采用这种方法,我们成功从spring仓库中得到了实例化的对象,而这个对象是利用定义自己的类的默认构造函数来实例化的。
②静态工厂方式实例化
在Spring中,也可以使用静态工厂的方式实例化Bean。此种方式需要提供一个静态工厂方法创建Bean的实例。
演示这种方式,和上一种方式同样,也需要先编写一个类,这里我们在项目的src目录下创建一个名为com.mengma.instance.static_factory的包,并在该包下创建一个实体类Person2,该类与Person1相同,不需要添加任何成员。
package com.mengma.instance.static_factory;
public class Person2 {
}
静态工厂方式需要我们编写一个静态工厂类,可以看作是对象生产工厂,专门产对象(女拳爪巴),在com.mengma.instance.static_factory包下创建一个名为MyBeanFactory的类,并在该类中创建一个名为createBean()的静态方法,用于创建Bean的实例,代码如下:
package com.mengma.instance.static_factory;
public class MyBeanFactory {
// 创建Bean实例的静态工厂方法
public static Person2 createBean() {
return new Person2();
}
}
完成了这两项,接下来就是配置XML文件,由于更换了方式,这里的bean代码也需要做出改变,先上代码:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean id="person2" class="com.mengma.instance.static_factory.MyBeanFactory"
factory-method="createBean" />
</beans>
可以看见bean那一行,除了id变了,class也变了,上一种方式class填写的对象自身的类,是因为我们采用的是默认的构造函数,而构造函数就在那个类里面,现在我们采用的是静态工厂,我们在工厂里面编写了一个new的方法,所以这里写的就是功能创建实例的静态工厂方法的位置,同时新增一个factory-method来表示这是创建了一个bean。
最后编写测试类,测试类和前一种没什么区别:
package com.mengma.instance.static_factory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest2 {
@Test
public void test() {
// 定义Spring配置文件的路径
String xmlPath = "com/mengma/instance/static_factory/applicationContext.xml"; // 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 通过容器获取id为person2实例
System.out.println(applicationContext.getBean("person2"));
}
}
运行结果如下:
③实例工厂方式实例化
在Spring中,还有一种实例化Bean的方式就是采用实例工厂。在这种方式中,工厂类不再使用静态方法创建Bean的实例,而是直接在成员方法中创建Bean的实例。同时,在配置文件中,需要实例化的Bean也不是通过class属性直接指向其实例化的类,而是通过factory-bean属性配置一个实例工厂,然后使用factory-method属性确定使用工厂中的哪个方法。
依然是设置一个空类Person3:
package com.mengma.instance.factory;
public class Person3 {
}
之后像第二种方法那样创建一个实例工程,不同的是这里要补充一个构造函数:
package com.mengma.instance.factory;
public class MyBeanFactory {
public MyBeanFactory() {
System.out.println("person3工厂实例化中");
}
// 创建Bean的方法
public Person3 createBean() {
return new Person3();
}
}
之后重点在配置XML文件,可以看到我们这里是增加了两个bean,其中第一个是实例化了一个工厂对象,工厂对象的class指向的就是前面写的工厂类,而真正需要的对象时下面的person3,采用实例工厂方式的情况下,我们要用工厂中的方法来实现,此时factory-bean写入工厂实例的id,factory选择工厂方法中返回实例化对象的方法名称,这样就相当于用工厂对象得到了一个实例化对象。二三两种方法的区别就在于,一个是用工厂生产了一个对象,另一个是用工厂对象得到了一个对象,关键在于是否实例化的工厂对象。
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 配置实例工厂 -->
<bean id="myBeanFactory" class="com.mengma.instance.factory.MyBeanFactory" />
<!-- factory-bean属性指定一个实例工厂,factory-method属性确定使用工厂中的哪个方法 -->
<bean id="person3" factory-bean="myBeanFactory" factory-method="createBean" />
</beans>
编写测试类及得到的结果如下:
package com.mengma.instance.factory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class InstanceTest3 {
@Test
public void test() {
// 定义Spring配置文件的路径
String xmlPath = "com/mengma/instance/factory/applicationContext.xml"; // 初始化Spring容器,加载配置文件,并对bean进行实例化
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 通过容器获取id为person3实例
System.out.println(applicationContext.getBean("person3"));
}
}
以上就是三种实例化的方法,记住工厂是一个存放对象的地方,而如何得到这些对象,就是这一部分所介绍的三种方法,利用默认构造函数、利用工厂类和利用工厂对象。
Spring中Bean的作用域
一般我们在C++编程中的作用域都是函数或者变量的作用域,而采用了Spring之后,实例化对象由容器统一管理,所以这里的作用域也就变成了bean的作用域。
Spring容器在初始化一个Bean的实例时,同时会指定该实例的作用域。一般有五种作用域:
①singleton
单例模式,使用singleton定义的Bean在Spring容器中只有一个实例,这也是Bean默认的作用域。一般默认情况下我们使用的就是这个作用域,整个容器里面就只有一个实例,无论我们怎样申请,只要id正确,获得的都是那一个实例。通常情况下,这种单例模式对于无会话状态的bean来说,是最理想的选择。
可以使用代码验证一下,首先新建一个包,在项目的目src录下创建一个名为com.mengma.scope的包,在该包下创建Person类,类中不需要添加任何成员,然后创建Spring的配置文件applicationContext.xml,配置文件中直接按照前面几次的方法创建一个bean。最后再编写一个测试程序:
package com.mengma.scope;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class PersonTest {
@Test
public void test() {
// 定义Spring配置文件路径
String xmlPath = "com/mengma/scope/applicationContext.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 输出获得实例
System.out.println(applicationContext.getBean("person"));
System.out.println(applicationContext.getBean("person"));
}
}
可见在这里我们是两次去仓库申请实例,由于是单例模式,id又都相同,所以这一个bean被反复调用了两次,输出结果也是一样的:
②prototype
原型模式,每次通过Spring容器获取prototype定义的Bean时,容器都将创建一个新的Bean实例。使用prototype作用域的Bean会在每次请求该Bean时都会创建一个新的Bean实例。因此对需要保持会话状态的Bean应该使用prototype作用域。这种作用域每次申请得到的都是不同的对象,将刚才编写的代码中的xml文件中bean的类型改成prototype即可,运行结果也相应发生变化:
③request
在一次HTTP请求中,容器会返回该Bean的同一个实例。而对不同的HTTP请求,会返回不同的实例,该作用域仅在当前HTTP Request内有效。
④session
在一次HTTP Session中,容器会返回该Bean的同一个实例。而对不同的HTTP请求,会返回不同的实例,该作用域仅在当前HTTP Session内有效。
⑤global Session
在一个全局的HTTP Session中,容器会返回该Bean的同一个实例。该作用域仅在使用portlet context时有效。
Spring Bean的生命周期
Spring容器可以管理singleton作用域Bean的生命周期,在此作用域下,Spring能够精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。每次客户端请求 prototype 作用域的Bean时,Spring容器都会创建一个新的实例,并且不会管那些被配置成prototype作用域的Bean的生命周期。
可以这样理解,单例模式下由于只有一个对象,所以容器必须严格管理,所以精确地知道bean何时被创建、完成初始化和销毁。而原型模式对象要一个造一个,所以不缺对象,每次造出来对象就交给客户端,也就不去管生命周期。
Spring中Bean的装配
Bean的装配可以理解为依赖关系注入,Bean的装配方式也就是Bean的依赖注入方式,也可以说是Bean是如何装到容器里面的。Spring容器支持多种形式的Bean的装配方式,如基于XML的Bean装配、基于Annotation的Bean装配和自动装配等。
①基于XML的装配
Spring基于XML的装配通常采用两种实现方式,即设值注入和构造注入。二者区别在于是否使用了构造函数,设值注入是依靠编写的set方法,而构造注入则是利用了编写的构造方法。采用设值注入时spring会先用无参的构造函数初始化一个对象,再利用set方法去对属性值进行修改。下面用代码演示一下两种方法,首先在项目springDemo02中的src目录下,创建一个名称为com.mengma.assembly的包,在该包下创建一个Person类,代码如下:
package com.mengma.assembly;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
// 重写toString()方法
public String toString() {
return "Person[name=" + name + ",age=" + age + "]";
}
// 默认无参的构造方法
public Person() {
super();
}
// 有参的构造方法
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
可见在这个代码中,除了构造函数还编写了set和get方法,由于要使用设值注入,还编写了默认的无参构造方法,所以说四个set和get方法加上无参构造方法对应的就是设值注入,而最后有参的构造方法是留给构造注入使用的。接下来编写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" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 使用设值注入方式装配Person实例 -->
<bean id="person1" class="com.mengma.assembly.Person">
<property name="name" value="zhangsan" />
<property name="age" value="20" />
</bean>
<!-- 使用构造方法装配Person实例 -->
<bean id="person2" class="com.mengma.assembly.Person">
<constructor-arg index="0" value="lisi" />
<constructor-arg index="1" value="21" />
</bean>
</beans>
从这个代码可以看出,使用设值注入就是在bean标签中利用property标签,标签里面的name实际上就对应代码里面编写的setName,这个set是自动补上去的,需要对应起来,而value就对应要设置的值。使用构造方法就是利用constructor-arg标签,其中index用于指明构造函数中自变量的顺序,从零开始,而value则是希望设置的值。
这里的设置注入方法和前面依赖注入中构造方法注入是很像的,同样是用了property标签,区别在于这里的设值注入装配bean是用的value给一个属性设置了值,而前面依赖注入中构造方法注入是将另一个bean注入了进去。
编写好之后利用junit测试,代码如下:
package com.mengma.assembly;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlBeanAssemblyTest {
@Test
public void test() {
// 定义Spring配置文件路径
String xmlPath = "com/mengma/assembly/applicationContext.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 设值方式输出结果
System.out.println(applicationContext.getBean("person1"));
// 构造方式输出结果
System.out.println(applicationContext.getBean("person2"));
}
}
运行结果如图:
②基于Annotation的装配
尽管使用XML配置文件可以实现Bean的装配工作,但如果应用中Bean的数量较多,会导致XML配置文件过于臃肿,从而给维护和升级带来一定的困难。利用Annotation就可以一定程度上缓解这一问题。通过使用注解功能,在Java程序中就实现bean的装配,而不需要再在xml文件中大量地编写。
Javaspring中常用的注解如下:
下面借助代码来实现以下,首先在src目录下创建一个名为com.mengma.annotation的包,在该包下创建一个名为PersonDao的接口,并添加一个add()方法,代码如下:
package com.mengma.annotation;
public interface PersonDao {
public void add();
}
之后编写这个接口的实现方法,在下面的代码就可以看出来,在类前面使用了一个注解,从前面的注解可以看出,这一句话就是将这个类识别为一个bean,其写法相当于配置文件中 <bean id="personDao"class=“com.mengma.annotation.PersonDaoImpl”/> 的书写,可以知道这个注解内部的参数指的就是bean的id,具体代码如下:
package com.mengma.annotation;
import org.springframework.stereotype.Repository;
@Repository("personDao")
public class PersonDaoImpl implements PersonDao {
@Override
public void add() {
System.out.println("Dao层的add()方法执行了...");
}
}
之后在com.mengma.annotation包下创建一个名为PersonService的接口,并添加一个add()方法,如下所示:
package com.mengma.annotation;
public interface PersonService {
public void add();
}
相应地编写这个接口的实现类,在接口类里面在类之前使用了另一个注解,查前面的含义可以知道这句注解相当于实例化了一个叫做personService的bean,其写法相当于配置文件中 <bean id="personService"class=“com.mengma.annotation.PersonServiceImpl”/> 的书写,而类内部的注解则相当于将personDao的bean注入到了这个bean中,就是前面的依赖注入,这相当于配置文件中 <property name="personDao"ref=“personDao”/> 的写法,具体代码如下:
package com.mengma.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@Service("personService")
public class PersonServiceImpl implements PersonService {
@Resource(name = "personDao")
private PersonDao personDao;
public PersonDao getPersonDao() {
return personDao;
}
@Override
public void add() {
personDao.add();// 调用personDao中的add()方法
System.out.println("Service层的add()方法执行了...");
}
}
最后在com.mengma.annotation包下创建一个名为PersonAction的类,代码里面使用了controller注解相当于实例化了一个叫做personAction的bean,其写法相当于在配置文件中编写 <bean id="personAction"class=“com.mengma.annotation.PersonAction”/>,之后注入了personServicebean,这相当于在配置文件内编写 ,具体代码如下:
package com.mengma.annotation;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
@Controller("personAction")
public class PersonAction {
@Resource(name = "personService")
private PersonService personService;
public PersonService getPersonService() {
return personService;
}
public void add() {
personService.add(); // 调用personService中的add()方法
System.out.println("Action层的add()方法执行了...");
}
}
所有代码都准备好之后,需要对xml做一点配置,由于我们使用注解已经将bean的标签全部放在了java文件里面,所以完全不需要再写bean标签的内容,只需要做一点修改,在配置信息的部分增加一些使用注释的内容,最后使用context命名空间的component-scan元素进行注解的扫描,其base-package属性用于通知spring所需要扫描的目录:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--使用context命名空间,通知spring扫描指定目录,进行注解的解析-->
<context:component-scan base-package="com.mengma.annotation"/>
</beans>
现在编写junit测试类,代码和测试结果如下:
package com.mengma.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnotationTest {
@Test
public void test() {
// 定义Spring配置文件路径
String xmlPath = "com/mengma/annotation/applicationContext.xml";
// 初始化Spring容器,加载配置文件,并对bean进行实例化
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
// 获得personAction实例
PersonAction personAction = (PersonAction) applicationContext
.getBean("personAction");
// 调用personAction中的add()方法
personAction.add();
}
}
总结这种装配方式,本质上就是利用注解,将xml文件里面的bean语句放在了java代码里面,从而简化了xml文件的内容。
③自动装配
除了使用XML和Annotation的方式装配Bean以外,还有一种常用的装配方式——自动装配。自动装配就是指Spring容器可以自动装配(autowire)相互协作的Bean之间的关联关系,将一个Bean注入其他Bean的Property中。要使用自动装配,就需要配置 <bean> 元素的 autowire 属性。autowire 属性有五个值:
采用这种方法需要修改xml文件,还是用刚才的例子,将注释去掉,并将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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="personDao" class="com.mengma.annotation.PersonDaoImpl" />
<bean id="personService" class="com.mengma.annotation.PersonServiceImpl"
autowire="byName" />
<bean id="personAction" class="com.mengma.annotation.PersonAction"
autowire="byName" />
</beans>
除此之外,前面的代码中需要将所有的注释全部去掉,并且在需要的代码里面补充上构造方法。通过使用autowire,当需要注入的时候,会根据代码内部实现的构造函数的名字,自动寻找属性名字相同的bean,并调用对应的构造方法完成注入,也就是说如果我在代码里面定义了一个setA的构造方法,那么spring会根据已经实例化的bean里面找一个名字叫A的bean,用这个bean去完成构造函数,最后的运行结果与第二种方法的一样。