EJB 3 In Action - Woking with EJB components - EJB runtime, DI, AOP - Notes

302 阅读16分钟

EJB Context

The javax.ejb.EJBContext interface is your backstage entrance into the mystic world of the container.

Basics of EJB context

image.png

You can use javax.ejb.EJBContext to access runtime services
MethodsDescription
getCallerPrincipal isCallerInRoleThese methods are useful when using bean-managed security.
getEJBHome getEJBLocalHomeThese methods are used to obtain the bean’s “remote home” and “local home” interfaces respectively. Both are optional for EJB 3 containers and are hardly used beyond legacy EJB 2.1 beans. We won’t discuss these methods beyond this basic introduction. They’re mainly provided for backward compatibility.
getRollbackOnly setRollbackOnlyThese methods are used for EJB transaction management in the case of container-managed transactions.
getUserTransactionThis method is used for EJB transaction management in the case of bean-managed transactions.
getTimerServiceThis method is used to get access to the EJB timer service.
lookupThis method is used to get references to objects stored in the JNDI registry. With dependency injection, direct JNDI lookup has been rendered largely unnecessary. But there are some edge cases that dependency injection can’t handle. This method proves handy in such circumstances. .
getContextDataThis method allows the EJB to get access to context data from an interceptor. We’ll discuss interceptors in greater detail later in this chapter.

EJB Context interfaces

image.png

SessionContext

An implementation that adds methods specific to the session bean environment.

Additional methods added by javax.ejb.SessionContext
MethodsDescription
getBusinessObjectGet an object as either the current bean’s no-interface view or as one of its business interfaces (local or remote).
getEJBLocalObject getEJBObjectGet the local or remote object for the current bean instance. Only valid for use with EJB 2 beans. An exception is generated if used with EJB 3.
getInvokedBusinessInterfaceThe interface or no-interface view used to invoke the business method for the current bean.
getMessageContextIf the bean is accessed through a web service, this method returns the MessageContext associated with that request.
wasCancelCalledGet if the user wants to cancel a long-running asynchronous method call.

MessageDrivenContext

An implementation specifically for the MDB environment. Instead, it overrides the following methods, throwing exceptions if they’re called: isCallerInRole, getEJBHome, or getEJBLocalHome.

Accessing the container environment through the EJB context

@Stateless 
public class DefaultBidService implements BidService { 
    @Resource SessionContext context; 
    ... 
}

@MessageDriven 
public class OrderBillingProcessor { 
    @Resource 
    MessageDrivenContext context; 
    ... 
}

Using EJB DI and JNDI

With Java EE 5, the @EJB annotation was added to the specification to make it easier for container-managed components to get reference to EJBs.

  • Although JNDI is still used in the background, those details are hidden from the developer by the @EJB annotation.
  • Prior to EE5, JNDI was used exclusively.
  • The key difference is it was the developer’s responsibility to wire up beans manually using a JNDI implementation of the service locator pattern instead of having the container do it through dependency injection.

JNDI primer for EJB

  • JNDI: JDBC of naming and directory services.
  • LDAP: Lightweight Directory Access Protocol

JNDI provides a uniform abstraction over a number of different naming services, such as LDAP , Domain Naming System (DNS), Network Informa- tion Service (NIS), Novell Directory Services (NDS), remote method invocation (RMI), Common Object Request Broker Architecture (CORBA), and so on.

image.png

image.png

  • JNDI plays a vital role in Java EE, although it’s largely hidden behind the scenes.
  • JNDI is used as the central repository for resources managed by the container.
  • Every bean managed by the container is automatically registered with JNDI.
  • A typical container JNDI registry will also store JDBC data sources, JMS queues, JMS connection factories, JPA entity managers, JPA entity manager factories, JavaMail sessions, and so on.

Initializing a JNDI context

Outside of a Java EE environment, you need to configure your application so it knows which JNDI libraries it needs to use.

  • One way to do this is to create a Properties object and then pass this to InitialContext:
Properties properties = new Properties(); properties.put(Context.INITIAL_CONTEXT_FACTORY ,"oracle.j2ee.rmi.RMIInitialContextFactory");
properties.put(Context.PROVIDER_URL, "ormi://192.168.0.6:23791/appendixa"); properties.put(Context.SECURITY_PRINCIPAL, "oc4jadmin"); properties.put(Context.SECURITY_CREDENTIALS, "welcome1"); Context context = new InitialContext(properties);
  • Another way to do the configuration is to create a jndi.properties file and put this file anywhere in your application’s CLASSPATH.

