第一篇:Java回顾之I/O
第二篇:Java回顾之网络通信
第三篇:Java回顾之多线程
第四篇:Java回顾之多线程同步
第五篇:Java回顾之集合
第六篇:Java回顾之序列化
第七篇:Java回顾之反射
第八篇:Java回顾之一些基础概念
第九篇:Java回顾之JDBC
第十篇:Java回顾之ORM框架
我计划分两到三篇文章来描述Spring,这一篇主要讲Spring一些基础的内容。
概述
我印象4、5年前,我还做java开发的时候,Spring是一个非常火的框架,尤其是在Web开发领域,和Struts以及Hibernate构成了SSH三剑客。当时Web开发的另一个组合是LAMP,即Linux+Apache+MySQL+PHP。我在前端方面基本没有实战经验,对js等技术也还是停留在概念和语法方面,所以扬长避短,我对Spring以及Hibernate特别感兴趣。
当年Spring是作为EJB的“替代者”横空出世的,其创始人Rod Johnson还写了一本《J2EE development without EJB》来推行这个框架,这也是一本关于Spring很经典的书,不过最好是在接触Spring一段时间后再去阅读,效果会好一点。
Spring最主要的特点有两个:IoC和AOP,这也是J2EE开发企业软件时经常碰到的问题:1)对象太多如何管理;2)共同逻辑和业务逻辑纠缠在一起,错综复杂,如何解耦。
这篇文章主要关注3个方面:IoC、AOP和数据库访问。这里我们假设所有需要的jar都已经准备就绪。
IoC
IoC的全称是Inversion of Control,中文称为控制反转, Martin Flower由根据它创造了一个新词:Dependency Injection,中文称为依赖注入。这两个词讲的是一回事儿。
IoC的实质是如何管理对象,传统意义上我们使用new方式来创建对象,但在企业应用开发的过程中,大量的对象创建都在程序中维护很容易造成资源浪费,并且不利于程序的扩展。
实现IoC通常有三种方式:
1)利用接口或者继承,一般以接口较多。这种实现方式和我们平时提到的lazy load有异曲同工之妙。
2)构造函数注入。
3)属性注入。
IoC是Spring框架的核心,接下来我们来探索一下Spring中IoC的风采。
IoC简单示例
我们先来定义一个简单的接口和实现:
1 public interface UserDao { 2 void save(); 3 } 4 5 public class UserDaoImpl implements UserDao 6 { 7 8 public void save() { 9 System.out.println("save() is called."); 10 } 11 12 }
然后是在classpath下创建一个beans.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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean id="userDaoImpl" class = "sample.spring.ioc.UserDaoImpl"/> </beans>
接下来是测试代码:
1 private static void test1() 2 { 3 ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/beans.xml"); 4 UserDao userDao = (UserDao)ctx.getBean("userDaoImpl"); 5 userDao.save(); 6 }
输出结果如下:
save() is called.
我们还可以通过工厂方式来创建对象。
通过静态工厂创建Bean
添加一个类,如下:
1 public class UserDaoFactory { 2 3 public static UserDao getUserDao() 4 { 5 return new UserDaoImpl(); 6 } 7 }
在beans.xml中,添加如下内容:
1 <bean id="userDaoImpl2" class = "sample.spring.ioc.UserDaoFactory" factory-method = "getUserDao"/>
测试代码和执行结果和上面类似,不再赘述。
通过实例工厂创建Bean
添加如下类:
1 public class UserDaoFactory2 2 { 3 public UserDao getUserDao() 4 { 5 return new UserDaoImpl(); 6 } 7 }
这个类和UserDaoFactory唯一的区别是这里的getUserDao是实例方法,而不是静态方法。
在beans.xml中追加如下内容:
1 <bean id="factory" class="sample.spring.ioc.UserDaoFactory2"/> 2 <bean id="userDaoImpl3" factory-bean="factory" factory-method="getUserDao"/>
测试方法和结果同上。
对象的生命周期
我们可以通过设置bean节点的scope属性来控制对象的声明周期,它包含两个可选值:
1)singleton,表明系统中对于同一个对象,只保留一个实例。
2)prototype,表明系统中每次获取bean时,都新建一个对象。
我们修改beans.xml文件:
1 <bean id="userDaoImpl" class = "sample.spring.ioc.UserDaoImpl" scope="singleton"/> 2 <bean id="userDaoImpl2" class = "sample.spring.ioc.UserDaoImpl" scope="prototype"/>
这两个bean指向同一个类型,但是scope的设置不同。
下面是测试方法:
1 private static void scopeTest() 2 { 3 ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/scope.xml"); 4 System.out.println("=====Singleton test====="); 5 UserDao userDao1A = (UserDao)ctx.getBean("userDaoImpl"); 6 UserDao userDao1B = (UserDao)ctx.getBean("userDaoImpl"); 7 System.out.println("userDao1A == userDao1B:" + (userDao1A==userDao1B)); 8 System.out.println("=====Prototype test====="); 9 UserDao userDao2A = (UserDao)ctx.getBean("userDaoImpl2"); 10 UserDao userDao2B = (UserDao)ctx.getBean("userDaoImpl2"); 11 System.out.println("userDao2A == userDao2B:" + (userDao2A==userDao2B)); 12 }
执行结果如下:
=====Singleton test===== userDao1A == userDao1B:true =====Prototype test===== userDao2A == userDao2B:false
如何设置对象属性
上面的示例中,我们的对象中没有包含属性,对于业务对象来说,这一般是不现实。实际中的对象或多或少都会有一些属性。
Spring支持两种方式对属性赋值:set方式和构造函数。
下面我们会分别描述两种方式,但首先我们需要展示业务对象:
1 public class UserServiceBean 2 { 3 private int userID; 4 private String userName; 5 private UserDao userDao; 6 private List<String> hobbies; 7 private Map<String, Integer> scores; 8 9 public UserServiceBean(int userID, String userName, UserDao userDao, List hobbies,Map scores) 10 { 11 this.userID = userID; 12 this.userName = userName; 13 this.userDao = userDao; 14 this.hobbies = hobbies; 15 this.scores = scores; 16 } 17 18 public UserServiceBean(){} 19 20 public void setUserID(int userID) { 21 this.userID = userID; 22 } 23 public int getUserID() { 24 return userID; 25 } 26 public void setUserName(String userName) { 27 this.userName = userName; 28 } 29 public String getUserName() { 30 return userName; 31 } 32 public void setUserDao(UserDao userDao) { 33 this.userDao = userDao; 34 } 35 public UserDao getUserDao() { 36 return userDao; 37 } 38 public void setHobbies(List<String> hobbies) { 39 this.hobbies = hobbies; 40 } 41 public List<String> getHobbies() { 42 return hobbies; 43 } 44 public void setScores(Map<String, Integer> scores) { 45 this.scores = scores; 46 } 47 public Map<String, Integer> getScores() { 48 return scores; 49 } 50 }
定义UserServiceBean
这是一个典型的学生信息,包括学号、姓名、爱好和成绩。
通过Set方式为对象属性赋值
我们在beans.xml中追加如内容:
1 <bean id="userService" class="sample.spring.ioc.UserServiceBean"> 2 <property name="userID" value="1"/> 3 <property name="userName" value="张三"/> 4 <property name="userDao" ref="userDaoImpl"/> 5 <property name="hobbies"> 6 <list> 7 <value>羽毛球</value> 8 <value>看电影</value> 9 <value>弹吉他</value> 10 </list> 11 </property> 12 <property name="scores"> 13 <map> 14 <entry key="数据结构" value="90"/> 15 <entry key="编译原理" value="85"/> 16 <entry key="离散数学" value="82"/> 17 </map> 18 </property> 19 </bean>
上面是典型的为属性赋值的示例,其中属性不仅包括简单属性(整数、字符串),也包含了复杂属性(List、Map),还有其他的bean。
下面是测试代码:
1 private static void propertyTest1() 2 { 3 ApplicationContext ctx = new ClassPathXmlApplicationContext("sample/spring/ioc/beans.xml"); 4 UserServiceBean userService = (UserServiceBean)ctx.getBean("userService"); 5 printUserService(userService); 6 } 7 8 private static void printUserService(UserServiceBean userService) 9 { 10 System.out.println("编号:" + userService.getUserID()); 11 System.out.println("姓名:" + userService.getUserName()); 12 System.out.println("爱好:"); 13 for(String hobby:userService.getHobbies()) 14 { 15 System.out.println(hobby); 16 } 17 System.out.println("学习成绩:"); 18 for(Entry<String,Integer> entry:userService.getScores().entrySet()) 19 { 20 System.out.println(entry.getKey() + "\t" + entry.getValue()); 21 } 22 userService.getUserDao().save(); 23 }
输出结果如下:
编号:1 姓名:张三 爱好: 羽毛球 看电影 弹吉他 学习成绩: 数据结构 90 编译原理 85 离散数学 82 save() is called.
通过构造函数为对象属性赋值
我们也可以通过构造函数来为对象赋值,在上面定义UserServiceBean时,我们已经添加了一个构造函数。下面来看beans.xml中的配置:
1 <bean id="userService2" class="sample.spring.ioc.UserServiceBean"> 2 <constructor-arg index="0" value="1"/> 3 <constructor-arg index="1" value="张三"/> 4 <constructor-arg index="2" ref="userDaoImpl"/> 5 <constructor-arg index="3"> 6 <list> 7 <value>羽毛球</value> 8 <value>看电影</value> 9 <value>弹吉他</value> 10 </list> 11 </constructor-arg> 12 <constructor-arg index="4"> 13 <map> 14 <entry key="数据结构" value="90"/> 15 <entry key="编译原理" value="85"/> 16 <entry key="离散数学" value="82"/> 17 </map> 18 </constructor-arg> 19 </bean>
测试代码和输出结果同上。
需要注意:我们定义的业务对象应该保留默认的构造函数。
使用Annotation来定位Bean
在Spring中,除了在xml配置文件中定义对象,我们还可以使用Annotation来定位,这位我们提供了很大的方便。
这里我们使用的Annotation主要包括:@Resource/@Autowried/@Qualifier。
来看下面的示例:
1 public class UserServiceBean2
2 {
3 private