本文目的是理解Spring的功能。对于一个初学者来说,可能很难理解Spring到底是干什么的,我们为什么要使用Spring,尤其是那些没有学过EJB的同学。本文将会带领您初窥Spring的神秘世界。
首先,作为一个大的工程,不能急于忙着一口气把他写完。作为一个有经验的编程人员或者设计师,会将整个复杂的过程分成几步来完成,在保证每一部分无误的情况下,才能使整个工程顺利完成。
本文不但讲解如何将Spring和Hibernate结合起来,同时也讲解其设计和实现方式。
本文从Hibernate着手,对实体类测试无误后,在进行Spring服务部分的编写,整个工程目录如下:
第一部分Hibernate部分的编写与测试:
先要实现model中的类,因为在设计之初就要完成实体模型的抽象和数据库表的建立。Student.java
package com.guan.springHibernateTest.model;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Student {
private String name;
private String password;
private String studentId;
private Date birthDay;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
@GeneratedValue
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
这个实体类,没有什么可解释的,如果大家不明白,请参考我前面关于Hibernate简介那篇文章。
然后就是Hibernate的配置文件:
hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/guanxinquan</property>
<property name="connection.username">root</property>
<property name="connection.password">gxq</property>
<!-- JDBC connection pool (use the built-in) -->
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<property name="current_session_context_class">thread</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping class="com.guan.springHibernateTest.model.Student"/>
</session-factory>
</hibernate-configuration>
然后需要写一个测试,来测试Hibernate是否正确配置。
StudentModelTest.java
package com.guan.springHibernateTest.model;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class StudentModelTest {
private static SessionFactory sessionFactory = null;
@BeforeClass
public static void beforeClass()
{
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void createStudentTest()
{
Student student = new Student();
student.setBirthDay(new Date());
student.setName("张三");
student.setStudentId("S05080412");
student.setPassword("zs");
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(student);
session.getTransaction().commit();
session.close();
}
@AfterClass
public static void afterClass()
{
sessionFactory.close();
}
public static void main(String[] args){
beforeClass();
}
}
注意,在这个测试中,我们向数据库中添加了一个同学。在后面的Spring的测试中,我们需要获取这个同学的信息。
需要注意的是,model的测试程序与原有model的包目录是相同的,只不过是最外层包不同,这样做在引用具体的对象时,不需要import,就向上面写道的那样,并不需要引入Student类。当然了,这样写的真正目的是一种规范,方便测试和管理,在对编写的每一个类都有这样的一个测试程序,会使得程序更健壮,更测试和排错。
如果您对以上的内容不是很清楚,请您查看我的关于Hibernate简介那篇文章,这样您将会对上面的代码更加清晰。
第二部分 编写服务。
对于一个完整的服务来说,不可能让用户或客户端直接对自身的数据进行操作,而是由服务对客户端或用户以接口的方式提供相应的功能。在服务端,服务也不是直接访问数据的,因为服务完成的是控制层面的业务逻辑,对于更底层的操作不能在服务程序端实现,这样就抽象出来低一层的数据访问层dao(Data Access Object)就是数据访问接口。
实际上,客户端不能直接操作服务的实现,在一个完整的服务中,需要对服务抽象出接口(本例子并没有抽象这个接口,在后续文档中用到时再解释)。
话归正题,前面我们实现了model层的功能,下面需要做的是建立服务的架子,之所以说是架子,是由于这个实现过程并不关心其内部的实现内容是什么,而只关心这些接口和实现间的关系。
现在,需要写一个beans.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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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-2.5.xsd">
<!—上面的不解释,每次都这么用就行了,下面这句话告诉Spring要扫描这个包下的所有文件,并解释执行Spring的标记 -->
<context:component-scan base-package="com.guan.springHibernateTest"/>
<!—创建了一个bean(就是一个实例)这个bean是数据库的配置文件,这里我们不再想把数据库配置文件和Spring的配置文件放在一起了,让其单独成一个文件,为以后更改数据库提供方便 -->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:database.properties</value>
</property>
</bean>
<!—创建一个数据源,这个数据源将会赋值给SessionFactory,内容不解释了很容易理解,注意${}是EL表达式,就是取这个变量的值,这些值在database.properties文件中配置,下面会给出这个文件,一看就明白什么意思了 -->
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!—创建了一个SessionFactory的工厂实例,并赋值了一定的属性-->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<!—这里告诉Hibernate需要解析哪些实体类,每添加一个实体类,就在这里添加一个value--> <value>com.guan.springHibernateTest.model.Student</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
${jdbc.dialect}
</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="current_session_context_class">thread</prop>
<prop key="format_sql">true</prop>
<prop key="hbm2ddl.auto">create</prop>
</props>
</property>
</bean>
</beans>
上面这个beans文件,您也不必太上心,只要能看懂,会修改就行了,实际上也没什么可修改的,唯一值得修改的就是Hibernate中的list,当您增加了实体类,就将这个实体添加到那个位置就可以了。另外component-scan要改成您的包。
给出数据库配置文件 database.properties。
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/guanxinquan
jdbc.username=root
jdbc.password=gxq
jdbc.dialect=org.hibernate.dialect.MySQLDialect
这个文件,是对数据库的配置,如果您需要修改数据库,代码的其他部分是无需修改的,仅仅是修改这个文件就可以了。
下面是服务配置部分,首先需要在impl中添加一个SuperImpl.java实际上不是必须的,我习惯这样写。这样写的好处就是在所有剩下的impl类中都继承这个父类,就省去了引用Hibernate的SessionFactory的麻烦:
SuperImpl.java
package com.guan.springHibernateTest.impl;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class SuperImpl extends HibernateDaoSupport{
@Autowired/*告诉Spring,当创建这个类的实例的时候,要注入一个SessionFactory类型的一个实体,就是说将beans.xml中创建的sessionFactory注入进来,就是把他的引用传进来 如果您对注入的概念不清晰,请参考Spring的官方文档*/
public void setHibernateSessionFactory(SessionFactory sessionFactory)
{
this.setSessionFactory(sessionFactory);
}
}
注入:就是对象在创建之前确定其依赖的对象,然后由系统将与之相关的对象直接传递给他。如果您还不明白,更白话点说就是相当于您写了一个构造函数,这个构造函数有一些参数,这些参数在对象实例化的时候需要您提供其实例。但是这样理解并是注入的真正含义。
下面给出Dao
StudentDao.java
package com.guan.springHibernateTest.dao;
import com.guan.springHibernateTest.model.Student;
public interface StudentDao {
}
开始写Dao的时候并不关心内部到底需要提供哪些接口,实际上我们现在只是在搭个架子,然后我们采用用例驱动来完成真个内部功能。
然后要写dao的实现:
package com.guan.springHibernateTest.impl;
import org.springframework.stereotype.Component;
import com.guan.springHibernateTest.dao.StudentDao;
import com.guan.springHibernateTest.model.Student;
@Component("studentDao")/*这个标记标示在Spring初始化的时候要创建这样的一个实例,并且这个实例(有人可能叫做对象)的名字叫(studentDao)。实际上在Spring框架中这个实例是一个bean,他很可能注入到其他的实例中*/
/*注意这个实例继承了SuperImpl并且实现了StudentDao(尽管现在还没写但是一会就填上)*/
public class StudentDaoImpl extends SuperImpl implements StudentDao{
}
然后,将要写一个服务
StudentService.java
package com.guan.springHibernateTest.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.guan.springHibernateTest.dao.StudentDao;
import com.guan.springHibernateTest.model.Student;
@Component("studentService")/*告诉Spring在框架加载的时候要生成一个这个类型并叫做studentService的实例*/
public class StudentService implements StudentDao{
private StudentDao studentDao;//这里定义了一个变量
public StudentDao getStudentDao() {
return studentDao;
}
@Autowired/*在Spring框架加载时要将StudentDao这个属性注入进来,如果您还没有忘记的话这个StudentDao刚刚在StudentDaoImpl.java中实现的*/
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
}
最后需要写一个测试程序:
StudentServiceTest.java
package com.guan.springHibernateTest.service;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.guan.springHibernateTest.model.Student;
public class StudentServiceTest {
@Test
public void StudentGetTest()
{
ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");/*通知Spring要分析beans.xml这个文件,就是告诉Spring它的配置描述文件在哪里*/
/*然后获得那个叫studentService的实例,然后我们就会使用这个实例来进行操作*/
StudentService studentService = (StudentService) appContext.getBean("studentService");
}
}
可能您看到这里已经是脑中一片混乱了,我简单的解释下Spring的这个流程:首先在测试程序中我们执行了ApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml");这句,告诉Spring处理beans.xml(实际上用告诉是不合理的,就是调用了Spring),Spring分析xml文档,发现有个SessionFactory的bean,就创建了一个叫sessionFactory的对象(我习惯叫实例),然后再xml文档中告诉Spring要扫描com.guan.springHibernateTest 下的所有类<context:component-scan base-package="com.guan.springHibernateTest"/>
这样Spring扫描到SuperImpl实现时需要注入SessionFactory,并且自己现在已经有了这样的实例。然后Spring又发现StudentDaoImpl要求在框架加载时创建他@Component("studentDao"),并且它又继承于SuperImpl,这样Spring创建StudentDaoImpl的实例,并将现在已经创建好的sessionFactory注入给他。当Spring继续扫描,发现studentService在框架加载的时候需要创建它@Component("studentService"),并且需要注入一个StudentDao类型的对象,很明显我们刚刚创建的studentDao正是这个类型(注意studentDao是StudentDaoImpl类型,但是StudentDaoImpl实现了StudentDao接口,所有StudentDaoImpl也是StudentDao类型),于是创建StudentService并将studentDao注入其中。
然后执行测试程序中的第二句:
StudentService studentService = (StudentService) appContext.getBean("studentService");
应用程序要从Spring中获取一个叫studentService的实例,不正是我们上面创建的那个吗,于是Spring就将这个实例反馈给用户实例。
如果您还是不明白,你可以和我一起探讨。
另外需要明白的是,Spring生成的这些bean一般都是单体类,就是说在整个过程中只有一个类的实例。一般这种单体类是很常见的,对应控制类来说,都应该建立成这种单体类。