With this configu- ration, you simply need to create a new InitialContext:

Context context = new InitialContext();
Common JNDI properties for connecting to a remote JNDI Java EE environment
Property nameDescriptionExample value
java.naming.factory .initialThe name of the factory class that will be used to create the contextoracle.j2ee.rmi.RMIInitialContextFactory
java.naming.provider.urlThe URL for the JNDI service providerormi://localhost:23791/chapter1
java.naming.security .principalThe username or identity for authenticating the caller in the JNDI service provideroc4jadmin
java.naming.security .credentialsThe password for the username/principal being used for authentication.wlcome1

Lookup JNDI resources

  • Object lookup(String name): Returns the named resource, which must be typecast to the type you need. A new Context instance is returned if the resource name is empty.

Lookup service directly

Context context = new InitialContext(); BidService service = (BidService) context.lookup("/ejb/bid/BidService");

Or you can chain lookups together:

Context newContext = new InitialContext(); Context bidContext = (Context) newContext.lookup("/ejb/bid/"); BidService service = (BidService) bidContext.lookup("BidService");

JNDI lookups were one of the primary reasons for EJB 2.x complexity.

  • First of all, you had to do lookups to access any resource managed by the container, even if you were only accessing data sources and EJBs from other EJBs located in the same JVM.
  • To make matters worse, before Java EE 6, JNDI resources didn’t have standard lookup names with every Enterprise server binding resources in JNDI with different names.

The good news is that except for certain corner cases, you won’t have to deal with JNDI directly in EJB 3. EJB 3 hides the mechanical details of JNDI lookups behind metadata-based DI. DI does such a great job in abstraction that you won’t even know that JNDI lookups are happening behind the scenes, even for remote lookups.

How EJB names are assigned

To make EJB JNDI lookups portable across all Java EE servers, the following portable JNDI name has been standardized:

java:<namespace>/[app-name]/<module-name>/<bean-name>[!fully-qualified- interface-name]

Where <namespace>, <module-name>, and <bean-name> are required and always present, [app-name] and [!fully-qualified-interface-name] may be optional. Let’s take a look at each part of this portable JNDI name and see how they get their values.

<Namesapce>

Java EE naming environment namespaces
NamespaceDescription
java:compLookups in this namespace are scoped per component. For example, an EJB is a component, so each EJB in a JAR file gets its own unique java:comp namespace. This means both AccountEJB and CartEJB can look up java:comp/LogLevel and get different values. For backward compatibility, this isn’t true for the web module. All components deployed as part of a WAR share the same java:comp namespace. It’s unlikely you’ll use this namespace very often. It’s mostly there for backward compatibility. Prior to Java EE 6, java:comp was really the only standard namespace.
java:moduleLookups in this namespace are scoped per module. All components in the module share the java:module namespace. An EJB-JAR file is a module, as is a WAR file. For backward compatibility, the java:comp and java:module namespaces are treated identically in web modules and refer to the same namespace. You should favor the use of java:module over java:comp when possible.
java:appLookups in this namespace are scoped per application. All components in all modules in the application share the java:app namespace. An EAR is an example of an application. All WARs and EJBs deployed from the EAR would share this namespace.
java:globalLookups in this namespace are global and shared by all components in all modules in all applications.

[app-name]

The [app-name] value is optional and only present if the EJBs are deployed to the server inside of an EAR. If an EAR isn’t used, then the [app-name] value will be absent from the EJBs’ portable JNDI name.

The default value for [app-name] is the name of the EAR file without the .ear extension. The application.xml file is able to override this default value, however.

[module-name]

The <module-name> value is required and will always be present. The value for <module-name> depends on how the modules containing the EJBs are deployed.

  • For EJBs deployed as standalone EJB-JAR files (JAR files deployed directly), the default value for <module-name> is the name of the EJB-JAR file without the .jar extension. This default name can be overridden using the module-name element of the META-INF/ejb-jar.xml configuration file.
  • For EJBs deployed as part of a standalone web module (WAR file), the default value for <module-name> is the name of the WAR file without the .war extension. This default name can be overridden using the module-name element of the WEB-INF/web.xml configuration file.

<bean-name>

