Archive forSpring

Getting Started With JPA in Spring 2.0

The motivation behind this blog entry is to provide a simple step-by-step guide for getting started with JPA in a standalone environment with the Spring Framework. While the JPA specification originated as the persistence mechanism for EJB 3.0, it was fortunately recognized that any such mechanism should in fact be able to persist simple POJOs. Therefore, with a handful of JARs in your classpath and a few Spring-configured beans, you can begin experimenting with JPA code within your favorite IDE. I will be using Glassfish JPA - which is the reference implementation and is based upon Oracle’s TopLink ORM framework.

A. Initial Setup:

  1. Ensure that you are using Java 5 (a prerequisite for JPA as well as EJB 3.0).
  2. Download the glassfish JPA jar from: https://glassfish.dev.java.net/downloads/persistence/JavaPersistence.html
    (NOTE: I used the “V2_build_02″ jar, but any later version should also work.)
  3. To unbundle the jar from the “installer” jar, run:
    java -jar glassfish-persistence-installer-v2-b02.jar
    (this is required for acceptance of the license agreement)
  4. Add the “toplink-essentials.jar” to your classpath
  5. Add the JAR containing your database driver (I am using hsqldb.jar version 1.8.0.1 in the example, but only minor changes are necessary to adapt for another database).
  6. Add the following Spring JARs using the 2.0 M5 versions (available here: http://sourceforge.net/project/showfiles.php?group_id=73357).

    • spring.jar
    • spring-jpa.jar
    • spring-mock.jar
  7. Finally, add these jars to your classpath as well:

    • commons-logging.jar
    • log4j.jar
    • junit.jar

B. Code - Domain Model:

This example will be based on a purposefully simple domain model of just 3 classes. Notice the use of annotations. With JPA, one may choose to use either annotations or an XML file to specify the object-relational mapping metadata - or even a combination of both approaches. Here, I have chosen to use solely annotations - for which brief descriptions will be provided immediately following the domain model code listings.

First, the Restaurant class:


package blog.jpa.domain;

import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;

@Entity
public class Restaurant {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	private String name;

	@OneToOne(cascade = CascadeType.ALL)
	private Address address;

	@ManyToMany
	@JoinTable(inverseJoinColumns = @JoinColumn(name = "ENTREE_ID"))
	private Set<Entree> entrees;

	public long getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public Set<Entree> getEntrees() {
		return entrees;
	}

	public void setEntrees(Set<Entree> entrees) {
		this.entrees = entrees;
	}

}

Second, the Address class:


package blog.jpa.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Address {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	@Column(name = "STREET_NUMBER")
	private int streetNumber;

	@Column(name = "STREET_NAME")
	private String streetName;

	public long getId() {
		return id;
	}

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

	public int getStreetNumber() {
		return streetNumber;
	}

	public void setStreetNumber(int streetNumber) {
		this.streetNumber = streetNumber;
	}

	public String getStreetName() {
		return streetName;
	}

	public void setStreetName(String streetName) {
		this.streetName = streetName;
	}

}

Third, the Entree class:


package blog.jpa.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Entree {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private long id;

	private String name;

	private boolean vegetarian;

	public long getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public boolean isVegetarian() {
		return vegetarian;
	}

	public void setVegetarian(boolean vegetarian) {
		this.vegetarian = vegetarian;
	}

}

As you can see, not every persistent field has been annotated. JPA uses defaults (such as using a column name that matches the property name exactly) so that in many cases you do not need to specify the metadata explicitly. However, you may still choose to do so in order to provide more thoroughly self-documenting code. Notice that in the Entree class I am not using annotations for the String property “name” or the boolean property “vegetarian”. However, in the Address class, I am using the annotations, because I want to have a non-default name for the columns in the database (for example, I have chosen “STREET_NAME” whereas the default would have been “STREETNAME”).

Of course, one of the most important features of any ORM mechanism is the way in which one specifies the mapping from relationships between Objects to their database counterparts. In the Restaurant class, there is a @OneToOne annotation to describe the relationship to an Address and a @ManyToMany annotation to describe the relationship with members of the Entree class. Since instances of these other classes are also being managed by the EntityManager, it is possible to specify “cascade” rules. For example, when a Restaurant is deleted, the associated Address will also be deleted. In a moment, you will see a testcase for this scenario.

Finally, take a look at the @Id annotations and the specified “strategy” for the ID’s @GeneratedValue. This metadata is used for describing the primary key generation strategy which in turn controls the identity within the database.

To learn much more about these and additional JPA annotations, check out the JPA specification - which is actually a subset of JSR-220.

C. Code - Data Access Layer:

For accessing instances of the domain model, it is best to create a generic interface that hides all details about the underlying persistence mechanism. That way, if switching later to something other than JPA, there will be no impact on the architecture. This also makes it easier to test the service layer, since it enables the creation of stub implementations of this data access interface - or even dynamic mock implementations.

Here is the interface. Notice that there are no dependencies on any JPA or Spring classes. In fact, the only dependencies here that are not core Java classes are the classes of my domain model (in this simple case, there is only one - Restaurant):


package blog.jpa.dao;

import java.util.List;
import blog.jpa.domain.Restaurant;

public interface RestaurantDao {

	public Restaurant findById(long id);

	public List<Restaurant> findByName(String name);

	public List<Restaurant> findByStreetName(String streetName);

	public List<Restaurant> findByEntreeNameLike(String entreeName);

	public List<Restaurant> findRestaurantsWithVegetarianEntrees();

	public void save(Restaurant restaurant);

	public Restaurant update(Restaurant restaurant);

	public void delete(Restaurant restaurant);

}

For the implementation of this interface, I am going to extend Spring’s JpaDaoSupport class. This provides a convenience method for retrieving the JpaTemplate. If you have used Spring with JDBC or other ORM technologies, then you will probably be quite familiar with this approach.

It should be noted that use of the JpaDaoSupport is optional. It is possible to construct a JpaTemplate directly by simply providing the EntityManagerFactory to its constructor. In fact, the JpaTemplate itself is optional. If you do not wish to have the automatic translation of JPA exceptions into Spring’s runtime exception hierarchy, then you can avoid JpaTemplate altogether. In that case, you may still be interested in Spring’s EntityManagerFactoryUtils class which provides a convenient static method for obtaining the shared (and thus transactional) EntityManager.

Here is the implementation:


package blog.jpa.dao;

import java.util.List;
import org.springframework.orm.jpa.support.JpaDaoSupport;
import blog.jpa.domain.Restaurant;

public class JpaRestaurantDao extends JpaDaoSupport implements RestaurantDao {

	public Restaurant findById(long id) {
		return getJpaTemplate().find(Restaurant.class, id);
	}

	public List<Restaurant> findByName(String name) {
		return getJpaTemplate().find("select r from Restaurant r where r.name = ?1", name);
	}

	public List<Restaurant> findByStreetName(String streetName) {
		return getJpaTemplate().find("select r from Restaurant r where r.address.streetName = ?1", streetName);
	}

	public List<Restaurant> findByEntreeNameLike(String entreeName) {
		return getJpaTemplate().find("select r from Restaurant r where r.entrees.name like ?1", entreeName);
	}

	public List<Restaurant> findRestaurantsWithVegetarianEntrees() {
		return getJpaTemplate().find("select r from Restaurant r where r.entrees.vegetarian = 'true'");
	}

	public void save(Restaurant restaurant) {
		getJpaTemplate().persist(restaurant);
	}

	public Restaurant update(Restaurant restaurant) {
		return getJpaTemplate().merge(restaurant);
	}

	public void delete(Restaurant restaurant) {
		getJpaTemplate().remove(restaurant);
	}

}

D. The Service Layer:

As the purpose here is to focus on a JPA implementation for the data-access layer, the service layer is omitted. Obviously, in a realistic scenario, the service layer would play a critical role in the system architecture. It would be the point where transactions are demarcated - and typically, they would be demarcated declaratively in the Spring configuration. In the next step, when you have a look at the configuration, you will notice that I have provided a “transactionManager” bean. It is used by the base test class to automatically wrap each test method in a transaction, and it is the same “transactionManager” that would wrap service layer methods with transactions. The main point to take away is that there is NO transaction-related code in the data-access tier. Using the Spring JpaTemplate ensures that the same EntityManager is shared across all DAOs. Therefore transaction propagation occurs automatically - as dictated by the service layer. In other words, it will actually behave exactly the same as other persistence mechanisms configured within the Spring framework. There is nothing JPA-specific - hence the rationale for leaving it out of this entry which is focused on JPA.

E. Configuration:

Since I opted for the annotation-based mappings, you have actually already seen most of the JPA-specific configuration when the domain classes were presented. As mentioned above, it also would have been possible to configure those mappings via XML (in an ‘orm.xml’ file). The only other required configuration is in ‘META-INF/persistence.xml’. In this case, that is very simple since the database-related properties will be available to the EntityManagerFactory via a dependency-injected “dataSource” provided in the Spring configuration (due up next). The only other information in this ‘persistence.xml’ is whether to use local or global (JTA) transactions. Here are the contents of the ‘persistence.xml’ file:


<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">

    <persistence-unit name="SpringJpaGettingStarted" transaction-type="RESOURCE_LOCAL"/>

</persistence>

There are only 4 beans in the Spring configuration (well ok, a couple of inner-beans too). First, there is the “restaurantDao” (I purposefully left “jpa” out of the bean name since any service layer beans depending on the DAO should only be concerned with the generic interface). The only required property for the JPA implementation of this DAO is the “entityManagerFactory” which it uses to create the JpaTemplate. The “entityManagerFactory” depends on the “dataSource”, and there is nothing JPA-specific about that. In this configuration, you will see a DriverManagerDataSource, but in production code, this would be replaced by a connection pool - and typically one obtained via a JndiObjectFactoryBean (or Spring 2.0’s new convenience <jndi:lookup> tag). The final bean is the “transactionManager” that is required by the test class. This is the same “transactionManager” that would be used for transactions demarcated in the service-layer. The implementation class is Spring’s JpaTransactionManager. To anyone familiar with configuring Spring for JDBC, Hibernate, JDO, TopLink, or iBATIS, most of these beans will look very familiar. The only exception is the EntityManagerFactory. I will discuss it briefly, but first have a look at the complete ‘applicationContext.xml’ file:


<?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.xsd">

   <bean id="restaurantDao" class="blog.jpa.dao.JpaRestaurantDao">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
   </bean>

   <bean id="entityManagerFactory" class="org.springframework.orm.jpa.ContainerEntityManagerFactoryBean">
      <property name="dataSource" ref="dataSource"/>
      <property name="jpaVendorAdapter">
         <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
            <property name="showSql" value="true"/>
            <property name="generateDdl" value="true"/>
            <property name="databasePlatform" value="oracle.toplink.essentials.platform.database.HSQLPlatform"/>
         </bean>
      </property>
      <property name="loadTimeWeaver">
         <bean class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver"/>
      </property>
   </bean>

   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
      <property name="url" value="jdbc:hsqldb:hsql://localhost/"/>
      <property name="username" value="sa"/>
      <property name="password" value=""/>
   </bean>

   <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="entityManagerFactory" ref="entityManagerFactory"/>
      <property name="dataSource" ref="dataSource"/>
   </bean>

</beans>

First you see that the “entityManagerFactory” needs to be aware of a “dataSource”. Next there is the “jpaVendorAdapter” as there are a variety of JPA implementations. In this case, I’ve configured the TopLinkJpaVendorAdapter as an inner bean, and it has a few properties of its own. There is a boolean property to specify whether SQL should be shown and another boolean property for the generation of DDL. Both of these have been set to “true” and thus the database schema will be automatically generated each time the tests are executed. This is rather convenient in the early development phase as it provides immediate feedback for experimentations in the mappings, column names, etc. The “databasePlatformClass” provides the necessary information of what particular database is being used. Finally, the “entityManagerFactory” has a property for a “loadTimeWeaver” that plays a role in the transformation of class files by JPA persistence providers in order to accomodate certain features such as lazy-loading.

F. Integration Testing:

Perhaps the best way to learn a new API is to write a bunch of testcases. The JpaRestaurantDaoTests class provides some basic tests. In order to learn more about JPA, modify the code and/or configuration and observe the impact on these tests. For example, try modifying the cascade settings - or the cardinality of the associations. Notice that JpaRestaurantDaoTests extends Spring’s AbstractJpaTests. You may already be familiar with Spring’s AbstractTransactionalDataSourceSpringContextTests. This class will behave the same way in that any database changes caused by the test methods will rollback by default. AbstractJpaTests actually does much more than that, but it is beyond the scope of this entry to go into those details. If interested, have a look at the source code for AbstractJpaTests.

Here is the JpaRestaurantDaoTests code:


package blog.jpa.dao;

import java.util.List;
import org.springframework.test.jpa.AbstractJpaTests;
import blog.jpa.dao.RestaurantDao;
import blog.jpa.domain.Restaurant;

public class JpaRestaurantDaoTests extends AbstractJpaTests {

	private RestaurantDao restaurantDao;

	public void setRestaurantDao(RestaurantDao restaurantDao) {
		this.restaurantDao = restaurantDao;
	}

	protected String[] getConfigLocations() {
		return new String[] {"classpath:/blog/jpa/dao/applicationContext.xml"};
	}

	protected void onSetUpInTransaction() throws Exception {
		jdbcTemplate.execute("insert into address (id, street_number, street_name) values (1, 10, 'Main Street')");
		jdbcTemplate.execute("insert into address (id, street_number, street_name) values (2, 20, 'Main Street')");
		jdbcTemplate.execute("insert into address (id, street_number, street_name) values (3, 123, 'Dover Street')");

		jdbcTemplate.execute("insert into restaurant (id, name, address_id) values (1, 'Burger Barn', 1)");
		jdbcTemplate.execute("insert into restaurant (id, name, address_id) values (2, 'Veggie Village', 2)");
		jdbcTemplate.execute("insert into restaurant (id, name, address_id) values (3, 'Dover Diner', 3)");

		jdbcTemplate.execute("insert into entree (id, name, vegetarian) values (1, 'Hamburger', 0)");
		jdbcTemplate.execute("insert into entree (id, name, vegetarian) values (2, 'Cheeseburger', 0)");
		jdbcTemplate.execute("insert into entree (id, name, vegetarian) values (3, 'Tofu Stir Fry', 1)");
		jdbcTemplate.execute("insert into entree (id, name, vegetarian) values (4, 'Vegetable Soup', 1)");

		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (1, 1)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (1, 2)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (2, 3)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (2, 4)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (3, 1)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (3, 2)");
		jdbcTemplate.execute("insert into restaurant_entree (restaurant_id, entree_id) values (3, 4)");
	}

	public void testFindByIdWhereRestaurantExists() {
		Restaurant restaurant = restaurantDao.findById(1);
		assertNotNull(restaurant);
		assertEquals("Burger Barn", restaurant.getName());
	}

	public void testFindByIdWhereRestaurantDoesNotExist() {
		Restaurant restaurant = restaurantDao.findById(99);
		assertNull(restaurant);
	}

	public void testFindByNameWhereRestaurantExists() {
		List<Restaurant> restaurants = restaurantDao.findByName("Veggie Village");
		assertEquals(1, restaurants.size());
		Restaurant restaurant = restaurants.get(0);
		assertEquals("Veggie Village", restaurant.getName());
		assertEquals("Main Street", restaurant.getAddress().getStreetName());
		assertEquals(2, restaurant.getEntrees().size());
	}

	public void testFindByNameWhereRestaurantDoesNotExist() {
		List<Restaurant> restaurants = restaurantDao.findByName("No Such Restaurant");
		assertEquals(0, restaurants.size());
	}

	public void testFindByStreetName() {
		List<Restaurant> restaurants = restaurantDao.findByStreetName("Main Street");
		assertEquals(2, restaurants.size());
		Restaurant r1 = restaurantDao.findByName("Burger Barn").get(0);
		Restaurant r2 = restaurantDao.findByName("Veggie Village").get(0);
		assertTrue(restaurants.contains(r1));
		assertTrue(restaurants.contains(r2));
	}

	public void testFindByEntreeNameLike() {
		List<Restaurant> restaurants = restaurantDao.findByEntreeNameLike("%burger");
		assertEquals(2, restaurants.size());
	}

	public void testFindRestaurantsWithVegetarianOptions() {
		List<Restaurant> restaurants = restaurantDao.findRestaurantsWithVegetarianEntrees();
		assertEquals(2, restaurants.size());
	}

	public void testModifyRestaurant() {
		String oldName = "Burger Barn";
		String newName = "Hamburger Hut";
		Restaurant restaurant = restaurantDao.findByName(oldName).get(0);
		restaurant.setName(newName);
		restaurantDao.update(restaurant);
		List<Restaurant> results = restaurantDao.findByName(oldName);
		assertEquals(0, results.size());
		results = restaurantDao.findByName(newName);
		assertEquals(1, results.size());
	}

	public void testDeleteRestaurantAlsoDeletesAddress() {
		String restaurantName = "Dover Diner";
		int preRestaurantCount = jdbcTemplate.queryForInt("select count(*) from restaurant");
		int preAddressCount = jdbcTemplate.queryForInt("select count(*) from address where street_name = 'Dover Street'");
		Restaurant restaurant = restaurantDao.findByName(restaurantName).get(0);
		restaurantDao.delete(restaurant);
		List<Restaurant> results = restaurantDao.findByName(restaurantName);
		assertEquals(0, results.size());
		int postRestaurantCount = jdbcTemplate.queryForInt("select count(*) from restaurant");
		assertEquals(preRestaurantCount - 1, postRestaurantCount);
		int postAddressCount = jdbcTemplate.queryForInt("select count(*) from address where street_name = 'Dover Street'");
		assertEquals(preAddressCount - 1, postAddressCount);
	}

	public void testDeleteRestaurantDoesNotDeleteEntrees() {
		String restaurantName = "Dover Diner";
		int preRestaurantCount = jdbcTemplate.queryForInt("select count(*) from restaurant");
		int preEntreeCount = jdbcTemplate.queryForInt("select count(*) from entree");
		Restaurant restaurant = restaurantDao.findByName(restaurantName).get(0);
		restaurantDao.delete(restaurant);
		List<Restaurant> results = restaurantDao.findByName(restaurantName);
		assertEquals(0, results.size());
		int postRestaurantCount = jdbcTemplate.queryForInt("select count(*) from restaurant");
		assertEquals(preRestaurantCount - 1, postRestaurantCount);
		int postEntreeCount = jdbcTemplate.queryForInt("select count(*) from entree");
		assertEquals(preEntreeCount, postEntreeCount);
	}
}

G. Further Reading:

JPA is a vast topic, and this blog has only scratched the surface - the main objective being to demonstrate the basic configuration of a JPA-based persistence implementation with Spring. Obviously this domain model is trivial in terms of Object-Relational mapping. However, once you have this working configuration, you will be able to extend the example here while exploring the ORM capabilities offered by JPA. I would highly encourage a closer look at the Spring JPA support via the JavaDoc and the Spring reference documentation. The 2.0 RC1 release contains an added sub-section on JPA within the ORM section of the reference document.

Here are a few helpful links:

Comments (33)

POJO Aspects in Spring 2.0: A Simple Example

While the material in this post is quite simple, it will actually offer a glimpse of some rather significant new features in Spring 2.0. I hope that with a little imagination, you will be able to apply what you see here to far less trivial use cases of your own.

I am going to show 2 examples actually. The first will use a rather simple logger:


package example;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class SimpleLogger {

   private static Log log = LogFactory.getLog(SimpleLogger.class);

   public void logOneString(String s) {
      log.info("string=" + s);
   }

   public void logTwoStrings(String s1, String s2) {
      log.info("string1=" + s1 + ",string2=" + s2);
   }
}

I will be using AOP to apply logging to a string concatenation service. Here is the interface:


package example;

public interface ConcatService {
   public String concat(String s1, String s2);
}

…and an implementing Class:


package example;

public class ConcatServiceImpl implements ConcatService {

   public String concat(String s1, String s2) {
      return s1 + s2;
   }
}

Alright - nothing is very exciting about this so far, but the most important thing to notice is that I am only dealing with POJOs up to this point.

Now, have a look at these bean definitions. Notice the usage of the new Spring 2.0 XML Schema and the “aop” namespace in particular:


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

	<aop:config>
		<aop:aspect id="loggingAspect" ref="simpleLogger">
			<aop:before
			      method="logTwoStrings"
			      pointcut="execution(* example..*Service.*(..)) and args(s1,s2)"/>
			<aop:after-returning
			      method="logOneString"
			      returning="s"
			      pointcut="execution(* example..*Service.*(..))"/>
		</aop:aspect>
	</aop:config>

	<bean id="simpleLogger" class="example.SimpleLogger"/>

	<bean id="concatService" class="example.ConcatServiceImpl"/>

</beans>

The “loggingAspect” is defined with a reference to “simpleLogger” which you saw in the very first code snippet above. Again, the interesting thing is that it is a simple POJO - it doesn’t implement any interfaces or follow any contract in order to be used as an aspect. In fact, you may very well have code like this lying around already. ;)

