|
6.1. Building a ServiceThe core functions of your application are roughly complete, but it's going to be hard to trace what changes a user made, and when. In this example, you'll build an audit trail that records a log record whenever someone takes an action that changes the database. Rather than add the same code in many different places, you'll build a simple Spring service to do the job. You'll focus on the Hibernate implementation that you built earlier. Object-oriented languages handle some problems pretty well, but they don't handle crosscutting concerns, common in enterprise developments, very well. Figure 6-1 shows a method with some common enterprise services that crosscut many methods in DAO and façade layers. Figure 6-1. Some classic enterprise crosscutting concernsTo handle crosscutting concerns, you might decide to use a container. Most containers accept only components that match their specification, and provide services to components once they're in the container. Heavyweight container architectures, like EJB 1.x and 2.x, handle crosscutting concerns, but they force you to write code that depends on your given container. You can't take the code out of the container and expect it to run, and since the container takes too long to start, the result is difficult to test. Lightweight containers that use AOP technologies accept components too, but the component is a simple POJO. You can then use AOP to attach services to the POJO. We'll get into how this works later in the chapter, but for now, think of this feature as the ability to add declarative services to POJO. In Spring, you implement declarative services with configuration rather than code, so you can change services on the fly through changes in the context. 6.1.1. How do I do that?The interceptor strategy uses three objects: the target (our façade), a proxy object that Spring creates for you, and an interceptor, which you'll build. Essentially, the proxy imitates the targetwherever you would use an instance of the target object, Spring will use the proxy instead. The proxy will pass all calls to the object through a series of interceptors that you'll configure in the context. Depending on the type of interceptors, the call might pass through the interceptor before reaching the target, on the way back from the target, or both. You've got to do three things:
To build advice, you'll simply implement an interface. Think of advice as something that will happen at some event in a program's execution. Spring has four types of advice:
In general, you should use the simplest type of advice that will solve your problem. For this example, you'll use before advice, and you'll build on the Hibernate interface. The interceptor will implement the MethodBeforeAdvice interface. It will do the logging. Don't worry about how it gets invoked just yet. Example 6-1 gives the advice. Example 6-1. LoggingBefore.javapublic class LoggingBefore implements MethodBeforeAdvice { private SessionFactory factory; public SessionFactory getFactory( ) { return factory; } public void setFactory(SessionFactory factory) { this.factory = factory; } public void before(Method method, Object[] objects, Object o) throws Throwable { Session s = null; LogEvent le = new LogEvent(method.getName( ), new Date( )); try { s = factory.openSession( ); s.save(le); } catch (Exception ex) { //log the exception } finally { s.close( ); } } } For now, you can manually invoke it in a test case. In this case, you can simply count the number of events that exist in the database prior to running the advice, then compare that to the number of events in the database after running it. For the test in Example 6-2, it doesn't matter which method is being logged, just that the logger is activated. Example 6-2. ControllerTest.javapublic void testLocal( ) throws Exception { LoggingBefore lb = new LoggingBefore( ); SessionFactory factory = (SessionFactory)ctx.getBean("sessionFactory"); // getCurEventsCount( ) is a private utility method that // gets the current number of log events in the database int initialCount = getCurEventsCount( ); lb.setFactory(factory); try { lb.before((Method)this.getClass( ).getMethods( )[0], null, null); } catch (Throwable t) { fail(t.getMessage( )); } int afterCount = getCurEventsCount( ); assertEquals("Should add one event to log", initialCount, afterCount - 1); } 6.1.2. What just happened?You've just built a service. Unlike other container architectures, Spring does not define the complete package of services that components in the container can use. The services are ideal for our purposes:
Of course, right now, the service doesn't do anything. You need to attach it to something real. |
|