The <bean-name> value is required and will always be present.

  • For EJBs defined using the @Stateless, @Stateful, or @Singleton annotations, the default value for <bean-name> is the unqualified name of the bean class. This default value can be overridden using the annotation name() attribute.
  • For EJBs defined using ejb-jar.xml the bean-name element sets the value for the bean name.

[!full-qualified-interface-name]

The [!fully-qualified-interface-name] is required, and a binding with a value for this part of the portable EJB name will always exist in JNDI.

The values for [!fully-qualified- interface-name] are the fully qualified names of each local, remote, EJB 2 local home, or EJB 2 remote home interface or the fully qualified name of the bean class if the bean class implements no interfaces.

Example

package com.bazaar; 

@Stateless 
public class AccountBean implements Account { 
    ... 
}
  • If it’s deployed as accountejb.jar, it’ll have the following JNDI bindings:
java:global/accountejb/AccountBean 
java:global/accountejb/AccountBean!com.bazaar.Account 

java:app/accountejb/AccountBean 
java:app/accountejb/AccountBean!com.bazaar.Account 

java:module/AccountBean 
java:module/AccountBean!com.bazaar.Account
  • If it’s deployed as accountejb.jar inside accountapp.ear, it’ll have the following JNDI bindings:
java:global/accountapp/accountejb/AccountBean java:global/accountapp/accountejb/AccountBean!com.bazaar.Account

java:app/accountejb/AccountBean 
java:app/accountejb/AccountBean!com.bazaar.Account 

java:module/AccountBean 
java:module/AccountBean!com.bazaar.Account
  • If it’s deployed as accountweb.war, it’ll have the following JNDI bindings:
java:global/accountweb/AccountBean 
java:global/accountweb/AccountBean!com.bazaar.Account 

java:app/accountweb/AccountBean 
java:app/accountweb/AccountBean!com.bazaar.Account 

java:module/AccountBean 
java:module/AccountBean!com.bazaar.Account

EJB injection using @EJB

The @EJB annotation was introduced to allow injection of EJBs into client code without the client code needing to perform JNDI lookups.

By using the @EJB annotation, that responsibility is now part of the EE server, and the EJB container will inject the dependencies for you.

@Target({TYPE, METHOD, FIELD}) 
@Retention(RUNTIME) 
public @interface EJB { 
    String name() default ""; 
    Class beanInterface() default Object.class; 
    String beanName() default ""; 
    String lookup() default ""; 
}

All three of the @EJB elements are optional, and for the most part, the EE server will be able to figure what bean to inject without any trouble simply by type.

@EJB annotation elements
ElementDescription
nameBesides performing injection, the @EJB annotation implicitly creates a binding referring to the injected EJB in the java:comp namespace. This is primarily done for backward compatibility. This attribute allows you to specify the name that’s used for the implicit binding. This is equivalent to the <ejb-ref-name> element in deployment descriptors used extensively in EJB 2.
beanInterfaceThis helps narrow the type of EJB references when needed, which can be a local business interface, a remote business interface, or the bean class if it implements no interfaces.
beanNameThis refers to either the name element of the @Stateless or @Stateful annotations or the <ejb-name> tag of the deployment descriptor for the bean. Like beanInterface, this helps to narrow down the specific bean to inject in cases where multiple beans implement the same interface.
lookupThis is the actual JNDI lookup name of the EJB to be injected. This is likely the attribute you’ll use the most.
mappedNameA vendor-specific name for the bean.
descriptionA description of the EJB.

When to use EJB injection

This annotation only works inside another EJB, in code running inside an application-client container (ACC), or in components registered with the web container (such as a Servlet or JSF-backing bean). If you need an EJB in any other code, you’ll need to use JNDI directly and perform a lookup.

@EJB annotation in action

Default injection

Suppose you have the following local interface and implementing bean:

@Local 
public interface AccountLocal { 
    public void Account getAccount(String accountId); 
} 

@Stateless(name="accountByDatabase") 
public class AccountByDatabaseEjb implements AccountLocal {
    . . .
}

To inject this bean into a container-managed resource, such as a Servlet, you can use the @EJB annotation without any of the optional elements and let the container figure out the DI:

@EJB 
AccountLocal accountInfo;

Injection using beanname

@Stateless(name="accountByActiveDirectory") 
public class AccountByActiveDirectoryEjb implements AccountLocal {
    . . .
}

With multiple beans now implementing the AccountLocal interface, you need to give the @EJB annotation a little help so the container knows which implementation should be injected:

