Saturday, August 03, 2013

JSF/CDI Tip of the Day: @PostConstruct Lifecycle Interceptor

SR-71
I was trying to come up with a way to inject a logger into a JSF managed bean that can be done prior to @Postconstruct. I was unsuccessful with this technique, but it was not a total loss. The examples I found on interceptors for life-cycle interception were "empty". They generally mention you can do it, but they did not show any real useful examples.

I hope to change that with this example. The example I demonstrate could be easily done with constructor injection, but in the case of a no-args constructor this may be your only opportunity to inject objects during @PostConstruct. I am going to suggest that if you want to make this functionality more general, you should use an interface and call the interface methods.

The InvocationContext will return Class<? extends T> using context.getTarget(). We can use this concept to control our injection to the object. In my case, I use setter injection on the object. Pretty cool!

The code for this example can be downloaded from here: cdi-lifecycle-interceptor-example

There are a couple of classes needed to set up our @Interceptor. The first is an interface we will call Loggable and the second is an @InterceptorBinding.

Loggable.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bluelotussoftware.example.cdi;
 
import java.util.logging.Logger;
 
/**
 * This allows for setting a {@link Logger} in an implementing class.
 *
 * @author John Yeary
 * @version 1.0
 */
public interface Loggable {
 
    /**
     * Sets the logger for a class.
     *
     * @param logger the logger to be set.
     */
    void setLogger(Logger logger);
}
This is a simple interface which we will apply to the interceptor, and to the target class that we want to intercept.

Interceptable.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.bluelotussoftware.example.cdi;
 
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
 
/**
 * An interceptor binding.
 *
 * @author John Yeary
 * @version 1.0
 */
@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({TYPE})
public @interface Interceptable {
}
The next class we need is our @Interceptor. This code contains all of the magic we need for the target class which we are intercepting.

PostConstructInterceptor.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.bluelotussoftware.example.cdi;
 
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
 
/**
 *
 * @author John Yeary
 * @version 1.0
 */
@Interceptable
@Interceptor
public class PostConstructInterceptor {
 
    private static final Logger log = Logger.getLogger(PostConstructInterceptor.class.getName());
 
    /**
     * This is the method that handles the {@link @PostConstruct} event. The
     * method name can be called any legal Java method name. I chose
     * <code>intercept</code> since it is an action describing what we are
     * doing.
     *
     * @param context The current invocation context.
     * @throws Exception If any {@link Exception} occurs during processing.
     */
    @PostConstruct
    public void intercept(InvocationContext context) throws Exception {
 
        log.setLevel(Level.FINE);
        log.info("Before call to @PostConstuct.");
 
        Map<String, Object> map = context.getContextData();
        for (String key : map.keySet()) {
            log.log(Level.INFO, "Context data key --> {0} data --> {1}", new Object[]{key, map.get(key)});
        }
 
        log.log(Level.INFO, "Examining object: {0}", context.getTarget().getClass().getName());
 
        // Best practice is to check if the object is an instanceof an interface.
        if (context.getTarget() instanceof Loggable) {
            Loggable loggable = (Loggable) context.getTarget();
            loggable.setLogger(Logger.getLogger(context.getTarget().getClass().getName()));
        }
 
        /*
         * This checks for an instanceof a specific bean. This is not the preferred approach
         * unless you are really expecting to only check for a specific class.
         */
        if (context.getTarget() instanceof IndexBean) {
            IndexBean indexBean = (IndexBean) context.getTarget();
            indexBean.setFactory(new UUIDFactory() {
 
                @Override
                public UUID generateUUID() {
                    return UUID.randomUUID();
                }
            });
        }
 
        context.proceed();
        log.info("After call to  to @PostConstuct.");
    }
}
As you can see we are making changes to the class after it is constructed, and before the @PostConstruct method is executed, i.e., context.proceed().

Finally, we need to have our class that we are intercepting. In this case, it is a simple @Named and @RequestScoped CDI bean.

IndexBean.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.bluelotussoftware.example.cdi;
 
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
 
/**
 * A page backing bean for the
 * <code>index.xhtml</code> page.
 *
 * @author John Yeary <jyeary@bluelotussoftware.com>
 * @version 1.0
 */
@Named
@RequestScoped
@Interceptable
public class IndexBean implements Loggable {
 
    private UUIDFactory factory;
    private Logger log;
 
    /**
     * Default no-argument constructor.
     */
    public IndexBean() {
    }
 
    @PostConstruct
    private void initialize() {
        // This should cause an NPE since logger is not initialized, but we will do our magic here.
        log.info("I am an injected logger");
    }
 
    /**
     * {@inheritDoc}
     */
    @Override
    public void setLogger(final Logger logger) {
        this.log = logger;
    }
 
    /**
     * This sets the UUID factory for the bean.
     *
     * @param factory the factory to be set.
     */
    public void setFactory(final UUIDFactory factory) {
        log.log(Level.INFO, "Factory being set to {0}", factory);
        this.factory = factory;
    }
 
    /**
     * This returns a randomly generated {@link UUID#randomUUID()#toString()}
     *
     * @return a randomly generated UUID value, or {@code null} if the factory
     * is not initialized.
     */
    public String getUUID() {
        if (null != factory) {
            return factory.generateUUID().toString();
        } else {
            return null;
        }
    }
}
The magic is complete, but I would recommend downloading the Maven based project to see the whole example here: cdi-lifecycle-interceptor-example

Our final output is shown here.

UUID Please?

0 comments :

Popular Posts