一个Maven实现的账户服务模块

下面是一个账号注册服务的account-persist模块。该模块负责账号数据的持久化,以XML的形式保存账户数据,并支持账户的创建、读取、更新、删除等操作。


1.模块的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.juvenxu.mvnbook.account</groupId>
  <artifactId>account-persist</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <name>Account Persist</name>
  
  <dependencies>
  	<dependency>
  		<groupId>dom4j</groupId>
  		<artifactId>dom4j</artifactId>
  		<version>1.6.1</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-core</artifactId>
  		<version>4.1.7.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-beans</artifactId>
  		<version>4.1.7.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  		<version>4.1.7.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  		<version>4.1.7.RELEASE</version>
  	</dependency>
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>3.8.1</version>
  		<scope>test</scope>
  	</dependency>
  </dependencies>
  
  <build>
  	<testResources>
  		<testResource>
  			<directory>src/test/resources</directory>
  			<filtering>true</filtering>
  		</testResource>
  	</testResources>
    <plugins>
      <plugin>
      	<groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
      	<groupId>org.apache.maven.plugins</groupId>
      	<artifactId>maven-resources-plugin</artifactId>
      	<configuration>
      		<encoding>UTF-8</encoding>
      	</configuration>
      </plugin>
    </plugins>
  </build>
</project>


该POM中配置了一些依赖。其中dom4j用来支持XML操作,之后是spring-framwork的依赖,主要用来支持依赖注入。最后是测试范围的junit依赖,用来支持单元测试。

build元素中包含了一个testResources子元素,为了开启资源过滤。build元素下还包含maven的两个插件配置。MyEclipse在产生项目时会选择编译版本,正是在artifactId为maven-compiler-plugin中定义的。maven的插件可以不用用写groupId,可以在setting.xml中配置<setting><pluginGroups><pluginGroup>使Maven检查其它groupId上的插件仓库元数据。另外这里的resources插件是为了使用UTF-8编码处理资源文件。


2.主代码部分

Acocunt类定义了账户的简单模型,包含一些字段柄提供getter和setter方法

//Account.jaca

package com.juvenxu.mvnbook.account.persist;

public class Account {

	private String id;
	private String name;
	private String email;
	private String password;
	private boolean activated;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public boolean isActivated() {
		return activated;
	}

	public void setActivated(boolean activated) {
		this.activated = activated;
	}

}
AccountPersistException是Exception的一个子类,该模块出错时均抛出此错误。

//AccountPersistException.java

package com.juvenxu.mvnbook.account.persist;

@SuppressWarnings("serial")
public class AccountPersistException extends Exception {

	public AccountPersistException(String message) {
		super(message);
	}

	public AccountPersistException(String message, Throwable throwable) {
		super(message, throwable);
	}
}


account-persist对外提供的服务在接口AccountPersistService中定义,其方法对应了账户的增删改查。

//AccountPersistService.java

package com.juvenxu.mvnbook.account.persist;

public interface AccountPersistService {

	Account createAccount(Account account) throws AccountPersistException;

	Account readAccount(String id) throws AccountPersistException;

	Account updateAccount(Account account) throws AccountPersistException;

	void deleteAccount(String id) throws AccountPersistException;

}

AccountPersistService对应的实现为AccountPersistServiceImpl类,它通过操作XML文件实现账户数据的持久化。首先该类包含两个私有方法:readDocumt()和writeDocument(),然后包含了对接口的实现。

//AccountPersistServiceImpl.java

package com.juvenxu.mvnbook.account.persist;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

public class AccountPersistServiceImpl implements AccountPersistService {
	private static final String ELEMENT_ROOT = "account-persist";
	private static final String ELEMENT_ACCOUNTS = "accounts";
	private static final String ELEMENT_ACCOUNT = "account";
	private static final String ELEMENT_ACCOUNT_ID = "id";
	private static final String ELEMENT_ACCOUNT_NAME = "name";
	private static final String ELEMENT_ACCOUNT_EMAIL = "email";
	private static final String ELEMENT_ACCOUNT_PASSWORD = "password";
	private static final String ELEMENT_ACCOUNT_ACTIVATED = "activated";

	private String file;

	private SAXReader reader = new SAXReader();

	public String getFile() {
		return file;
	}

	public void setFile(String file) {
		this.file = file;
	}

	public Account createAccount(Account account)
			throws AccountPersistException {
		Document doc = readDocument();

		Element accountsEle = doc.getRootElement().element(ELEMENT_ACCOUNTS);

		accountsEle.add(buildAccountElement(account));

		writeDocument(doc);

		return account;
	}

	@SuppressWarnings("unchecked")
	public void deleteAccount(String id) throws AccountPersistException {
		Document doc = readDocument();

		Element accountsEle = doc.getRootElement().element(ELEMENT_ACCOUNTS);

		for (Element accountEle : (List<Element>) accountsEle.elements()) {
			if (accountEle.elementText(ELEMENT_ACCOUNT_ID).equals(id)) {
				accountEle.detach();

				writeDocument(doc);

				return;
			}
		}
	}