@EJB(beanName="accountByActiveDirectory") 
AccountLocal accountInfo;

Injection using beaninterface

public interface AccountServices {
    public Account getAccount(String accountId);
}

@Local
public interface AccountLocal extends AccountServices {}

@Remote
public interface AccountRemote extends AccountServices {}

@Stateless
public class AccountEjb implements AccountLocal, AccountRemote {. . .}

The @EJB annotation is configured using the beanInterface property to tell the container to inject either the remote or the local interface:

@EJB(beanInterface="AccountLocal.class")
AccountServices accountServices;

Injecting like this tells the container you want to use a local bean but interact with it through AccountServices. This is good practice if you want to have both remote and local interactions to stay the same.

Injection using lookup

@EJB(lookup="java:module/DefaultAccountService") 
AccountServices accountServices;

The @EJB annotation is a special subset of DI that only handles EJBs. There are other resources, such as JDBC data sources, JMS queues, and email sessions, which the container manages and your code needs to get at. In the next ses- sion we’ll discuss the @Resource annotation for injecting these other resources.

Resource injection using @Resource

The @Resource annotation is by far the most versatile mechanism for resource injection in EJB 3.

  • In most cases the annotation is used to inject JDBC data sources, JMS resources, and EJB contexts.
  • But the annotation can also be used for anything in the JNDI registry.
@Target({TYPE, FIELD, METHOD}) @Retention(RUNTIME)
public @interface Resource {
    String name() default "";
    String lookup() default "";
    Class type() default java.lang.Object.class;
    AuthenticationType authenticationType() default AuthenticationType.CONTAINER;
    boolean shareable() default true;
}
@Resource annotation elements
ElementDescription
nameBesides performing an injection, the @Resource annotation implicitly creates a binding referring to the injected resource in the java:comp namespace. This is primarily done for backward compatibility. This attribute allows you to specify the name that’s used for the implicit binding. This is equivalent to the <res-ref-name> element in deployment descriptors used extensively in EJB 2.
lookupThis is the actual JNDI lookup name of the resource to be injected. This is likely the attribute you’ll use the most.
typeThe type of the resource. If the @Resource annotation is on a field, the default is the type of the field. If the annotation is on a setter method, the default is the type of the method’s element.
authenticationTypeThis can have the value AuthenticationType.CONTAINER or AuthenticationType.APPLICATION. This is only used for connection factory–type resources like data sources.
shareableThis indicates if this resource can be shared between other components. This is only for connection factory type resources like data sources.
mappedNameA vendor-specific name for the bean.
descriptionA description of the EJB.

When to use resource injection

The @Resource annotation is used to inject container-managed resources into code that the container also manages. This annotation works only inside an EJB, an MDB, in code running inside an application-client container (ACC), or in components regis- tered with the web container (such as a Servlet or JSF-backing bean). Resources range from a simple integer value to a complex DataSource, but as long as the resource is container-managed, the @Resource annotation can be used to inject it into your code.

@Resource annotation in action

Injecting Datasource

