本文部分内容节选自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规范支持的环境注册项类型如下:
- java.lang.String
- java.lang.Integer
- java.lang.Short
- java.lang.Float
- java.lang.Double
- java.lang.Byte
- java.lang.Character
- java.lang.Boolean
在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名。