The “loggingAspect” contains 2 kinds of advice. One is the “before” kind of advice, and the other is the “afterReturning” kind. Next, you see that the advice actually maps to methods on the SimpleLogger POJO - logTwoStrings() for the before advice and logOneString() for the afterReturning advice. This option of declarative mapping to a POJO method is a useful alternative to implementing advice interfaces.

Finally, a quick word on binding and pointcuts. In the “before” advice, args(s1,s2) specifies that this pointcut will apply when there are 2 arguments which can be bound to the 2 String parameters of the logTwoStrings() method - and that is exactly what happens here as you will see in just a moment. In the “afterReturning” case, the return value is going to bind to the single String parameter of the logOneString() method.

Now, for the pointcuts… the values in the “pointcut” attributes above are actually standard AspectJ pointcut expressions. In this case, they define what methods will be advised. The “*” is a wildcard, and the first “..” signifies any descendant package while the second “..” signifies any number and type of parameters. Essentially this pointcut will apply to any method of a Class that ends with “Service” regardless of its parameter type or count with any return value as long as it is somehow descending from the “example” package. Okay, so maybe that doesn’t seem quite so simple - but if it does at least sound interesting then you can read much more about the AspectJ expression language at the AspectJ site.

NOTE: While AspectJ expressions are being used here, the advice is still applied via Spring’s Proxy-based AOP as opposed to AspectJ weaving. This means that an interceptor is able to add behavior at method execution joinpoints only. It is highly likely that method execution interception will satisfy a majority of your AOP use-cases. However, for applying advice at other joinpoints (such as field access), you may use the full power of AspectJ (which is beyond the scope of this post).