@Stateless
public class DefaultBidService implements BidService {
  ...
  @Resource(lookup="java:global/jdbc/ActionBazaarDB")
  private DataSource dataSource;

Injecting JMS resources

@Resource(lookup="java:global/jms/ActionBazaarQueue")
private Queue queue;

Injecting EJBContext

@Resource
private SessionContext context;

Note that the injected session context isn’t stored anywhere in JNDI. Instead, when the container detects the @Resource annotation on the context variable, it figures out that the EJB context specific to the current bean instance must be injected by looking at the variable data type, javax.ejb.SessionContext. Because DefaultBidService is a session bean, the result of the injection would be the same if the variable were specified to be the parent class, EJBContext.

Injecting environment entries

@Resource
private boolean censorship;
<env-entry>
  <env-entry-name>censorship</env-entry-name>
  <env-entry-type>java.lang.Boolean</env-entry-type>
  <env-entry-value>true</env-entry-value>
</env-entry>

Injecting email resources

@Resource(lookup="java:global/mail/ActionBazaar")
private javax.mail.Session mailSession;

Injecting the timer service

@Resource
private javax.ejb.TimerService timerService;

Field injection versus setter injection

In the vast majority of cases, resources and EJBs are injected into fields. In DI parlance this is called field injection. But besides field injection, EJB also supports setter injection (on the other hand, EJB doesn’t support constructor injection, whereas CDI does)

@Stateless
public class DefaultBidService implements BidService {
  ...
  private DataSource dataSource;
  ...
  @Resource(lookup="java:global/jdbc/ActionBazaarDB")
  public void setDataSource(DataSource dataSource) {
    this.dataSource = dataSource;
}

Looking up resources and EJBs from JNDI

EJBContext lookup

@Stateless
public class DefaultDiscountService implements DiscountService {
  @Resource
  private SessionContext sessionContext;
  ...
  DiscountRateService discountRateService
      = (DiscountRateService) sessionContext.lookup(
          "java:app/ejb/HolidayDiscountRateService");
  ...
  long discount = discountRateService.calculateDiscount(...);

InitialContext lookup

Although both DI and lookup using EJBContext are relatively convenient, the problem is that the EJBContext is available only inside the Java EE or application client container. For POJOs outside a container, you’re limited to the most basic method of looking up JNDI references—using a JNDI InitialContext.

Context context = new InitialContext();
BidService bidService = (BidService)
        context.lookup("java:app/ejb/DefaultBidService");
...
bidService.addBid(bid);

Also, the object can be used to connect to a remote JNDI server, not just a local one.

Mechanical JNDI lookup code was one of the major pieces of avoidable complexity in EJB 2, particularly when these same bits of code were repeated hun- dreds of times across an application.

When to use JNDI lookups

  • The @EJB and @Resource annotations are available only to classes managed by the Java EE or application client container. This includes objects like EJBs, MDBs, Servlets, and JSF-backing beans. Any nonmanaged class will need to use JNDI lookups through the InitialContext to get access to the server’s resources.
  • Resource access isn’t always static, and as a consequence, it may not be possible to use the @EJB or @Resource annotations to inject a static resource into your object.
  • If your resource lookup is dynamic, you’ll need to use JNDI lookups.
  • If this dynamic behavior occurs in a container-managed resource such as an EJB, you’re able to inject an EJBContext into your EJB (using @Resource, of course) and then use EJB- Context.lookup() to dynamically look up the resource you need.
  • If your dynamic behavior occurs in a non-container-managed resource, such as a utility class, you’ll need to use the InitialContext.

Application client containers

  • ACC: application client container

The ACC is a hidden gem in the EE world. It’s a mini Java EE container that can be run from the command line. Think of it as a souped-up Java Virtual Machine (JVM) with some Java EE juice added. You can run any Java SE client such as a Swing applica- tion inside the ACC as if you were using a regular JVM.

Any Java class with a main method can be run inside the ACC. Typically, you package your application as a JAR and define the MainClass in META-INF/MANIFEST. Optionally, the JAR may contain a deployment descriptor (META-INF/application-client.xml) and a jndi.properties file that contains the environment properties for connecting to a remote EJB container.

Embedded containers

An embedded container is an in-memory EJB container that an SE application (or unit tests) can start and run within its own JVM.

Creating an embedded container

The javax.ejb.embeddable.EJBContainer is an abstract class that EE servers may choose to implement—it’s not a requirement. A static method bootstraps its creation:

EJBContainer ec = EJBContainer.createEJBContainer();
 Methods of EJBContainer
MethodDescription
close()Shuts down the in-memory EJB container.
createEJBContainer()Creates an EJBContainer using default properties.
createEJBContainer(Map<?,?>)Creates an EJBContainer providing values for some properties.
getContext()Returns javax.naming.Context.

Registering EJBs

For example, this configuration will scan for EJBs only in the bidservice.jar and accountservice.jar files; there may be other EJBs in other modules but they’ll be ignored.

Properties props = new Properties();
props.setProperty(
  EJBContainer.MODULES, new String[]{"bidservice","accountsevice"});
EJBContainer ec = EJBContainer.createEJBContainer(props);

When an EJB is found, the embedded container will bind the EJB to JNDI using the portable global JNDI name, the same as a full EE server.

java:global/bidservice/BidServiceEjb
java:global/bidservice/BidServiceEjb!com.bazaar.BidServiceLocal

Performing EJB lookups

EJB lookups are the same as any other JNDI lookup.

Closing

Closing the embedded container doesn’t mean the SE application is closing as well.

try (EJBContainer ec = EJBContainer.createEJBContainer())
{
    // do what you want with the embedded container
} catch (Throwable t) {
    t.printStackTrace();
}

Using EJB injection and lookup effectively

Using the @EJB annotation to inject bean instances into your code is the quickest, easiest, and safest way for you to wire your application together.

But a lookup becomes necessary for @EJB injection if multiple beans implement the same interface and you’re referencing the bean by that interface.

CDI injection has a more elegant solution, which we’ll look at next.

EJB versus CDI injection

