现在基本上所有的项目都会用到IOC,在Spring中,接口和实现的绑定是定义在xml文件中的,guice是定义在java类中,用过Module来管理,但是在大部分情况下,或者有些小项目中,我们并不想繁琐的定义这些关系,而且发生变更的时候修改起来很烦,那我们改如何处理呢.
从刚学习spring的时候就很好奇spring的注解究竟是如何实现的自动注入自动绑定等,后来用到guice,发现guice也有一个ImplementedBy
的注解,定义在接口上,用来指定接口的实现.使用代码如下
@ImplementedBy(PayPalCreditCardProcessor.class)
public interface CreditCardProcessor {
ChargeResult charge(String amount, CreditCard creditCard)
throws UnreachableException;
}
调用的时候直接就可以根据CreditCardProcessor来获取对应的实现.究竟是怎么做到的呢,前天决定好好研究以下,于是把guice的源代码拿下来跟进去看了看,发现如下代码
/**
* Returns a just-in-time binding for {@code key}, creating it if necessary.
*
* @throws ErrorsException if the binding could not be created.
*/
private <T> BindingImpl<T> getJustInTimeBinding(Key<T> key, Errors errors, JitLimitation jitType)
throws ErrorsException {
boolean jitOverride = isProvider(key) || isTypeLiteral(key) || isMembersInjector(key);
synchronized (state.lock()) {
// first try to find a JIT binding that we've already created
for (InjectorImpl injector = this; injector != null; injector = injector.parent) {
@SuppressWarnings("unchecked") // we only store bindings that match their key
BindingImpl<T> binding = (BindingImpl<T>) injector.jitBindings.get(key);
if (binding != null) {
// If we found a JIT binding and we don't allow them,
// fail. (But allow bindings created through TypeConverters.)
if (options.jitDisabled
&& jitType == JitLimitation.NO_JIT
&& !jitOverride
&& !(binding instanceof ConvertedConstantBindingImpl)) {
throw errors.jitDisabled(key).toException();
} else {
return binding;
}
}
}
if (failedJitBindings.contains(key) && errors.hasErrors()) {
throw errors.toException();
}
return createJustInTimeBindingRecursive(key, errors, options.jitDisabled, jitType);
} // end synchronized(state.lock())
}
```
原来guice的``ImplementedBy``注解并不是在容器启动的时候就进行了绑定,而是在获取接口对应实现的时候,扫描接口的注解,得到``ImplementedBy``注解中的实现,然后动态创建对应的实现类,进行绑定的,关于Guice的Just in time,可以查看这篇文章:[https://code.google.com/p/google-guice/wiki/JustInTimeBindings](https://code.google.com/p/google-guice/wiki/JustInTimeBindings).
发现Guice并没有做一个自动扫描的东西在启动的时候直接绑定,为什么Spring启动的时候那么慢呢,有一部分原因就是spring在启动的时候会进行类扫描,然后进行绑定注入等操作.这里不讨论自动绑定接口和实现在其他层面的问题.仅从技术层面讨论.那我们如何来让接口和实现自动绑定呢?实现思路如下:
1. 定义一个注解,比如``@AutoBind``,定义在具体的实现类上.
2. 扫描所有class目录的类,发现类标记有``@AutoBind``注解,则保存到内存中.
3. 通过确定的类来获取类所对应的接口或者超类,然后进行关系的绑定.
4. 遇到一个接口有多个实现的时候,使用JSR303定义的``@Named``注解来标识每个实现绑定时的别名.
定义一个注解很简单,但是第2步我们要怎么处理呢,Google了下相关的问题.发现了很多的实现,如下:
[http://guoliangqi.iteye.com/blog/644876](http://guoliangqi.iteye.com/blog/644876) 代码比较简陋,无法在生产环境下使用,扫描也很慢.
这个帖子里面对比了一些其他方式,包括``Scannotation``,作者后面推荐一个工具``Reflections``.
[http://bill.burkecentral.com/2008/01/14/scanning-java-annotations-at-runtime/](http://bill.burkecentral.com/2008/01/14/scanning-java-annotations-at-runtime/)
后来我试用了``Reflections``,发现其本身要依赖好多东西,有没有更简单的方案呢,从我做公司内部框架的时候就一直在寻找,最后终于在stackoverflow的某个答案中找到了合适的工具.[http://stackoverflow.com/questions/259140/scanning-java-annotations-at-runtime](http://stackoverflow.com/questions/259140/scanning-java-annotations-at-runtime)
那就是``annotation-detector``,项目地址:[https://github.com/rmuller/infomas-asl](https://github.com/rmuller/infomas-asl) 只有简单的几个类,就能实现注解的扫描,后来我测试了其他的扫描工具,发现这个的速度丝毫不差,对其简单的封装以下,就能满足我们的需求.
扫描工具类部分代码如下:
```java
/**
* 扫描类注解.
*
* @param annotationClass
* @param packages
* @return
*/
public static Set<Class<?>> scan(
final Class<? extends Annotation> annotationClass,
String... packages) {
final Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
final Reporter reporter = new TypeReporter() {
@SuppressWarnings("unchecked")
@Override
public Class<? extends Annotation>[] annotations() {
return new Class[] { annotationClass };
}
@Override
public void reportTypeAnnotation(
Class<? extends Annotation> annotation, String className) {
loadClass(classes, className);
}
};
return startScan(classes, reporter, packages);
}
private static Set<Class<?>> startScan(final Set<Class<?>> classes,
final Reporter reporter, String... packageNames) {
final AnnotationDetector cf = new AnnotationDetector(reporter);
try {
if (packageNames.length == 0) {
// 解决在web容器下扫描不到类的问题.
URL url = Thread.currentThread().getContextClassLoader()
.getResource("");
File file = new File(url.getPath());
File[] files = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.isDirectory() && !pathname.isHidden();
}
});
List<String> fileNames = Lists.newLinkedList();
for (File one : files) {
fileNames.add(one.getName());
}
LOG.debug("san path:{}", fileNames);
cf.detect(ArrayUtils.toStringArray(fileNames));
// FIXME 这里扫描全部可能会有性能问题
// XXX 在java项目中可以扫描到jar文件中的类,在web项目中不行.
cf.detect();
} else {
cf.detect(packageNames);
}
} catch (IOException e) {
LOG.error("scan package error packages:{}",
Arrays.toString(packageNames));
}
return classes;
}
Set<Class<?>> classList=AnnotationClassScanner.scan(AutoBind.class);
拿到了所有包含AutoBind
注解的类就可以进行绑定啦.更多代码见我之前写的一个小项目guice-ext.
总结:有了这个工具就可以做好多好多看起来智能化的东西啦,比如插件,自动发现一些需要的加载类等等,发挥你的想象力吧.
Comments