现在基本上所有的项目都会用到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
Write a Comment