  • @EJB is limited to injecting only EJBs and only into managed resources like other EJBs, JSF- backing beans, and Servlets.
  • CDI, on the other hand, is much more powerful and @Inject can inject just about anything into anything else. This includes the ability for @Inject to inject EJBs. Because @Inject is more powerful and can inject EJB

Why use @EJB?

public interface BidService { ... }

@Stateless(name="defaultBids")
public class DefaultBidService implements BidService { ... }

@Stateless(name="clearanceBids")
public class ClearanceBidService implements BidService { ... }

@EJB handles this nicely in a few ways, but the easiest is to use the beanName parameter:

@EJB(beanName="clearanceBids")
BidService clearanceBidService;

@Inject works a bit differently and requires more work to narrow down which implementation of BidService to inject. @Inject requires the creation of a producer class, which follows the factory pattern:

public class BidServiceProducer {
    @Produces
    @EJB(beanName="defaultBids")
    @DefaultBids
    BidService defaultBids;

    @Produces
    @EJB(beanName="clearanceBids")
    @ClearanceBids
    BidService clearanceBids;
}

@Inject can now be paired with the qualifiers used in BidServiceProducer to inject the right instance of the BidService EJB:

@Inject @ClearanceBids
BidService clearanceBidService;

AOP in the EJB world: interceptors

@Interceptor
public class SayHelloInterceptor {
  @AroundInvoke
  public Object sayHello(InvocationContext ctx) throws Exception {
    System.out.println("Hello Interceptor!");
    return ctx.proceed();
  }
}
  • @Interceptors annotation applied to an individual method:
@Stateless
public class OrderBean {
  @Interceptors(SayHelloInterceptor.class)
  public Order findOrderById(String id) { return null; }
}
  • @Interceptors annotation applied to the EJB class itself.
@Stateless
@Interceptors(SayHelloInterceptor.class)
public class OrderBean {  }

Specifying interceptors

@Interceptors can be used at both a method and a class level.

Method and class-level interceptors

The @Interceptors annotation is fully capable of attaching more than one interceptor at either a class or a method level.

@Stateless
@Interceptors({SayHelloInterceptor.class, SayGoodByeInterceptor.class})
public class OrderBean { ... }

Default-level interceptor

A default interceptor is a catchall mechanism that attaches to all the methods of every bean in the module.

  • If the interceptor is in an EJB-JAR file, it’s applied only to beans in that EJB-JAR file and no other EJB-JAR files in the EJB container.
  • If the interceptor is inside of a JAR that’s part of the library of a WAR, the interceptor is applied to only those beans in the JAR, not to the entire WAR.

To define a default interceptor for a module, you have to use the ejb-jar.xml file. No corresponding annotation exists.

image.png

Ordering inteerceptors

The default ordering of interceptors.

  • the default-level interceptor is triggered first,
  • then any class-level interceptors in the order in which they’re listed in the @Interceptors annotation on the class
  • and finally the method-level interceptors in the order in which they’re listed in the @Interceptors annotation on the method.

To change the default ordering of interceptors, you use the ejb-jar.xml file to configure the order.

image.png

<interceptor-order>: any default-level interceptors will be overridden as well as any class-level interceptors defined by the @Interceptors annotation

If your bean also has methods annotated with @Interceptors, the ejb-jar.xml can be used to override and reorder those interceptors as well.

image.png

Disbale interceptors

