这个只是spring 部分
6.1.3 Spring 2.5拿手戏--控制反转与依赖注入
人类社会由数以亿计的拥有不同本领的人组成,通过这些人的不同分工协作完成了许多不可思议的惊天杰作,从而呈现出一幅又一幅美妙的生活全景图。我们习惯将这种组合称为团队,团队讲求的是分工与协作,谁也离不开谁。在Java EE的世界里,充满了身怀绝技的Java类,通过这云云Java类的分工协作,造就了五彩缤纷的Java EE 应用王国。
当我们需要别人的帮助才能完成某项工作时,毫不犹豫地就会想到"请",这种"请"无疑是一种盛情的、主动的"请求"。沿用这种处事风格,我们的Java EE应用开发走过了漫长的"请"的岁月。例如:
1./** 负责处理用户管理底层数据访问 */
2.public class UserDao{
3.//保存用户数据
4.public void saveUser(User user){
5.//进行数据的保存工作,省略代码
6. }
7. ......
8.}
9.
10./** 负责处理用户管理业务逻辑 */
11.public class UserService{
12.//主动创建数据访问组件
13. UserDao dao = new UserDao();
14.//处理新增用户业务逻辑
15.public void addUser(String userName,String userPwd){
16. User user = new User();
17. user.setUserName(userName);
18. user.setUserPwd(userPwd);
19.//调用UserDao的saveUser方法进行数据保存
20. dao.saveUser(user);
21. }
22. ......
23.}
24.
25./** 负责处理用户管理业务请求 */
26.public class UserAction{
27.//主动创建业务逻辑处理组件
28. UserService service = new UserService();
29.//处理新增用户请求
30.public void addUser(){
31. String userName = "liubin";
32. String userPwd = "123456";
33.//调用UserService的addUser方法进行业务逻辑处理
34. service.addUser(userName,userPwd);
35. }
36. ......
37.}
上面这种风格(见图6-2)的代码是那么眼熟,又是那么亲切,毕竟用了那么多年,不知是习惯了还是麻木了。在一个类(如UserAction类)中需要用到另一个类(如UserService类),也就是一个类依赖另一个类时,顺手就是一个"new",当应用变得庞大,依赖关系网也随之复杂,为测试与维护带来的困难呈指数上升。
那能不能做到"不请自来"呢?答案是可以的。想象一下,如果有一座工厂专门负责生产Bean实例的话,"new"的工作就可以交给它了,倘若还能"送货上门"的话,自然也就"不请自来"。这就是Spring最初的动机,自Spring呱呱坠地之日起,理想变为现实,从此Java EE应用开发步入了"不请自来"的时代。例如:
1./** 负责处理用户管理底层数据访问 */
2.public class UserDao{
3.//保存用户数据
4.public void saveUser(User user){
5.//进行数据的保存工作,省略代码
6. }
7. ......
8.}
9.
10./** 负责处理用户管理业务逻辑 */
11.public class UserService{
12.//仅声明数据访问组件的引用
13. UserDao dao;
14.//处理新增用户业务逻辑
15.public void addUser(String userName,String userPwd){
16. User user = new User();
17. user.setUserName(userName);
18. user.setUserPwd(userPwd);
19.//调用UserDao的saveUser方法进行数据保存
20. dao.saveUser(user);
21. }
22.//提供一条UserDao对象的注入通道
23.public void setDao(UserDao dao){
24.this.dao=dao;
25. }
26. ......
27.}
28.
29./** 负责生产与分发Bean的小作坊 */
30.public class BeanFactory{
31.//生产一个数据访问组件
32. UserDao dao = new UserDao();
33.//再生产一个业务逻辑处理组件
34. UserService service = new UserService();
35.//将数据访问组件装配到业务逻辑处理组件中
36. service.setDao(dao);
37.//分发业务逻辑处理组件
38.public UserService getUserService(){
39.return service
40. }
41.}
42.
43./** 负责处理用户管理业务请求 */
44.public class UserAction{
45.//实例化Bean小作坊
46. BeanFactory beanFactory=new BeanFactory();
47.//从Bean小作坊中取出业务逻辑处理组件实例
48. UserService service=beanFactory.getUserService();
49.//处理新增用户请求
50.public void addUser(){
51. String userName = "liubin";
52. String userPwd = "123456";
53.//调用UserService的addUser方法进行业务逻辑处理
54. service.addUser(userName,userPwd);
55. }
56. ......
57.}
上述代码中,BeanFactory是我们特意设计的一个Bean小作坊,用于生产、装配与分发Bean实例。大家可以看到,业务控制器UserAction中从Bean小作坊取回的业务逻辑组件是已经完全装配好的成品Bean,在业务逻辑组件UserService中再也看不到往昔"new"的身影了,这种基于Bean容器的设计模式,使控制层、逻辑层及数据层实现充分解耦,如图6-3所示,可以提高可测试性与可维护性
于是很多保守与固执的"老资历"Java EE工程师开始呐喊:"反了!反了!!"。没错,是反了,是控制权发生了反转,应用本身不再负责依赖对象的创建及维护,依赖对象的创建及维护交由外部容器负责。这样控制权就由应用转移到了外部容器,这种控制权的转移就是所谓的控制反转或反转控制。
所谓依赖注入就是指程序在运行期,由外部容器动态地将依赖对象注入到组件中。这种依赖注入的过程就如同生产车间将零件装配到机器上一样,注入的过程其实就是一种装配的过程。
正是基于这种思想,Spring才推出了功能强大的Bean工厂与控制反转IoC容器,在IoC容器里通过简单地装配完成Bean实例之间的依赖注入
6.1.4 何为"面向切面编程AOP"
在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:
日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代
码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真
下面我们以用户管理业务逻辑组件UserService的AOP实现过程(见图6-6)为例,深度剖析一下AOP技术的实现原理。AOP技术是建立在Java语言的反射机制与动态代理机制之上的。业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。
现将图6-6中涉及到的一些概念解释如下。
切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。
通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。
切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
6.1.6 开始Spring 2.5旅程--Hello World6
在对Spring 2.5有了一个初步的了解之后,下面我们将以Spring 2.5在Java App中的应用SpringHelloWorld项目为例,全面演示一下Spring的基本使用步骤,请大家重点注意一下面向接口编程的实际应用及applicationContext.xml的具体配置。
(1)创建一个"Java Project",命名为"SpringHelloWorld"。
(2)创建一个文件夹"lib"用于存放Spring 2.5框架的Jar包。
(3)将Spring 2.5所需的spring-beans.jar、spring-context.jar、spring-core.jar、commons- attributes-compiler.jar、commons-attributes-api.jar、commons-logging.jar及log4j-1.2.15.jar 复制到lib文件夹下,将这些Jar包加入到项目的Classpath中。
(4)创建以下几个包,用于存放项目的类或接口:test.spring.dao包(存放数据访问接口)、test.spring.dao.impl包(存放数据访问接口实现)、test.spring.service包(存放业务逻辑接口)、test.spring.service.impl包(存放业务逻辑接口实现)、test.spring.action 包(存放业务控制组件)、test.spring.bean包(存放ORM对象)、test.spring.junit包(存放Junit测试用例)。
(5)编写用户ORM类User,代码如下:
1.package test.spring.bean;
2./** 用户持久化类 */
3.public class User {
4.Integer id;//自然ID号
5.String userName; //用户名
6.String userPwd;//用户密码
7.//默认构造方法
8.public User(){}
9.//自定义构造方法
10.public User(Integer id, String userName, String userPwd)
{
11.this.id = id;
https://www.wendangku.net/doc/8712610463.html,erName = userName;
https://www.wendangku.net/doc/8712610463.html,erPwd = userPwd;
14.}
15.//省略属性的get/set方法对
16.}
(6)编写数据访问接口UserDao,代码如下:
1.package test.spring.dao;
2.import https://www.wendangku.net/doc/8712610463.html,er;
3./** 用户管理底层数据访问接口 */
4.public interface UserDao {
5.//处理新增用户业务逻辑
6.public void addUser(User user);
7.//处理装载用户业务逻辑
8.public User loadUser(Integer id);
9.//处理修改用户业务逻辑
10.public void modiUser(User user);
11.//处理删除用户业务逻辑
12.public void delUser(Integer id);
13.}
(7)编写数据访问接口实现类UserDaoImpl,代码如下:
1.package test.spring.dao.impl;
2.import https://www.wendangku.net/doc/8712610463.html,er;
3.import https://www.wendangku.net/doc/8712610463.html,erDao;
4./** 用户管理底层数据访问接口实现 */
5.public class UserDaoImpl implements UserDao {
6.//处理新增用户业务逻辑
7.public void addUser(User user) {
8.//由于该实例未实现数据库访问,省略插入数据库记录的代码
9.System.out.println("用户名为"+user.getUserName()+"的用户新
增成功!");
10.}
11.//处理删除用户业务逻辑
12.public void delUser(Integer id) {
13.//由于该实例未实现数据库访问,省略删除数据库记录的代码
14.System.out.println("ID号为"+id+"的用户删除成功!");
15.}
16.//处理装载用户业务逻辑
17.public User loadUser(Integer id) {
18.//由于该实例未实现数据库访问,省略读取数据库记录的代码
19.return new User(1,"liubin","123456");
20.}
21.//处理修改用户业务逻辑
22.public void modiUser(User user) {
23.//由于该实例未实现数据库访问,省略更新数据库记录的代码
24.System.out.println("ID号为"+user.getId()+"的用户修改成
功!");
25.}
26.}
(8)编写业务逻辑接口UserService,代码如下:
1.package test.spring.service;
2.import https://www.wendangku.net/doc/8712610463.html,er;
3./** 用户管理业务逻辑接口 */
4.public interface UserService {
5.//处理新增用户业务逻辑
6.public void addUser(String userName,String userPwd);
7.//处理装载用户业务逻辑
8.public User loadUser(Integer id);
9.//处理修改用户业务逻辑
10.public void modiUser(Integer id,String userName,String u
serPwd);
11.//处理删除用户业务逻辑
12.public void delUser(Integer id);
13.}
(9)编写业务逻辑接口实现类UserServiceImpl,代码如下:
1.package test.spring.service.impl;
2.import https://www.wendangku.net/doc/8712610463.html,er;
3.import https://www.wendangku.net/doc/8712610463.html,erDao;
4.import https://www.wendangku.net/doc/8712610463.html,erService;
5./** 用户管理业务逻辑接口实现 */
6.public class UserServiceImpl implements UserService {
7.//仅声明数据访问组件的引用
https://www.wendangku.net/doc/8712610463.html,erDao dao;
9.//处理新增用户业务逻辑
10.public void addUser(String userName, String userPwd) {
https://www.wendangku.net/doc/8712610463.html,er user = new User();
https://www.wendangku.net/doc/8712610463.html,er.setUserName(userName);
https://www.wendangku.net/doc/8712610463.html,er.setUserPwd(userPwd);
14.dao.addUser(user);
15.}
16.//处理删除用户业务逻辑
17.public void delUser(Integer id) {
18.dao.delUser(id);
19.}
20.//处理装载用户业务逻辑
21.public User loadUser(Integer id) {
22.return dao.loadUser(id);
23.}
24.//处理修改用户业务逻辑
25.public void modiUser(Integer id, String userName, String
userPwd) {
https://www.wendangku.net/doc/8712610463.html,er user = dao.loadUser(id);
https://www.wendangku.net/doc/8712610463.html,er.setUserName(userName);
https://www.wendangku.net/doc/8712610463.html,er.setUserPwd(userPwd);
29.dao.modiUser(user);
30.}
31.//提供一条UserDao对象的注入通道
32.public void setDao(UserDao dao){
33.this.dao=dao;
34.}
35.}
(10)编写业务控制器UserAction,代码如下:
1.package test.spring.action;
2.import https://www.wendangku.net/doc/8712610463.html,er;
3.import https://www.wendangku.net/doc/8712610463.html,erService;
4./** 用户管理业务控制器 */
5.public class UserAction {
6.//仅声明业务逻辑组件的引用
https://www.wendangku.net/doc/8712610463.html,erService service;
8.//处理新增用户请求
9.public void addUser() {
10.String userName="liubin";
11.String userPwd="123456";
12.service.addUser(userName,userPwd);
13.}
14.//处理删除用户请求
15.public void delUser() {
16.service.delUser(1);
17.}
18.//处理装载用户请求
19.public void loadUser() {
https://www.wendangku.net/doc/8712610463.html,er user = service.loadUser(1);
21.System.out.println("用户名="+user.getUserName());
22.}
23.//处理修改用户请求
24.public void modiUser() {
25.Integer id = 1;
26.String userName="liujunyu";
27.String userPwd="123456";
28.service.modiUser(id, userName, userPwd);
29.}
30.//提供一条UserService对象的注入通道
31.public void setService(UserService service) {
32.this.service = service;
33.}
34.}
(11)创建Spring 2.5的配置文件applicationContext.xml,并在该配置文件中配置数据访问实现类UserDaoImpl、业务逻辑实现类UserServiceImpl、业务控制器UserAction,代码如下:
1.
2. 3.xmlns="https://www.wendangku.net/doc/8712610463.html,/schema/beans" 4.xmlns:xsi="https://www.wendangku.net/doc/8712610463.html,/2001/XMLSchema-instance" 5.xsi:schemaLocation="https://www.wendangku.net/doc/8712610463.html,/schem a/beans 6.https://www.wendangku.net/doc/8712610463.html,/schema/beans/spring-beans -2.5.xsd"> 7. 8. > 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.
(12)为UserAction创建一个Junit的测试testUserAction,分别对其中的方法进行测试,代码如下:
1.package test.spring.junit;
2.import org.junit.BeforeClass;
3.import org.junit.Test;
4.import org.springframework.context.ApplicationContext;
5.import org.springframework.context.support.
6.ClassPathXmlApplicationContext;
7.import https://www.wendangku.net/doc/8712610463.html,erAction;
8./** 用户管理业务控制器的测试用例 */
9.public class testUserAction {
10.static ApplicationContext cxt;
11.static UserAction userAction;
12.//初始化ApplicationContext容器
13.@BeforeClass
14.public static void setUpBeforeClass() throws Exception {
15.//使用ClassPathXmlApplicationContext方式初始化
ApplicationContext容器
16.cxt = new ClassPathXmlApplicationContext("applicationCon
text.xml");
17.//从Bean工厂容器中获取名为"userAction"的UserAction实例
https://www.wendangku.net/doc/8712610463.html,erAction = (UserAction)cxt.getBean("userAction");
19.}
20.//测试UserAction的AddUser方法
21.@Test
22.public void testAddUser() {
https://www.wendangku.net/doc/8712610463.html,erAction.addUser();
24.}
25.//测试UserAction的DelUser方法
26.@Test
27.public void testDelUser() {
https://www.wendangku.net/doc/8712610463.html,erAction.delUser();
29.}
30.//测试UserAction的LoadUser方法
31.@Test
32.public void testLoadUser() {
https://www.wendangku.net/doc/8712610463.html,erAction.loadUser();
34.}
35.//测试UserAction的ModiUser方法
36.@Test
37.public void testModiUser() {
https://www.wendangku.net/doc/8712610463.html,erAction.modiUser();
39.}
40.}
运行测试用例,控制台打印出预想的提示信息,说明Spring 2.5在SpringHelloWorld项目被成功应用。该实例各组件之间的关系如图6-7所示。
6.2 Spring 2.5核心技术
通过6.1节的学习,大家对Spring 2.5从组成到功能及一些基本概念等应该有了一个初步的了解了。这些功能到底是如何实现的呢?在实际的项目中又该如何应用呢?带着这些疑问,下面我们一起来探究一下Spring 2.5的技术内幕。
6.2.1 Bean工厂之BeanFactory介绍
BeanFactory顾名思义是Bean工厂的意思,采用的是工厂设计模式。Bean工厂的神圣职责就是负责Bean实例的创建、Bean实例之间依赖关系的装配及Bean实例的分发。这是Spring的核心技术之一,作为工厂,"看图生产"是必不可少的,同理,我们必须为Bean工厂提供一份"生产图纸"用于指导生产,这就是前面提到的Spring的配置文件(如applicationContext.xml),Spring并未规定配置文件的命名及数量,起什么名字都行,多个配置文件也可以。Spring容器在实例化时就会装载这些配置文件,并进行"看图生产"。
基于"面向接口编程"的指导思想,BeanFactory被定义为接口,XmlBeanFactory 是BeanFactory接口最常用的实现类。XmlBeanFactory通过装载指定的XML配置文件实例化BeanFactory容器。例如:
1.//通过装载指定的XML配置文件实例化BeanFactory容器
2.Resource res = new FileSystemResource("c:/applicationCon
text.xml");
3.BeanFactory factory = new XmlBeanFactory(res);
4.//从Bean工厂容器中获取名为"userAction"的UserAction实例
https://www.wendangku.net/doc/8712610463.html,erAction = (UserAction)factory.getBean("userAction");
BeanFactory容器实例化之后,我们就可以使用getBean方法从BeanFactory容器中获取装配好的Bean实例了。
Spring开发团队在设计Spring时,考虑到一些受限资源的应用场合,譬如PDA设备上的Java应用等,故特意将BeanFactory容器尽可能地精简,以减少资源的消耗。但对于那些需要国际化支持、Bean事件发布与监听的应用来讲,BeanFactory显然有点力不从心了。于是通过继承BeanFactory接口,又创建了ApplicationContext与WebApplicationContext等子接口,用以满足Java EE应用的开发。
6.2.2 实用的Bean工厂ApplicationContext
ApplicationContext的中文意思是"应用上下文",它继承自BeanFactory接口,除了包含BeanFactory的所有功能之外,在国际化支持、资源访问(如URL和文件)、事件传播等方面进行了良好的支持,被推荐为Java EE应用之首选,可应用在Java APP 与Java Web中。
在ApplicationContext接口的众多实现类中,有3个是我们经常用到的(见表6-1),并且使用这3个实现类也基本能满足我们Java EE应用开发中的绝大部分需求。
表6-1 ApplicationContext接口的常用实现类介绍
这些实现类的主要区别就是装载Spring配置文件实例化ApplicationContext容器的方式不同,在ApplicationContext实例化后,同样通过getBean方法从ApplicationContext容器中获取装配好的Bean实例以供使用。
与BeanFactory不同的是,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。而BeanFactory容器实例化后并不会自动实例化Bean,只有当Bean被使用时BeanFactory容器才会对该Bean 进行实例化与依赖关系的装配。
在Java项目中通过ClassPathXmlApplicationContext类手动实例化ApplicationContext容器通常是不二之选。但对于Web项目就不行了,Web项目的启动是由相应的Web服务器负责的,因此,在Web项目中ApplicationContext容器的实例化工作最好交给Web服务器来完成。
Spring为此提供了两种解决方案,一种是基于ContextLoaderListener实现的(此方案只适用于Servlet 2.4及以上规范的Servlet容器)。例如,在web.xml中加入如下代码:
1.
2.
3.
4. e> 5. 6. 7. 8. 9.ContextLoaderListener 10. 别一种方案则是基于ContextLoaderServlet实现的。例如,在web.xml中加入如下代码: 1. 2. 3. 4. e> 5. 6. 7. 8. 9. 10.ContextLoaderServlet 11. 12.