In this chapter, we are going to briefly introduce two concepts of Java EE development: interceptors and events. We will also see how to integrate these concepts with WebLogic services. It's a common misunderstanding that these technologies are complex and difficult to use, but after working with examples of this chapter, it will become clear that they are powerful yet easy to use. Along the way, we will cover WebLogic Server's logging services, which shows us how to configure the framework, how to write messages to it, and how to read them using the administration console.
Interceptors are defined as part of the EJB 3.1 specification (JSR 318), and are used to intercept Java method invocations and lifecycle events that may occur in Enterprise Java Beans (EJB) or Named Beans from Context Dependency Injection (CDI).
The three main components of interceptors are as follows:
As an example, a logging interceptor will be developed and integrated into the Store application. Following the hands-on approach of this book, we will see how to apply the main concepts through the given examples without going into a lot of details.
A log interceptor is a common requirement in most Java EE projects as it's a simple yet very powerful solution because of its decoupled implementation and easy distribution among other projects if necessary. Here's a diagram that illustrates this solution:
Log
and LogInterceptor
are the core of the log interceptor functionality; the former can be thought of as the interface of the interceptor, it being the annotation that will decorate the elements of SearchManager
that must be logged, and the latter carries the actual implementation of our interceptor. The business rule is to simply call a method of class LogService
, which will be responsible for creating the log entry.
Here's how to implement the log interceptor mechanism:
com.packt.store.log
in the project Store.LogLevel
inside this package. This enumeration will be responsible to match the level assigned to the annotation and the logging framework:package com.packt.store.log; public enum LogLevel { // As defined at java.util.logging.Level SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST; public String toString() { return super.toString(); } }
Log
. This annotation will be used to mark every method that must be logged, and it accepts the log level as a parameter according to the LogLevel
enumeration created in the previous step:package com.packt.store.log;
@Inherited
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Log {
@Nonbinding
LogLevel value() default LogLevel.FINEST;
}
As this annotation will be attached to an interceptor, we have to add the @InterceptorBinding
decoration here. When creating the interceptor, we will add a reference that points back to the Log
annotation, creating the necessary relationship between them.
Also, we can attach an annotation virtually to any Java element. This is dictated by the @Target
decoration, where we can set any combination of the ElementType
values such as ANNOTATION_TYPE
, CONSTRUCTOR
, FIELD
, LOCAL_VARIABLE
, METHOD
, PACKAGE
, PARAMETER
, and TYPE
(mapping classes, interfaces, and enums), each representing a specific element. The annotation being created can be attached to methods and classes or interface definitions.
LogService
that is going to execute the actual logging:@Stateless public class LogService { // Receives the class name decorated with @Log public void log(final String clazz, final LogLevel level, final String message) { // Logger from package java.util.logging Logger log = Logger.getLogger(clazz); log.log(Level.parse(level.toString()), message); } }
LogInterceptor
, to trap calls from classes or methods decorated with @Log
and invoke the LogService
class we just created—the main method must be marked with @AroundInvoke
—and it is mandatory that it receives an InvocationContext
instance and returns an Object
element:@Log @Interceptor public class LogInterceptor implements Serializable { private static final long serialVersionUID = 1L; @Inject LogService logger; @AroundInvoke public Object logMethod(InvocationContext ic) throws Exception { final Method method = ic.getMethod(); // check if annotation is on class or method LogLevel logLevel = method.getAnnotation(Log.class)!= null ? method.getAnnotation(Log.class).value() : method.getDeclaringClass().getAnnotation(Log.class).value(); // invoke LogService logger.log(ic.getClass().getCanonicalName(),logLevel, method.toString()); return ic.proceed(); } }
As we defined earlier, the Log
annotation can be attached to methods and classes or interfaces by its @Target
decoration; we need to discover which one raised the interceptor to retrieve the correct LogLevel
value.
When trying to get the annotation from the class shown in the method.getDeclaringClass().getAnnotation(Log.class)
line, the engine will traverse through the class' hierarchy searching for the annotation, up to the Object
class if necessary. This happens because we marked the Log
annotation with @Inherited
. Remember that this behavior only applies to the class's inheritance, not interfaces.
Finally, as we marked the value
attribute of the Log
annotation as @Nonbinding
in step 3, all log levels will be handled by the same LogInterceptor
function. If you remove the @Nonbinding
line, the interceptor should be further qualified to handle a specific log level, for example @Log(LogLevel.INFO)
, so you would need to code several interceptors, one for each existing log level.
beans.xml (under /WEB-INF/)
file to tell the container that our class must be loaded as an interceptor—currently, the file is empty, so add all the following lines:<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> <interceptors> <class>com.packt.store.log.LogInterceptor</class> </interceptors> </beans>
@Log
in order to test what we've done. For example, apply it to the getTheaters()
method in SearchManager
from the project Store. Remember that it will be called every time you refresh the query page:@Log(LogLevel.INFO) public List<Theater> getTheaters() { ... }
http://localhost:7001/theater/theaters.jsf
, refresh it a couple of times, and check the server output. If you have started your server from Eclipse, it should be under the Console tab:Nov 12, 2012 4:53:13 PM com.packt.store.log.LogService log INFO: public java.util.List com.packt.store.search.SearchManager.getTheaters()
Let's take a quick overview of what we've accomplished so far; we created an interceptor and an annotation that will perform all common logging operations for any method or class marked with such an annotation. All log entries generated from the annotation will follow WebLogic's logging services configuration.
There are some equivalent concepts on these topics, but at the same time, they provide some critical functionalities, and these can make a completely different overall solution. In a sense, interceptors work like an event mechanism, but in reality, it's based on a paradigm called Aspect Oriented Programming (AOP). Although AOP is a huge and complex topic and has several books that cover it in great detail, the examples shown in this chapter make a quick introduction to an important AOP concept: method interception.
Consider AOP as a paradigm that makes it easier to apply crosscutting concerns (such as logging or auditing) as services to one or multiple objects. Of course, it's almost impossible to define the multiple contexts that AOP can help in just one phrase, but for the context of this book and for most real-world scenarios, this is good enough.