  • @javax.interceptor.ExcludeDefaultInterceptors: on either a class or a method disables all default interceptors on the class or method.
  • @javax.interceptor.ExcludeClassInterceptors: disables class-level interceptors for a method.
@Interceptors(SayHelloInterceptor.class)
@ExcludeDefaultInterceptors
@ExcludeClassInterceptors
public Order findOrderById(String id) { ... }

Interceptors in action

image.png

@AroundInvoke methods

@AroundInvoke
public Object logMethodEntry(InvocationContext invocationContext)
throws Exception {
    System.out.println("Entering method: "
        + invocationContext.getMethod().getName());
    return invocationContext.proceed();
}

InvocationContext interface

public interface InvocationContext {
    public Object getTarget();
    public Method getMethod();
    public Object[] getParameters();
    public void setParameters(Object[]);
    public java.util.Map<String,Object> getContextData();
    public Object proceed() throws Exception;
}
  • getTarget(): retrieves the bean instance that the intercepted method belongs to.
  • getMethod(): returns the method of the bean class for which the interceptor was invoked.
  • getParameters(): returns the parameters passed to the intercepted method as an array of objects.
  • setParameters(): allows you to change these values at runtime before they’re passed to the method.
@AroundInvoke
public Object giveDiscount(InvocationContext context)
throws Exception {
    System.out.println("*** DiscountVerifier Interceptor"
        + " invoked for " + context.getMethod().getName() + " ***");
    if (context.getMethod().getName().equals("chargePostingFee")
        && (((String) (context.getContextData().get("MemberStatus")))
            .equals("Gold"))) {
        Object[] parameters = context.getParameters();
        parameters[2] = new Double((Double) parameters[2] * 0.99);
        System.out.println(
            "*** DiscountVerifier Reducing Price by 1 percent ***");
            context.setParameters(parameters);
    }
    return context.proceed();
}

Lifecycle callback methods in the interceptor class

  • Differences between lifecycle and business method interceptors. Lifecycle interceptors are created to handle EJB lifecycle callbacks. Business method interceptors are associated with business methods and are automatically invoked when a user invokes the business method.
Supported featureLifecycle callback methodsBusiness method interceptor
InvocationGets invoked when a certain lifecycle event occurs.Gets invoked when a business method is called by a client.
LocationIn a separate Interceptor class or in the bean class.In the class or an interceptor class.
Method signaturevoid <METHOD> (InvocationContext)—in a separate interceptor class. void <METHOD>()—in the bean class.Object <METHOD>(InvocationContext) throws Exception
Annotation@PreDestroy, @PostConstruct, @PrePassivate, @PostActivate@AroundInvoke
Exception handlingMay throw runtime exceptions but must not throw checked exceptions. May catch and swallow exceptions. No other lifecycle callback methods are called if an exception is thrown.May throw application or runtime exception. May catch and swallow runtime exceptions. No other business interceptor methods or the business method itself are called if an exception is thrown before calling the proceed method.
Transaction and security contextNo security and transaction context.Share the same security and transaction context within which the original business method was invoked.

Using interceptors effectively

Interceptors are used to implement crosscutting concern logic before or after a business method is executed. Keep their use limited to these crosscutting concerns like auditing, metrics, logging, error handling, and so on.

CDI versus EJB interceptors

EJB interceptors are very powerful, but they provide only a basic framework for implementing interceptors. CDI takes this a step further by providing type-safe interceptor bindings that can be combined in a number of different ways to provide much more advanced options when implementing interceptors.

Creating interceptor bindings

@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RUNTIME)
public @interface Audited {}

Declaring bindings for an interceptor

@Audited @Interceptor
public class AuditInterceptor {
    @AroundInvoke
    public Object audit(InvocationContext context) throws Exception {
        System.out.print("Invoking: "
            + context.getMethod().getName());
        System.out.println(" with arguments: "
            + context.getParameters());
        return context.proceed();
    }
}

Looking an interceptor to a bean

@Stateless
@Audited
public class BidService {
    ...
}

or 

@Stateless
public class BidService {
    @Audited
    public void addBid(Bid bid) {
        bidDao.addBid(bid);
    }
    ...
}

beans.xml

Recall that when using CDI, your bean archive JAR file needs a META-INF/beans.xml file for CDI to look in your JAR files for beans. In addition, when using CDI interceptors, all the interceptors you want to activate must be listed in beans.xml. So the final step is to add the AuditInterceptor to bean.xml:

<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.bazaar.AuditInterceptor</class>
   </interceptors>
</beans>

Multiple bindings

image.png

@Secured @Interceptor
public class SecurityCheckInterceptor {
    @AroundInvoke
    public Object checkSecurity(InvocationContext context)
    throws Exception {
        // Check security here, proceed if OK
        return context.proceed();
    }
}

If CDI interceptors are mixed with EJB interceptors, the EJB interceptors go first, followed by the CDI ones. So the order of interceptor execution would be as follows:

  • Interceptors in the @Interceptors annotation
  • Interceptors in ejb-jar.xml
  • List of CDI interceptors in beans.xml