本文部分内容节选自Enterprise JavaBeans 3.0 by Bill Burke & Richard Monson-Haefel
3 Injection
每个部署于应用服务器中的EJB容器都拥有一个属于它自己的内部注册表(internal registry),该内部注册表被成为Enterprise Naming Context(ENC)。EJB容器可以在其中维护某些指向外部环境资源的引用。可以通过annotation和XML文件配置ENC。可以绑定到ENC中的内容有:EJB接口、EntityManagerFactory、EntityManager、DataSource、JMS Destination、TimeService、UserTransaction和Environment Entry等。
3.1 Injection of EJBs
以下是个关于Injection of EJBs的简单例子:
public interface DataStore { String getData(); }
import javax.ejb.Local; @Local public interface DataStoreLocal extends DataStore { }
import javax.ejb.Remote; @Remote public interface DataStoreRemote extends DataStore { }
import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; @Stateless @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class DataStoreImpl implements DataStoreLocal, DataStoreRemote { public String getData() { return "data"; } }
import javax.ejb.Local; @Local public interface DataReaderLocal { String readLocalData(); }
import javax.ejb.Remote; @Remote public interface DataReaderRemote { String readRemoteData(); }
import javax.ejb.EJB; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; @Stateless @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class DataReaderImpl implements DataReaderLocal, DataReaderRemote { @EJB(name="DataStoreLocal") private DataStoreLocal dataStoreLocal; @EJB(name="DataStoreRemote") private DataStoreRemote dataStoreRemote; public String readLocalData() { return "Local " + dataStoreLocal.getData(); } public String readRemoteData() { return "Remote " + dataStoreRemote.getData(); } }
import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; public class InjectionTest { public static void main(String args[]) throws Exception { // Properties properties = new Properties(); properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory"); InitialContext ctx = new InitialContext(properties); // DataReaderLocal drl = (DataReaderLocal)ctx.lookup("DataReaderImplLocal"); System.out.println(drl.readLocalData()); // Object ref = ctx.lookup("DataReaderImplRemote"); DataReaderRemote drr = (DataReaderRemote) PortableRemoteObject.narrow(ref, DataReaderRemote.class); System.out.println(drr.readRemoteData()); } }
任何注册到ENC中的内容都可以在java:comp/env上下文中通过名称查找。SessionContext和MessageDrivenContext接口都继承自EJBContext。EJBContext接口中有个便捷方法用于JNDI查找,它不抛出检查式异常,而且用的是相对于ENC的内部名称,而不是之前提到的java:comp/env全名。以下是个简单的例子:
import javax.annotation.Resource; import javax.ejb.EJB; import javax.ejb.EJBs; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; @Stateless @EJBs ({ @EJB(name="DataStoreLocal", beanInterface=DataStoreLocal.class), @EJB(name="DataStoreRemote", beanInterface=DataStoreRemote.class) }) @TransactionAttribute(TransactionAttributeType.SUPPORTS) public class DataReaderImpl implements DataReaderLocal, DataReaderRemote { @Resource private SessionContext context; public String readLocalData() { // DataStoreLocal dsl = null; try { InitialContext ctx = new InitialContext(); dsl = (DataStoreLocal)ctx.lookup("java:comp/env/DataStoreLocal"); } catch (NamingException e) { throw new RuntimeException("failed to lookup DataStoreLocal", e); } // return "Local " + dsl.getData(); } public String readRemoteData() { Object ref = context.lookup("DataStoreRemote"); DataStoreRemote dsr = (DataStoreRemote)PortableRemoteObject.narrow(ref, DataStoreRemote.class); return "Remote " + dsr.getData(); } }
3.2 Injection of DataSource
以下是个关于Injection of DataSource的简单例子:
import java.io.Serializable; import javax.persistence.Basic; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Movie implements Serializable { // private static final long serialVersionUID = -3282817754636291024L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @Basic private String title; @Basic private String director; @Basic private int year; public Movie() { } public Movie(String title, String director, int year) { this.title = title; this.director = director; this.year = year; } public String toString() { StringBuffer sb = new StringBuffer(); sb.append("id: " + id); sb.append(", title: " + title); sb.append(", director: " + director); sb.append(", year: " + year); return sb.toString(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getDirector() { return director; } public void setDirector(String director) { this.director = director; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } }
import java.util.List; public interface MovieDao { List<Movie> getAllMovies(); void addMovies(List<Movie> movies); void deleteMovie(Movie movie); }
import javax.ejb.Local; @Local public interface MovieDaoJdbcLocal extends MovieDao { }
import javax.ejb.Remote; @Remote public interface MovieDaoJdbcRemote extends MovieDao { }
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.sql.DataSource; @Stateless @TransactionAttribute(TransactionAttributeType.REQUIRED) public class MovieDaoJdbcImpl implements MovieDaoJdbcLocal, MovieDaoJdbcRemote { @Resource(name="mysqlDataSource") private DataSource mysql; public List<Movie> getAllMovies() { ArrayList<Movie> movies = new ArrayList<Movie>(); try { Connection con = mysql.getConnection(); PreparedStatement stmt = con.prepareStatement("SELECT id, director, title, year FROM movie"); ResultSet rs = stmt.executeQuery(); while ( rs.next() ) { Movie movie = new Movie(); movie.setId(rs.getInt("id")); movie.setDirector(rs.getString("director")); movie.setTitle(rs.getString("title")); movie.setYear(rs.getInt("year")); movies.add(movie); } rs.close(); stmt.close(); con.close(); } catch(SQLException e) { throw new RuntimeException(e); } return movies; } public void addMovies(List<Movie> movies) { try { Connection con = mysql.getConnection(); for(Movie movie : movies) { PreparedStatement stmt = con.prepareStatement("INSERT INTO movie (director, title, year) VALUES (?, ?, ?)"); stmt.setString(1, movie.getDirector()); stmt.setString(2, movie.getTitle()); stmt.setInt(3, movie.getYear()); stmt.execute(); stmt.close(); } con.close(); } catch(SQLException e) { throw new RuntimeException(e); } } public void deleteMovie(Movie m) { try { Connection con = mysql.getConnection(); PreparedStatement stmt = con.prepareStatement("DELETE FROM movie WHERE id = ?"); stmt.setInt(1, m.getId()); stmt.executeUpdate(); stmt.close(); con.close(); } catch(SQLException e) { throw new RuntimeException(e); } } }
OpenEJB可以通过JNDI Context的properties定义DataSource,例如:
import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; public class InjectionOfDataSourceTest { public static void main(String args[]) throws Exception { // Properties properties = new Properties(); properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory"); properties.put("mysqlDataSource", "new://Resource?type=DataSource"); properties.put("mysqlDataSource.JdbcDriver", "com.mysql.jdbc.Driver"); properties.put("mysqlDataSource.JdbcUrl", "jdbc:mysql://localhost:3306/ejb"); properties.put("mysqlDataSource.username", "root"); properties.put("mysqlDataSource.password", "password"); properties.put("mysqlDataSourceUnmanaged.JtaManaged", "true"); InitialContext ctx = new InitialContext(properties); // List<Movie> movies = new ArrayList<Movie>(); movies.add(new Movie("Dances with Wolves", "Kevin Costner", 1990)); movies.add(new Movie("Legends of the Fall", "Edward Zwich", 1994)); movies.add(new Movie("A Very Long Engagement", "Jean-Pierre Jeunet", 2004)); // Object ref1 = ctx.lookup("MovieDaoJdbcImplRemote"); MovieDaoJdbcRemote dao1 = (MovieDaoJdbcRemote) PortableRemoteObject.narrow(ref1, MovieDaoJdbcRemote.class); dao1.addMovies(movies); for(Movie m : dao1.getAllMovies()) { System.out.println("dao1.getAllMovies(): " + m); dao1.deleteMovie(m); } } }
此外,也可以在conf/openejb.xml中定义DataSource,例如以下是openejb.xml中的相关配置:
<Resource id="mysqlDataSource" type="DataSource"> JdbcDriver com.mysql.jdbc.Driver JdbcUrl jdbc:mysql://localhost:3306/ejb UserName root Password password JtaManaged true </Resource>
目前OpenEJB尚不支持在openejb.xml中,以加密后的形式保存Password,关于详细介绍请参考OpenEJB User Forum。
3.3 Injection of EntityManager
EntityManager可以被注入到EJB中。当你将EntityManager注册到ENC中或者注入到EJB时,EJB容器会对EntityManager所依赖的persistentce context具有完全的控制权。在注入的EntityManager上调用close方法会导致异常。默认的PersistenceContextType是TRANSACTION。EXTENDED类型的persistence context只能用于stateful session bean。META-INF目录下的persistence.xml用以配置JPA。以下是个简单的例子:
import javax.ejb.Local; @Local public interface MovieDaoJpaLocal extends MovieDao { }
import javax.ejb.Remote; @Remote public interface MovieDaoJpaRemote extends MovieDao { }
import java.util.List; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType; import javax.persistence.Query; @Stateless @TransactionAttribute(TransactionAttributeType.REQUIRED) public class MovieDaoJpaImpl implements MovieDaoJpaLocal, MovieDaoJpaRemote { @PersistenceContext(unitName = "ejb", type = PersistenceContextType.TRANSACTION) private EntityManager entityManager; public List<Movie> getAllMovies() { Query query = entityManager.createQuery("SELECT m from Movie as m"); return query.getResultList(); } public void addMovies(List<Movie> movies) { for(Movie m : movies) { entityManager.persist(m); } } public void deleteMovie(Movie movie) { Movie m = entityManager.merge(movie); entityManager.remove(m); } }
import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; public class InjectionOfEntityManagerTest { public static void main(String args[]) throws Exception { // Properties properties = new Properties(); properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory"); properties.put("mysqlDataSource", "new://Resource?type=DataSource"); properties.put("mysqlDataSource.JdbcDriver", "com.mysql.jdbc.Driver"); properties.put("mysqlDataSource.JdbcUrl", "jdbc:mysql://localhost:3306/ejb"); properties.put("mysqlDataSource.username", "root"); properties.put("mysqlDataSource.password", "password"); properties.put("mysqlDataSourceUnmanaged.JtaManaged", "true"); InitialContext ctx = new InitialContext(properties); // List<Movie> movies = new ArrayList<Movie>(); movies.add(new Movie("Dances with Wolves", "Kevin Costner", 1990)); movies.add(new Movie("Legends of the Fall", "Edward Zwich", 1994)); movies.add(new Movie("A Very Long Engagement", "Jean-Pierre Jeunet", 2004)); // Object ref2 = ctx.lookup("MovieDaoJpaImplRemote"); MovieDaoJpaRemote dao2 = (MovieDaoJpaRemote) PortableRemoteObject.narrow(ref2, MovieDaoJpaRemote.class); dao2.addMovies(movies); for(Movie m : dao2.getAllMovies()) { System.out.println("dao2.getAllMovies(): " + m); dao2.deleteMovie(m); } } }
META-INF/ persistence.xml文件的内容如下:
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0"> <persistence-unit name="ejb"> <jta-data-source>mysqlDataSource</jta-data-source> <provider> org.apache.openjpa.persistence.PersistenceProviderImpl </provider> <properties> <property name="openjpa.Log" value="log4j"/> <property name="openjpa.ConnectionFactoryProperties" value="PrettyPrint=true, PrettyPrintLineLength=72"/> <property name="openjpa.jdbc.TransactionIsolation" value="read-committed"/> </properties> </persistence-unit> </persistence>
3.4 Injection of env-entry
可以通过环境注册项(environment entry)对EJB进行配置。环境注册项不是元数据,而是配置项。EJB3.0规范支持的环境注册项类型如下:
在OpenEJB中并不限于以上类型。如果提供了合适的java.beans.PropertyEditor,那么任何可以从String转换到的类型都被支持。除了在META-INF/ejb-jar.xml中配置环境注册项外,也可以在META-INF/env-entries.properties文件中进行配置。以下是个简单的例子:
import java.beans.PropertyEditorManager; public enum Fruit { // APPLE, PEACH, UNKNOWN; // static { PropertyEditorManager.registerEditor(Fruit.class, FruitEditor.class); } }
import java.beans.PropertyEditorSupport; public class FruitEditor extends PropertyEditorSupport { public void setAsText(String text) throws IllegalArgumentException { text = text.trim(); if (text.equalsIgnoreCase("APPLE")) { setValue(Fruit.APPLE); } else if (text.equalsIgnoreCase("PEACH")) { setValue(Fruit.PEACH); } else { setValue(Fruit.UNKNOWN); } } }
import java.util.Date; import java.util.List; import java.util.Map; import javax.ejb.Local; @Local public interface BasketLocal { int getCount(); Date getDate(); Fruit getFruit(); List<Fruit> getList(); Map<Fruit, Float> getMap(); }
import java.util.Date; import java.util.List; import java.util.Map; import javax.annotation.Resource; import javax.ejb.Stateful; @Stateful public class BasketImpl implements BasketLocal { @Resource private int count; @Resource(name="date") private Date date; @Resource(name = "fruit") private Fruit fruit; @Resource(name="list") private List<Fruit> list; @Resource private Map<Fruit, Float> map; public int getCount() { return count; } public Date getDate() { return date; } public Fruit getFruit() { return fruit; } public List<Fruit> getList() { return list; } public Map<Fruit, Float> getMap() { return map; } }
import java.util.Iterator; import java.util.Properties; import javax.naming.Context; import javax.naming.InitialContext; public class InjectionOfEnvEntryTest { public static void main(String args[]) throws Exception { // Properties properties = new Properties(); properties.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory"); InitialContext ctx = new InitialContext(properties); // BasketLocal basket = (BasketLocal)ctx.lookup("BasketImplLocal"); System.out.println("count: " + basket.getCount()); System.out.println("date: " + basket.getDate()); System.out.println("fruit: " + basket.getFruit()); System.out.print("list: "); for(Iterator<Fruit> iter = basket.getList().iterator(); iter.hasNext();) { System.out.print(iter.next()); if(iter.hasNext()) { System.out.print(", "); } } System.out.println(); System.out.print("map: "); for(Iterator<Fruit> iter = basket.getMap().keySet().iterator(); iter.hasNext();) { Fruit key = iter.next(); System.out.print(key + "=" + basket.getMap().get(key)); if(iter.hasNext()) { System.out.print(", "); } } System.out.println(); } }
META-INF/env-entries.properties的内容如下:
date=2008-05-06
list=APPLE,PEACH,PEACH
com.yourpackage.BasketImpl/map=APPLE=1.00\nPEACH=2.00\n
META-INF/ejb-jar.xml的内容如下:
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" version="3.0" metadata-complete="false"> <enterprise-beans> <session> <ejb-name>BasketImpl</ejb-name> <env-entry> <description>count</description> <env-entry-name>com.yourpackage.BasketImpl/count</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>12</env-entry-value> </env-entry> <env-entry> <description>fruit</description> <env-entry-name>fruit</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>APPLE</env-entry-value> </env-entry> </session> </enterprise-beans> </ejb-jar>
需要注意的是,以上例子中BasketImpl的成员变量count和map的@Resource中没有指定name属性,那么默认的ENC名称是:所在类的全限定类名 / 数据成员或方法的基础名。当需要在XML部署描述文件中进行配置时,需要正确使用ENC名。