appFuse2.x第五篇--Services

This tutorial will show you how to create a Business Facade class (and a JUnit Test) to talk to the DAO you created in the Persistence Tutorial.

In the context of AppFuse, this is called a Manager class. Its main responsibility is to act as a bridge between the persistence (DAO) layer and the web layer. It's also useful for de-coupling your presentation layer from your database layer (i.e. for Swing apps and web services). Managers should also be where you put any business logic for your application.

You will learn two things:

  1. You don't need to write Managers if you just need generic CRUD functionality.
  2. How to write Managers when you need custom functionality.

Table of Contents

  1. Register a personManager bean definition
  2. Create a Manager Interface
  3. Create a Manager Test to test finder functionality
  4. Create a Manager Implementation
  5. Run the Manager Test
  6. Register your Manager Implementation
  7. Write the Manager Test using EasyMock
Source Code

The code for this tutorial is located in the "tutorial-service" module of the appfuse-demos project on Google Code. Use the following command to check it out from Subversion:

svn checkout http://appfuse-demos.googlecode.com/svn/trunk/tutorial-service

Register a personManager bean definition

AppFuse 2.x doesn't require you to write a Manager or DAO to persist a POJO. You can use one of the pre-existing manager interfaces/implementations if all you need is CRUD on an object:

  • GenericManager: A generics-based class that requires you to create a Spring bean definition.
  • UniversalManager: A class that requires you to cast to the specific object type.

The UniversalManagerImpl class is already registered as a "manager" bean, so you can easily use it without any additional configuration. However, many developers prefer the generics-based Manager because it provides type safety. To register a personManager bean, its best to wrap the personDao bean.

Transactions
All service.*Manager beans will automatically be configured by Spring to wrap PROPOGATION_REQUIRED transactions around their method executions. This is done by the following Spring AOP configuration in appfuse-service.jar.
<aop:config>     
    ...     
    <aop:advisor id="managerTx" advice-ref="txAdvice"          
        pointcut="execution(* *..service.*Manager.*(..))" order="2"/> 
</aop:config>

Open your src/main/webapp/WEB-INF/applicationContext.xml and replace the personDao bean with the following:

Hibernate:

<script class="javascript" src="/download/resources/confluence.ext.code:code/shCore.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushCSharp.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushPhp.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushJScript.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushVb.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushSql.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushXml.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushShell.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushDelphi.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushPython.js" type="text/javascript"></script><script class="javascript" src="/download/resources/confluence.ext.code:code/shBrushJava.js" type="text/javascript"></script>
<bean id="personManager" class="org.appfuse.service.impl.GenericManagerImpl">       
    <constructor-arg>           
        <bean class="org.appfuse.dao.hibernate.GenericDaoHibernate" autowire="byType">               
            <constructor-arg value="org.appfuse.tutorial.model.Person"/>           
        </bean>       
    </constructor-arg>   
</bean> 
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>
iBATIS and JPA Configuration
For iBATIS and JPA, you'll need to change the nested DAO that gets injected into GenericManagerImpl:
  • iBATIS: <bean class="org.appfuse.dao.ibatis.GenericDaoiBatis" ...
  • JPA: <bean class="org.appfuse.dao.jpa.GenericDaoJpa" ...

If you wrote the PersonDao interface and implementation in the previous tutorial, you'll want to use the following for your personManager bean definition. If you don't, your PersonDaoTest will fail because there's no longer an exposed personDao bean.

<bean id="personManager" class="org.appfuse.service.impl.GenericManagerImpl">       
    <constructor-arg ref="personDao"/>   
</bean> 
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

Once you've created a personManager bean definition, you can use this bean on an object by adding the following setter method:

public void setPersonManager(GenericManager<Person, Long> personManager) {  
    this.personManager = personManager;   
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

If you'd prefer to use the UniversalManager, you simply need to declare your set method as follows:

public void setManager(UniversalManager manager) {  
     this.manager = manager;   
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

No futher XML configuration is required when using this class.

The Spring Framework
To learn more about how Spring works, please see the Spring Reference Guide.

That's it! To persist an object with AppFuse 2.x, all you need to do is:

  1. Create a POJO with annotations
  2. Register it with your persistence framework
  3. Write some XML to register a type-safe class to persist it, or write no XML to use the Universal* classes.

Not only did you write data access code, you also used interfaces to abstract your implementation. This means that it should be possible to replace your persistence framework at any time.

We hope to eliminate many of these steps in 2.2. For the final 2.0 release, you should be able to perform all 3 steps with the AppFuse Maven Plugin.

If you need more than just CRUD functionality, you'll want to continue reading below. If not, you can continue to writing your web application.

Create a Manager Interface

First off, create a PersonManager interface (in the src/main/java/**/service directory - you may need to create this first) and specify the finder method for any implementation classes.

package org.appfuse.tutorial.service;  
 
import org.appfuse.service.GenericManager;  
import org.appfuse.tutorial.model.Person;  
 
import java.util.List;  
 
public interface PersonManager extends GenericManager<Person, Long> {  
    public List<Person> findByLastName(String lastName);  
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

Create a Manager Test to test finder functionality

Now you know what you want your manager to do, so it's time to write the tests. In this example, you will use mock objects to isolate your tests from external dependencies. This means you do not need a database set up to run these tests, and it will be easier to test different scenarios. In a large project, you could even test your Manager class before someone else finished writing the DAO implementation.

You can write your own mock objects, but here you are going to use jMock. Another widely use Mock Object library is EasyMock.

Create PersonManagerImplTest in src/test/java/**/service/impl:

package org.appfuse.tutorial.service.impl;  
 
import java.util.ArrayList;  
import java.util.List;  
 
import org.appfuse.tutorial.dao.PersonDao;  
import org.appfuse.tutorial.model.Person;  
import org.appfuse.service.impl.BaseManagerMockTestCase;  
import org.jmock.Mock;  
 
public class PersonManagerImplTest extends BaseManagerMockTestCase {  
    private PersonManagerImpl manager = null;  
    private Mock dao = null;  
    private Person person = null;  
 
    protected void setUp() throws Exception {  
        dao = new Mock(PersonDao.class);  
        manager = new PersonManagerImpl((PersonDao) dao.proxy());  
    }  
 
    protected void tearDown() throws Exception {  
        manager = null;  
    }  
 
    public void testGetPerson() {  
        log.debug("testing getPerson");  
 
        Long id = 7L;  
        person = new Person();  
 
        // set expected behavior on dao  
        dao.expects(once()).method("get")  
                .with(eq(id))  
                .will(returnValue(person));  
 
        Person result = manager.get(id);  
        assertSame(person, result);  
        dao.verify();  
    }  
 
    public void testGetPersons() {  
        log.debug("testing getPersons");  
 
        List people = new ArrayList();  
 
        // set expected behavior on dao  
        dao.expects(once()).method("getAll")  
                .will(returnValue(people));  
 
        List result = manager.getAll();  
        assertSame(people, result);  
        dao.verify();  
    }  
 
    public void testFindByLastName() {  
        log.debug("testing findByLastName");  
 
        List people = new ArrayList();  
        String lastName = "Smith";  
 
        // set expected behavior on dao  
        dao.expects(once()).method("findByLastName")  
                .with(eq(lastName))  
                .will(returnValue(people));  
 
        List result = manager.findByLastName(lastName);  
        assertSame(people, result);  
        dao.verify();  
    }  
 
    public void testSavePerson() {  
        log.debug("testing savePerson");  
 
        person = new Person();  
 
        // set expected behavior on dao  
        dao.expects(once()).method("save")  
                .with(same(person))  
                .will(returnValue(person));  
 
        manager.save(person);  
        dao.verify();  
    }  
 
    public void testRemovePerson() {  
        log.debug("testing removePerson");  
 
        Long id = 11L;  
        person = new Person();  
 
        // set expected behavior on dao  
        dao.expects(once()).method("remove")  
                .with(eq(id))  
                .isVoid();  
 
        manager.remove(id);  
        dao.verify();  
    }  
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

This will not compile, as you have not created the PersonManagerImpl class it tests.

Create a Manager Implementation

The next step is to create a PersonManagerImpl class that implements the methods in PersonManager.

Create PersonManagerImpl.java in src/main/java/**/service/impl.

package org.appfuse.tutorial.service.impl;  
 
import org.appfuse.tutorial.dao.PersonDao;  
import org.appfuse.tutorial.model.Person;  
import org.appfuse.tutorial.service.PersonManager;  
import org.appfuse.service.impl.GenericManagerImpl;  
 
import java.util.List;  
 
public class PersonManagerImpl extends GenericManagerImpl<Person, Long> implements PersonManager {  
    PersonDao personDao;  
 
    public PersonManagerImpl(PersonDao personDao) {  
        super(personDao);  
        this.personDao = personDao;  
    }  
 
    public List<Person> findByLastName(String lastName) {  
        return personDao.findByLastName(lastName);  
    }  
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

Now before you run your tests, review your test class to make sure that it will test all possible conditions.

I would put any complicated code in protected methods and go back and add extra test cases at this stage.

Run the Manager Test

Save all your edited files and try running mvn test -Dtest=PersonManagerImplTest.

Register your Manager Implementation

Open your src/main/webapp/WEB-INF/applicationContext.xml file and replace the personDao bean with the following:

<bean id="personManager" class="org.appfuse.tutorial.service.impl.PersonManagerImpl">  
    <constructor-arg ref="personDao"/>  
</bean> 
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

The Web application tutorials assume that you will be using the GenericManager. If you follow them after making this change, you will need to change all the references in their code from the GenericManager to your new PersonManager interface. For example:

private PersonManager personManager;

public void setPersonManager(PersonManager personManager) {
    this.personManager = personManager;
}

That's it. If you want to see how to use EasyMock instead of JMock, then carry on reading. If not, you can continue to writing your web application.

Using EasyMock instead of JUnit in Unit Tests

Sooner or later, you will want to add extra dependencies into your project.

AppFuse currently ships with JMock, but not EasyMock, so this section will show you how to add EasyMock as a project dependency.

Edit your pom.xml file in your project's top level directory. Add EasyMock as a dependency in the <dependencies> element:

<dependencies>  
    ...  
    <dependency>  
        <groupId>org.easymock</groupId>  
        <artifactId>easymock</artifactId>  
        <version>2.2</version>  
        <scope>test</scope>  
    </dependency>  
</dependencies> 
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

EasyMock is only required during testing, and you don't want it packaged in your application, so it's restricted it to a scope of test.

To remove jMock and add EasyMock to references regenerate project with mvn install eclipse:eclipse for Eclipse or mvn idea:idea for IDEA.

Edit the PersonManagerImplTest class you wrote above so it looks as follows:

package org.appfuse.tutorial.service.impl;  
 
import java.util.ArrayList;  
import java.util.List;  
import org.appfuse.tutorial.dao.PersonDao;  
import org.appfuse.tutorial.model.Person;  
 
import static org.easymock.EasyMock.*;  
import org.apache.commons.logging.LogFactory;  
import org.apache.commons.logging.Log;  
import junit.framework.TestCase;  
 
public class PersonManagerImplTest extends TestCase {  
    private final Log log = LogFactory.getLog(PersonManagerImplTest.class);  
    private PersonManagerImpl manager = null;  
    private PersonDao dao = null;  
    private Person person = null;  
 
    protected void setUp() throws Exception {  
        log.debug("setUpDao for PersonManagerImplTest");  
        dao = createStrictMock(PersonDao.class);  
        manager = new PersonManagerImpl((PersonDao) dao);  
    }  
 
    public void testGetPerson() {  
        log.debug("testing getPerson");  
 
        Long id = 7L;  
        person = new Person();  
 
        // set expected behavior on dao  
        expect(dao.get(id)).andReturn(person);  
        replay(dao);  
 
        Person result = manager.get(id);  
        assertSame(person, result);  
        verify(dao);  
    }  
 
    public void testGetPersons() {  
        log.debug("testing getPersons");  
 
        List people = new ArrayList();  
 
        // set expected behavior on dao  
        expect(dao.getAll()).andReturn(people);  
        replay(dao);  
 
        List result = manager.getAll();  
        assertSame(people, result);  
        verify(dao);  
    }  
 
    public void testGetByLastName() {  
        log.debug("testing getByLastName");  
 
        List people = new ArrayList();  
        String lastName = "Smith";  
 
        // set expected behavior on dao  
        expect(dao.findByLastName(lastName)).andReturn(people);  
        replay(dao);  
 
        List result = manager.findByLastName(lastName);  
        assertSame(people, result);  
        verify(dao);  
    }  
 
    public void testSavePerson() {  
        log.debug("testing savePerson");  
 
        person = new Person();  
 
        // set expected behavior on dao  
        expect(dao.save(person)).andReturn(person);  
        replay(dao);  
 
        manager.save(person);  
        verify(dao);  
    }  
 
    public void testRemovePerson() {  
        log.debug("testing removePerson");  
 
        Long id = 11L;  
        person = new Person();  
 
        // set expected behavior on dao  
        dao.remove(id);  
        replay(dao);  
 
        manager.remove(id);  
        verify(dao);  
    }  
<script class="javascript" type="text/javascript"> if(!window.newcodemacro_initialised) { window.newcodemacro_initialised = true; window.oldonloadmethod = window.onload; window.onload = function(){ dp.SyntaxHighlighter.HighlightAll('newcodemacro'); if(window.oldonloadmethod) { window.oldonloadmethod(); } } } </script>

Note that this class extends junit.framework.TestCase and not an EasyMock class. This makes EasyMock a good choice for annotation based test frameworks such as JUnit 4 and TestNG. Unfortunately (at the time of writing) Maven does not work properly with these frameworks.

Now check everything works by running mvn test -Dtest=PersonManagerImplTest again.

That's it, you can continue to writing your web application.

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值