So, without further hesitation… here is a simple main() method to try it out:


public static void main(String[] args) {
   ApplicationContext context = new ClassPathXmlApplicationContext("example/simpleLoggerContext.xml");
   ConcatService concatService = (ConcatService)context.getBean("concatService");
   concatService.concat("some", "thing");
}

And the result!:


string1=some,string2=thing
string=something

Now, for the second example…

Of course, you may want to log more information, such as method arguments, the calling method itself, and so on. To show how to accomplish this, I will revise this SimpleLogger just a bit. The secret is in the JoinPoint class (and the StaticPart Class) which will now be provided to the methods of my new class, MethodLogger:


package example;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.StaticPart;

public class MethodLogger {

   private static Log log = LogFactory.getLog(MethodLogger.class);

   public void logMethodEntry(JoinPoint joinPoint) {
      Object[] args = joinPoint.getArgs();
      String name = joinPoint.getSignature().toLongString();
      StringBuffer sb = new StringBuffer(name + " called with: [");
      for(int i = 0; i < args.length; i++) {
         Object o = args[i];
         sb.append(o);
         sb.append((i == args.length - 1) ? "]" : ", ");
      }
      log.info(sb);
   }

   public void logMethodExit(StaticPart staticPart, Object result) {
      String name = staticPart.getSignature().toLongString();
      log.info(name + " returning: [" + result + "]");
   }
}