	@SuppressWarnings("unchecked")
	public Account readAccount(String id) throws AccountPersistException {
		Document doc = readDocument();

		Element accountsEle = doc.getRootElement().element(ELEMENT_ACCOUNTS);

		for (Element accountEle : (List<Element>) accountsEle.elements()) {
			if (accountEle.elementText(ELEMENT_ACCOUNT_ID).equals(id)) {
				return buildAccount(accountEle);
			}
		}

		return null;
	}

	public Account updateAccount(Account account)
			throws AccountPersistException {
		if (readAccount(account.getId()) != null) {
			deleteAccount(account.getId());

			return createAccount(account);
		}

		return null;
	}

	private Account buildAccount(Element element) {
		Account account = new Account();

		account.setId(element.elementText(ELEMENT_ACCOUNT_ID));
		account.setName(element.elementText(ELEMENT_ACCOUNT_NAME));
		account.setEmail(element.elementText(ELEMENT_ACCOUNT_EMAIL));
		account.setPassword(element.elementText(ELEMENT_ACCOUNT_PASSWORD));
		account.setActivated(("true".equals(element
				.elementText(ELEMENT_ACCOUNT_ACTIVATED)) ? true : false));

		return account;
	}

	private Element buildAccountElement(Account account) {
		Element element = DocumentFactory.getInstance().createElement(
				ELEMENT_ACCOUNT);

		element.addElement(ELEMENT_ACCOUNT_ID).setText(account.getId());
		element.addElement(ELEMENT_ACCOUNT_NAME).setText(account.getName());
		element.addElement(ELEMENT_ACCOUNT_EMAIL).setText(account.getEmail());
		element.addElement(ELEMENT_ACCOUNT_PASSWORD).setText(
				account.getPassword());
		element.addElement(ELEMENT_ACCOUNT_ACTIVATED).setText(
				account.isActivated() ? "true" : "false");

		return element;
	}

	private Document readDocument() throws AccountPersistException {
		File dataFile = new File(file);

		if (!dataFile.exists()) {
			dataFile.getParentFile().mkdirs();

			Document doc = DocumentFactory.getInstance().createDocument();

			Element rootEle = doc.addElement(ELEMENT_ROOT);

			rootEle.addElement(ELEMENT_ACCOUNTS);

			writeDocument(doc);
		}

		try {
			return reader.read(new File(file));
		} catch (DocumentException e) {
			throw new AccountPersistException(
					"Unable to read persist data xml", e);
		}
	}

	private void writeDocument(Document doc) throws AccountPersistException {
		Writer out = null;

		try {
			out = new OutputStreamWriter(new FileOutputStream(file), "utf-8");

			XMLWriter writer = new XMLWriter(out,
					OutputFormat.createPrettyPrint());

			writer.write(doc);
		} catch (IOException e) {
			throw new AccountPersistException(
					"Unable to write persist data xml", e);
		} finally {
			try {
				if (out != null) {
					out.close();
				}
			} catch (IOException e) {
				throw new AccountPersistException(
						"Unable to close persist data xml writer", e);
			}
		}
	}
}

看看这里的writeDocument()方法。该方法首先使用变量file构建一个文件输入流,file是AccountPersistServiceImpl的一个私有变量,它的值通过SpringFramework注入。得到输入流后,该方法再使用DOM4J创建一个XMLWriter,这里的OutputFomat.createPrettyPrint()用来创建一个带缩进及换行的友好格式。得到XMLWriter后,就调用其write方法,将Document写入到文件中。该方法的其他代码用作处理流的关闭及异常处理。

readDocument()方法与writeDocument()方法对应,它负责从文件中读取XML数据,也就是Document对象。借助DocumentFactory创建一个Document对象,接着添加XML元素,再把这个不包含任何账户数据的XML文档写入到文件中。如果文件已经被初始化,则该方法使用SAXReader读取文件至Document对象。


用来存储账户数据的XML文件结构很简单,如下是一个包含一个账户数据的文件示例,并不需要在项目中包含

<?xml version="1.0" encoding="UTF-8"?>
<account-persist>
	<accounts>
		<account>
			<id>zachary</id>
			<name>Zachary Chang</name>
			<email>ZacharyChang.zc@gmail.com</email>
			<password>this_is_encrypted</password>
			<activated>false</activated>
		</account>
	</accounts>
</account-persist>

这个XML文件的根元素是account-persist,其下是accounts元素,可以包含多个account元素,每个account元素代表一个账户,其子元素表示该账户的id、姓名、电子邮件、密码以及是否被激活等信息。这时可以返回上面看一下增删改查的实现过程。


另外,还需要一个Spring框架的配置文件,将它放在src/main/resources目录下

//account-persists.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"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<bean id="propertyConfigurer"
		class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="location" value="classpath:account-service.properties" />
	</bean>

	<bean id="accountPersistService"
		class="com.juvenxu.mvnbook.account.persist.AccountPersistServiceImpl">
		<property name="file" value="${persist.file}" />
	</bean>

