1 Spring框架简介
1.1 什么是Spring
Spring框架是一个开源的轻量级的DI和AOP容器框架,致力于简化企业级应用开发,让开发者使用简单的Java Bean来实现从前只有EJB才能实现的功能。
1.2 为什么要使用Spring
Spring堪称Java世界中最强大的框架,其亮点非常的多,主要体现在以下几个方面。
(1)使用Spring可以实现DI(依赖注入)。实现面向接口编程,以解决项目开发中组件间的解耦问题,让项目模块得以独立测试、灵活扩展和替换。
(2)使用Spring可以实现AOP(面向切面)。AOP可以在无需修改原有类源代码的情况下为它们切入增强功能。
(3)使用Spring可以实现声明式事务管理,无需在项目中写死事务处理边界,具有更高灵活性。
(4)Spring可与大部分的Java开源框架(如Hibernate、MyBatis、Struts2等)进行整合,并进一步简化这些框架的编码。
(5)Spring可以实现分布式远程调用、消息队列、安全验证等诸多大型应用所需的复杂功能,可以大大简化企业级系统的开发。在Spring出现之前,这些功能一般开发者都难以解决,只能依赖于大型厂商(如IBM、Oracle、SAP等)提供的昂贵的EJB容器;但现在,开源世界的Spring都可以做到,而且更为灵活和轻盈。
2 面向接口编程与容器框架
2.1 面向接口编程
Spring首先是一个容器框架,用于管理系统中的JavaBean。那么我们为何需要一个容器框架呢,这是由Java世界推崇的面向接口编程所决定的。
JavaEE平台的其中一个特点是倡导面向接口编程,JavaEE本身就是由SUN提出的各种规范和接口构成的。例如,JSP技术中的Servlet、Filter、Listener、ServletRequest、ServletResponse、HttpSession等对象统统都是接口。
为什么要面向接口编程呢,面向接口,可以降低组件与组件之间的依赖,实现弱耦合,被依赖组件随时可以被替代。例如Tomcat服务器,不过是一组JSP/Servlet接口的实现容器,我们完全可以用其它实现同样接口的容器(如Jetty)来替代它。此外,面向接口编程也使得组件的独立开发与测试提供了可能,否则开发上层模块的开发者就需要等待下层模块完成才能开工,各个模块无法并行开发。
虽然面向接口编程的想法不错,但使用时却要解决一个核心问题——具体对象从何而来?
参考如下代码:“CategoryDao”是一个接口,而“CategoryDaoImpl”是它的实现类,获取对象的常规方式是使用“new”调用实现类的构造方法。如果我们用这种方式来构建具体使用对象,就谈不上面向接口编程了,因为具体实现类已被写死在调用代码中了。
CategoryDao categoryDao = new CategoryDaoImpl();
categoryDao.save(categoryName);
2.2 工厂模式
在传统的面向对象编程中,对象是调用者创建(new)出来的,调用者和被调用者产生了强耦合,而工厂模式在可以在调用者中隐藏具体类型,解决这一问题。
参考如下代码:“ObjectFactory”是对象工厂,可以根据不同的DAO名称获取对应的DAO实现类对象。具体可以使用Java反射技术与XML配置来实现。
CategoryDao categoryDao = (CategoryDao)ObjectFactory.getInstance("categoryDao");
categoryDao.save(categoryName);
2.3 使用Spring充当Bean容器
Spring框架首先实现了上述的“工厂模式”功能,它就是一个bean(对象)工厂,我们称为bean容器。我们可以把系统需要用到的所有功能对象通过配置的方式放入到Spring容器中,然后在需要使用时从Spring获取。
当然,Spring远不止这么简单,它更进一步的实现了我们称为“依赖注入”的对象获取方式,我们将在下文讨论。
3 通过Spring容器配置并获取对象
下面我们正式体验Spring框架的对象管理功能。
(1)获取并添加Spring框架的依赖
使用Spring前,我们需要浏览一下Spring的官方站点,上面有官方文档和相关Maven坐标的描述。
Spring Framework
Spring框架有众多部件,根据官方文档,我们可以通过以下maven坐标得到Spring框架的核心支持(spring-context)。
<!-- Spring DI容器 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.2.5.RELEASE</version></dependency>
(2)在“类路径”下加Spring的bean配置文件“applicationContext.xml”,并配置需要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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="categoryDao" class="demo.dao.impl.CategoryDaoImpl" />……
</beans>
注意,所谓“类路径”,指的是Eclipse Web项目中的“src目录”或者Maven项目中的“src/main/resources”路径。
(3)创建Spring容器(工厂),并通过bean的名称获取bean对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");CategoryDao categoryDao = (CategoryDao)ctx.getBean("categoryDao");categoryDao.save("科幻");
4 使用Spring实现依赖注入
所谓依赖注入DI(Dependency Injection),一些文献也称之控制反转IOC(Inversion of Control):是一种松耦合的开发模式,指对象是被动地接收它的依赖类对象,而非自己主动去查找或创建。在开发中A类依赖于B类(如业务对象依赖于数据访问对象),往往是A类中直接代码创建B类对象使用(或使用JNDI查找B类对象)。而在依赖注入中,A类中的B对象不由A自身创建,而是由容器C在实例化A类对象时主动将A所依赖的B对象注入给它。
通过bean元素的property子元素,可以通过bean对象的属性实现依赖注入。 property子元素中,name属性用于声明属性名,ref属性用于引用已声明的复杂类型bean对象,value属性用于指定普通类型常量值。
<bean id="userBiz" class="com.demo.biz.impl.UserBizImpl"><property name="userDao" ref="userDao" />
</bean>
5 Bean的作用域。
Spring默认使用单例模式管理bean对象,也就是说对于同一个类Spring中只保留一个实例,我们多次通过Spring的“getBean()”获取的是相同的一个实例。而一些实际的场合(单例模式,适用于无状态Bean,不适用于有状态Bean),单例模式不适用,这时我们可以通过bean的scope属性来改变Bean的作用域。
通过bean元素的scope属性,可以指定Bean对象在Spring容器中的作用域。其取值如下表所示。
scope属性取值 | 含义 |
singleton | 默认值,Spring容器中对该bean做单例模式处理,对所有id与该bean定义匹配的bean请求,Spring只会返回bean的同一实例。 |
prototype | 每一次请求都会产生一个新的bean实例,相当与一个new的操作,对于prototype作用域的bean,有一点非常重要,那就是Spring不能对一个prototype bean的整个生命周期负责,容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。 |
request | request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效。 |
session | session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效。 |
global session | global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。 |
示例:
<bean id="userDao" class="com.demo.dao.impl.UserDaoImpl" scope="prototype" />
注意:若配置为request、session、global session时,若使用的是Servlet 2.4及以上的web容器,那么需要在web应用的XML声明文件web.xml中增加下述ContextListener声明:
<listener><listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
6 在Web环境中启动Spring容器
在Web环境中,应用程序是由Web服务器启动的,Spring要作为对象容器(对象工厂)为各层提供依赖注入功能,就必须在Web服务器启动时创建Spring实例,并在整个应用程序生命周期中保持唯一。这时,我们就不能在main函数中随便创建ApplicationContext()对象了,因为Web应用程序并不是由main函数启动的。
针对这个问题,Spring提供了Web服务器的监听程序,使用监听器监听Web应用程序的启动事件,并在事件处理函数中创建Spring实例并使用单例模式缓存起来(存放到Web应用程序上下文中,即ServletContext类型的application对象)。这样在Web程序的任意地方,就可以获取到唯一的Spring实例并实现依赖注入了。
下面介绍在Web环境中使用Spring的注意事项:
(1)在项目种添加Spring Web依赖
<!-- Spring容器 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.2.5.RELEASE</version></dependency><!-- Spring Web --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>4.2.5.RELEASE</version></dependency>
(2)配置web.xml,设置Spring Web监听器,在Web应用启动时创建并缓存Spring容器
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <!-- 启动Spring容器的监听器 --><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml</param-value></context-param>
</web-app>
(3)在Servlet/JSP等请求处理器中获取Spring容器和它所管理的Bean
在请求上下文中,可以通过以下方法获取Spring容器
WebApplicationContextUtils.getRequiredWebApplicationContext(ServletContext object)
下面是Servlet中使用Spring容器的示例
@WebServlet("/admin/category-list")
public class CategoryListServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {//获取Spring容器ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());//获取Spring管理的bean对象CategoryBiz categoryBiz = (CategoryBiz)ctx.getBean("categoryBiz");//执行请求处理request.setAttribute("categories", categoryBiz.getAll());request.getRequestDispatcher("/admin/category-list.jsp").forward(request, response);}
}
7 Spring DI的其它用法
7.1 构造方法(构造器)注入
通过bean元素的constructor-arg子元素,可以通过bean对象的构造方法实现依赖注入。
在constructor-arg子元素中,index用于指定参数的索引,name用于指定参数名,ref用于引用已声明的bean对象,value用于指定普通类型常量值。
<bean id="userBiz2" class="com.demo.biz.impl.UserBizImpl2"><constructor-arg index="0" ref="userDao" />
</bean>
7.2 三种实例化bean的方式
(1)使用类构造方法实例化
<bean id="userDao" class="com.demo.dao.impl.UserDaoImpl"></bean>
(2)使用静态工厂方法实例化
首先定义静态工厂方法。
public static UserDao getInstance(){System.out.println("执行了静态工厂方法。");return new UserDaoImpl();}
然后配置,通过静态工厂实例化bean。
<bean id="userDao" class="com.demo.dao.impl.UserDaoFactory" factory-method="getInstance" />
(3)使用实例工厂方法实例化
首先定义实例工厂方法。
public UserDao createInstance(){System.out.println("执行了对象工厂方法。");return new UserDaoImpl();}
然后配置,通过实例(对象)工厂实例化bean
<bean id="userDaoFactory" class="com.demo.dao.impl.UserDaoFactory" />
<bean id="userDao" factory-bean="userDaoFactory" factory-method="createInstance" />
7.3 Bean的生命周期管理
在实例化bean时,有时有必要执行一些初始化代码来使它处于可用状态,或者在丢弃bean时需要执行一些清理工作。Spring为这种需求提供了初始化方法init-method和销毁方法destory-method配置,使得bean对象的生命周期管理更为细致。
public class UserDaoImpl implements UserDao {public void init(){System.out.println("执行了UserDao的初始化方法");}public void save() {System.out.println("执行UserDao,用户信息保存成功。");}public void destory(){System.out.println("执行了UserDao的销毁化方法");}
}
通过配置即可在适当时机调用初始化和销毁方法。
<bean id="userDao" class="com.demo.dao.impl.UserDaoImpl" init-method="init" destroy-method="destory" />