Jersey 2 + HK2 - 类的自动绑定
Jersey 2 + HK2 - automatic binding of classess
话题继续
.
我已经知道如何绑定 类 以便 @Inject
它们正确。
你有什么想法,如何使这个过程自动化?将每个服务都放在 bind
语句中似乎在我的应用程序中很难闻。
我建议先看这里:Automatic Service Population。
基本过程是在 类 上使用 @Service 注释并在构建时使用 JSR-269 (APT) 处理器 (Metadata Generator)。这样做会将一些元数据添加到您的 jar 文件中(通常在 META-INF/hk2-locator/default 下)。
然后,您可以确保自动获取这些服务,而不必使用每个 ServiceLocator 中都可用的 Populator which you get from the Dynamic Configuration Service 进行所有那些讨厌的绑定。
伪代码应该是这样的:
public void populate(ServiceLocator locator) throws Exception {
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
populator.populate(new ClasspathDescriptorFileFinder(getClass().getClassLoader()));
}
在上面的代码中,ClasspathDescriptorFileFinder 用于搜索类路径以查找元数据。在 OSGi 等环境中可以使用其他策略。
IMO 这是添加服务的更好方法,而不是您自己进行所有绑定。
使用 Google 的 Guice 多年后,我已经习惯了 Just-In-Time binder 的可用性,允许注入任意类型而无需任何前期配置。
我也发现必须显式绑定每个服务的想法是一种糟糕的代码味道。我也不喜欢使用特殊构建步骤和为填充器添加初始化代码的需要。
所以我想出了以下 JustInTimeResolver
实现:
/**
* Mimic GUICE's ability to satisfy injection points automatically,
* without needing to explicitly bind every class, and without needing
* to add an extra build step.
*/
@Service
public class JustInTimeServiceResolver implements JustInTimeInjectionResolver {
@Inject
private ServiceLocator serviceLocator;
@Override
public boolean justInTimeResolution( Injectee injectee ) {
final Type requiredType = injectee.getRequiredType();
if ( injectee.getRequiredQualifiers().isEmpty() && requiredType instanceof Class ) {
final Class<?> requiredClass = (Class<?>) requiredType;
// IMPORTANT: check the package name, so we don't accidentally preempt other framework JIT resolvers
if ( requiredClass.getName().startsWith( "com.fastmodel" )) {
final List<ActiveDescriptor<?>> descriptors = ServiceLocatorUtilities.addClasses( serviceLocator, requiredClass );
if ( !descriptors.isEmpty() ) {
return true;
}
}
}
return false;
}
}
在我的项目中,我只需将以下内容添加到我的 Jersey 应用程序配置中的活页夹中:
bind( JustInTimeServiceResolver.class ).to( JustInTimeInjectionResolver.class );
并且我可以像在 Guice 中那样自动创建绑定。
我有一个建议可以解决我的问题,我已经尝试了建议的解决方案,但在这里没有用。在我的解决方案中,有必要用@MyInjectable 注释对每个 class 进行注释。
1-创建注释
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface MyInjectable {
}
2-创建一个AbstractBinder实现
public class MyApplicationBinder extends AbstractBinder {
@Override
protected void configure() {
bindFactory(EMFFactory.class).to(EntityManagerFactory.class).in(Singleton.class);
bindFactory(EMFactory.class).to(EntityManager.class).in(RequestScoped.class);
bind(Environment.class).to(Environment.class);
scanAndBind("com.yourpackage.here");
}
private void scanAndBind(String packageName) {
try {
Class[] classes = getClasses(packageName);
for (Class<?> klazz:
classes) {
MyInjectable annotation = klazz.getAnnotation(MyInjectable.class);
if (annotation!= null) {
bind(klazz).to(klazz);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}
3-创建资源配置
public class MyApplication extends ResourceConfig {
@Inject
public MyApplication(ServiceLocator locator) {
ServiceLocatorUtilities.enableImmediateScope(locator);
....
register(new MyApplicationBinder());
}
}
4-在web.xml
中正确配置
<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>br.com.solutiontrue.ws</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>your.package.name.MyApplication</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.resource.validation.disable</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
话题继续
我已经知道如何绑定 类 以便 @Inject
它们正确。
你有什么想法,如何使这个过程自动化?将每个服务都放在 bind
语句中似乎在我的应用程序中很难闻。
我建议先看这里:Automatic Service Population。
基本过程是在 类 上使用 @Service 注释并在构建时使用 JSR-269 (APT) 处理器 (Metadata Generator)。这样做会将一些元数据添加到您的 jar 文件中(通常在 META-INF/hk2-locator/default 下)。
然后,您可以确保自动获取这些服务,而不必使用每个 ServiceLocator 中都可用的 Populator which you get from the Dynamic Configuration Service 进行所有那些讨厌的绑定。
伪代码应该是这样的:
public void populate(ServiceLocator locator) throws Exception {
DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
populator.populate(new ClasspathDescriptorFileFinder(getClass().getClassLoader()));
}
在上面的代码中,ClasspathDescriptorFileFinder 用于搜索类路径以查找元数据。在 OSGi 等环境中可以使用其他策略。
IMO 这是添加服务的更好方法,而不是您自己进行所有绑定。
使用 Google 的 Guice 多年后,我已经习惯了 Just-In-Time binder 的可用性,允许注入任意类型而无需任何前期配置。
我也发现必须显式绑定每个服务的想法是一种糟糕的代码味道。我也不喜欢使用特殊构建步骤和为填充器添加初始化代码的需要。
所以我想出了以下 JustInTimeResolver
实现:
/**
* Mimic GUICE's ability to satisfy injection points automatically,
* without needing to explicitly bind every class, and without needing
* to add an extra build step.
*/
@Service
public class JustInTimeServiceResolver implements JustInTimeInjectionResolver {
@Inject
private ServiceLocator serviceLocator;
@Override
public boolean justInTimeResolution( Injectee injectee ) {
final Type requiredType = injectee.getRequiredType();
if ( injectee.getRequiredQualifiers().isEmpty() && requiredType instanceof Class ) {
final Class<?> requiredClass = (Class<?>) requiredType;
// IMPORTANT: check the package name, so we don't accidentally preempt other framework JIT resolvers
if ( requiredClass.getName().startsWith( "com.fastmodel" )) {
final List<ActiveDescriptor<?>> descriptors = ServiceLocatorUtilities.addClasses( serviceLocator, requiredClass );
if ( !descriptors.isEmpty() ) {
return true;
}
}
}
return false;
}
}
在我的项目中,我只需将以下内容添加到我的 Jersey 应用程序配置中的活页夹中:
bind( JustInTimeServiceResolver.class ).to( JustInTimeInjectionResolver.class );
并且我可以像在 Guice 中那样自动创建绑定。
我有一个建议可以解决我的问题,我已经尝试了建议的解决方案,但在这里没有用。在我的解决方案中,有必要用@MyInjectable 注释对每个 class 进行注释。
1-创建注释
@Retention(RUNTIME)
@Target(ElementType.TYPE)
public @interface MyInjectable {
}
2-创建一个AbstractBinder实现
public class MyApplicationBinder extends AbstractBinder {
@Override
protected void configure() {
bindFactory(EMFFactory.class).to(EntityManagerFactory.class).in(Singleton.class);
bindFactory(EMFactory.class).to(EntityManager.class).in(RequestScoped.class);
bind(Environment.class).to(Environment.class);
scanAndBind("com.yourpackage.here");
}
private void scanAndBind(String packageName) {
try {
Class[] classes = getClasses(packageName);
for (Class<?> klazz:
classes) {
MyInjectable annotation = klazz.getAnnotation(MyInjectable.class);
if (annotation!= null) {
bind(klazz).to(klazz);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static Class[] getClasses(String packageName)
throws ClassNotFoundException, IOException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
assert classLoader != null;
String path = packageName.replace('.', '/');
Enumeration<URL> resources = classLoader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
dirs.add(new File(resource.getFile()));
}
ArrayList<Class> classes = new ArrayList<Class>();
for (File directory : dirs) {
classes.addAll(findClasses(directory, packageName));
}
return classes.toArray(new Class[classes.size()]);
}
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<Class>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
for (File file : files) {
if (file.isDirectory()) {
assert !file.getName().contains(".");
classes.addAll(findClasses(file, packageName + "." + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
}
}
return classes;
}
}
3-创建资源配置
public class MyApplication extends ResourceConfig {
@Inject
public MyApplication(ServiceLocator locator) {
ServiceLocatorUtilities.enableImmediateScope(locator);
....
register(new MyApplicationBinder());
}
}
4-在web.xml
中正确配置<servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>br.com.solutiontrue.ws</param-value>
</init-param>
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>your.package.name.MyApplication</param-value>
</init-param>
<init-param>
<param-name>jersey.config.server.resource.validation.disable</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>