</beans>

该配置文件首先配置了一个id为peopertyConfigurer的bean,其实现类为PropertyPlaceholerConfigurer,作用是从项目classpath载入名为account-service.properties的配置文件。随后的bean是accountPersistService,实现为AccountPersistServiceImpl,同时这里使用属性persist.file配置其file字段的值。简而言之,XML数据文档的位置是由项目下account-service.properties文件中persist.file属性的值配置的。


3.测试代码部分

测试代码位于src/test/java/目录下,测试资源文件位于src/test/resources/目录下。在以上Spring框架中定义要求项目classpath下有一个名为account-service.properties的文件,且该文件需包含一个persist.file属性,以定义文件存储位置。为了能够测试账户数据的持久化,在测试资源目录下创建文件

//account-service.properties

persist.file=${project.build.testOutputDirectory}/persist-data.xml
该文件只包含一个persist.file属性,其中${project.build.ttestOutputDirectory}部分是一个Maven属性,该属性表示Maven的测试输出目录,其默认地址为项目根目录下的target/test-classes文件夹。即在测试中使用测试输出目录下的ppersist-data.xml文件存储账户数据。


然后编写测试用例AccountPersistService。为了避免冗余,这里只测试readAccount()方法

//AccountPersistServiceTest.java

package com.juvenxu.mvnbook.account.persist;

import static org.junit.Assert.*;

import java.io.File;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AccountPersistServiceTest
{
	private AccountPersistService service;
	
	@Before
	public void prepare()
		throws Exception
	{
		File persistDataFile = new File ( "target/test-classes/persist-data.xml" );
		
		if ( persistDataFile.exists() )
		{
			persistDataFile.delete();
		}
		
		ApplicationContext ctx = new ClassPathXmlApplicationContext( "account-persist.xml" );

		service = (AccountPersistService) ctx.getBean( "accountPersistService" );
    	
    	Account account = new Account();
    	account.setId("juven");
    	account.setName("Juven Xu");
    	account.setEmail("juven@changeme.com");
    	account.setPassword("this_should_be_encrypted");
    	account.setActivated(true);
    	
    	service.createAccount(account);
	}
	
    @Test
    public void testReadAccount()
        throws Exception
    {
        Account account = service.readAccount( "juven" );

        assertNotNull( account );
        assertEquals( "juven", account.getId() );
        assertEquals( "Juven Xu", account.getName() );
        assertEquals( "juven@changeme.com", account.getEmail() );
        assertEquals( "this_should_be_encrypted", account.getPassword() );
        assertTrue( account.isActivated() );
    }

    @Test
    public void testDeleteAccount()
        throws Exception
    {
        assertNotNull( service.readAccount( "juven" ) );
        service.deleteAccount( "juven" );
        assertNull( service.readAccount( "juven" ) );
    }
    
    @Test
    public void testCreateAccount()
    	throws Exception
    {
    	assertNull( service.readAccount( "mike" ) );
    	
    	Account account = new Account();
    	account.setId("mike");
    	account.setName("Mike");
    	account.setEmail("mike@changeme.com");
    	account.setPassword("this_should_be_encrypted");
    	account.setActivated(true);
    	
    	service.createAccount(account);
    	
    	assertNotNull( service.readAccount( "mike" ));
    }
    
    @Test
    public void testUpdateAccount()
    	throws Exception
    {
    	Account account = service.readAccount( "juven" );
    	
    	account.setName("Juven Xu 1");
    	account.setEmail("juven1@changeme.com");
    	account.setPassword("this_still_should_be_encrypted");
    	account.setActivated(false);
    	
    	service.updateAccount( account );
    	
    	account = service.readAccount( "juven" );
    	
        assertEquals( "Juven Xu 1", account.getName() );
        assertEquals( "juven1@changeme.com", account.getEmail() );
        assertEquals( "this_still_should_be_encrypted", account.getPassword() );
        assertFalse( account.isActivated() );
    }
}
该测试用例使用与AccountPersistService一致的包名,它有两个表示方法:prepare()与testReadAccount()。其中prepare()方法使用@Before标注,表示在测试用例之前执行该方法。它首先检查数据文件是否存在,如果存在则将其删除以得到干净的测试环境,接着使用account-persist.xml配置文件初始化SpringFramework的IoC容器,再从容器中获取要测试的AccountPersistService对象。最后,prepare()方法 创建一个Account对象,设置对象的字段之后,使用AccountPersistService的createAccount()方法将其持久化。

使用@Test标注的testReadAccount()方法就是要测试的方法。该方法根据id使用AccountPersistService读取Accoutn对象,然互检查该对象不为空,并且每个字段的值必须与刚才插入的对象的值完全一致。

该测试用例遵循了测试接口而不测试实现这一原则也就是说,测试代码不能应用实现类,由于测试是从接口用户的角度编写的,这样就能保证接口的用户无须知晓接口的实现细节,既保证了代码的解耦,也促进了代码的设计。


参考:Maven实战(第8章)——许晓斌著

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值