As you can see, the JoinPoint provides access to the runtime info that I need. In the logMethodExit() method, only the type is required, so the StaticPart is sufficient (it is actually part of the JoinPoint in that JoinPoint provides a getStaticPart() method). As a general rule, whenever you can do what you need to without accessing runtime info, then you should.

Here are the bean definitions for using the MethodLogger:


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

	<aop:config>
		<aop:pointcut id="servicePointcut" expression="execution(* example..*Service+.*(..))"/>
		<aop:aspect id="loggingAspect" ref="methodLogger">
			<aop:before
			      method="logMethodEntry"
			      pointcut-ref="servicePointcut"/>
			<aop:after-returning
			      method="logMethodExit"
			      returning="result"
			      pointcut-ref="servicePointcut"/>
		</aop:aspect>
	</aop:config>

	<bean id="methodLogger" class="example.MethodLogger"/>

	<bean id="concatService" class="example.ConcatServiceImpl"/>

</beans>

Again, you see the aspects and advice. This time the “pointcut” is defined separately and reused for both advice types. Perhaps the most interesting thing here is that there is no explicit binding for method parameters and no need to configure anything to recognize the JoinPoint or StaticPart parameters. In fact, you can always specify one of these as the first parameter of a method in order to have access to more information about the context of the method’s execution.

To run this example, I will use the same main() but this time passing the path of the new bean definition file into the ClassPathXmlApplicationContext constructor. Here is the result:


public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) called with: [some, thing]
public abstract java.lang.String example.ConcatService.concat(java.lang.String,java.lang.String) returning: [something]

That’s about it for this simple example. The main point to take away is that POJO services can be decorated with additional behavior by POJO aspects. In fact, in some cases the only thing making them aspects is the configuration. In other cases, when you need more runtime info, then the JoinPoint and StaticPart can be quite useful.

If you are interested in more complete coverage of this topic, visit
this blog by Adrian Colyer.

NOTE: In that post, you will see examples with <aop:advice> elements. In Spring 2.0 M3, those elements have been replaced with the more specific ones as used in this entry - such as: <aop:before>.

Comments (27)