zjp0633 发表于 2015-7-17 07:53:55

Solr4.8.0源码分析(7)之Solr SPI

Solr4.8.0源码分析(7)之Solr SPI
  查看Solr源码时候会发现,每一个package都会由对应的resources. 如下图所示:

  一时对这玩意好奇了,看了文档以后才发现,这个services就是java SPI机制。首先介绍下java SPI机制,然后再结合Solr谈一下SPI。

1. JAVA SPI
  当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
  基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
  jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
  假设有一个内容搜索系统,分为展示和搜索两个模块。展示和搜索基于接口编程。搜索的实现可能是基于文件系统的搜索,也可能是基于数据库的搜索。实例代码如下:
  Search.java: 搜索接口



1 package search;
2
3 import java.util.List;
4
5 import definition.Doc;
6
7 public interface Search {
8   List search(String keyword);
9 }
  FileSearch.java:文件系统的搜索实现



1 package search;
2
3 import java.util.List;
4
5 import definition.Doc;
6
7 public class FileSearch implements Search {
8
9   @Override
10   public List search(String keyword) {
11         System.out.println("now use file system search. keyword:" + keyword);
12         return null;
13   }
14
15 }
  DatabaseSearch.java



1 package search;
2
3 import java.util.List;
4
5 import definition.Doc;
6
7 public class DatabaseSearch implements Search {
8
9   @Override
10   public List search(String keyword) {
11         System.out.println("now use database search. keyword:" + keyword);
12         return null;
13   }
14
15 }
  SearchTest.java



1 package search;
2
3 import java.util.Iterator;
4 import java.util.ServiceLoader;
5
6 public class SearchTest {
7
8   public static void main(String[] args) {
9         ServiceLoader s = ServiceLoader.load(Search.class);
10         Iterator searchs = s.iterator();
11         if (searchs.hasNext()) {
12             Search curSearch = searchs.next();
13             curSearch.search("test");
14         }
15   }
16 }
  最后创建在META-INF/searvices/search.Search文件。
  当search.Search文件内容是"search.FileSearch"时,程序输出是:
  now use file system search. keyword:test
  当search.Search文件内容是"search.DatabaseSearch"时,程序输出是:
  now use database search. keyword:test
可以看出SearchTest里没有任何和具体实现有关的代码,而是基于spi的机制去查找服务的实现。
  

2. Solr SPI
  以Codec类为例,查看resources/META-INF/services/org.apache.lucene.codecs.Codec:可以看出Codec服务接口具有以下具体的实现类。这就很好的解释了Solrconfig.xml里面的LuceneVersion的配置,也为Lucene的向前兼容提供了保障。



1 #Licensed to the Apache Software Foundation (ASF) under one or more
2 #contributor license agreements.See the NOTICE file distributed with
3 #this work for additional information regarding copyright ownership.
4 #The ASF licenses this file to You under the Apache License, Version 2.0
5 #(the "License"); you may not use this file except in compliance with
6 #the License.You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #Unless required by applicable law or agreed to in writing, software
11 #distributed under the License is distributed on an "AS IS" BASIS,
12 #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #See the License for the specific language governing permissions and
14 #limitations under the License.
15
16 org.apache.lucene.codecs.lucene40.Lucene40Codec
17 org.apache.lucene.codecs.lucene3x.Lucene3xCodec
18 org.apache.lucene.codecs.lucene41.Lucene41Codec
19 org.apache.lucene.codecs.lucene42.Lucene42Codec
20 org.apache.lucene.codecs.lucene45.Lucene45Codec
21 org.apache.lucene.codecs.lucene46.Lucene46Codec
  接下来可以看下Codec服务接口的实现代码



1 package org.apache.lucene.codecs;
2
3 /*
4* Licensed to the Apache Software Foundation (ASF) under one or more
5* contributor license agreements.See the NOTICE file distributed with
6* this work for additional information regarding copyright ownership.
7* The ASF licenses this file to You under the Apache License, Version 2.0
8* (the "License"); you may not use this file except in compliance with
9* the License.You may obtain a copy of the License at
10*
11*   http://www.apache.org/licenses/LICENSE-2.0
12*
13* Unless required by applicable law or agreed to in writing, software
14* distributed under the License is distributed on an "AS IS" BASIS,
15* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16* See the License for the specific language governing permissions and
17* limitations under the License.
18*/
19
20 import java.util.Set;
21 import java.util.ServiceLoader; // javadocs
22
23 import org.apache.lucene.index.IndexWriterConfig; // javadocs
24 import org.apache.lucene.util.NamedSPILoader;
25
26 /**
27* Encodes/decodes an inverted index segment.
28*
29* Note, when extending this class, the name ({@link #getName}) is
30* written into the index. In order for the segment to be read, the
31* name must resolve to your implementation via {@link #forName(String)}.
32* This method uses Java's
33* {@link ServiceLoader Service Provider Interface} (SPI) to resolve codec names.
34*
35* If you implement your own codec, make sure that it has a no-arg constructor
36* so SPI can load it.
37* @see ServiceLoader
38*/
39 public abstract class Codec implements NamedSPILoader.NamedSPI {
40   
41   private static final NamedSPILoader loader =
42   new NamedSPILoader(Codec.class);
43
44   private final String name;
45
46   /**
47    * Creates a new codec.
48    *
49    * The provided name will be written into the index segment: in order to
50    * for the segment to be read this class should be registered with Java's
51    * SPI mechanism (registered in META-INF/ of your jar file, etc).
52    * @param name must be all ascii alphanumeric, and less than 128 characters in length.
53    */
54   protected Codec(String name) {
55   NamedSPILoader.checkServiceName(name);
56   this.name = name;
57   }
58   
59   /** Returns this codec's name */
60   @Override
61   public final String getName() {
62   return name;
63   }
64   /**
65    * 以下几个Format跟Lucene的索引文件格式有关
66    * */
67   /** Encodes/decodes postings */
68   public abstract PostingsFormat postingsFormat();
69
70   /** Encodes/decodes docvalues */
71   public abstract DocValuesFormat docValuesFormat();
72   
73   /** Encodes/decodes stored fields */
74   public abstract StoredFieldsFormat storedFieldsFormat();
75   
76   /** Encodes/decodes term vectors */
77   public abstract TermVectorsFormat termVectorsFormat();
78   
79   /** Encodes/decodes field infos file */
80   public abstract FieldInfosFormat fieldInfosFormat();
81   
82   /** Encodes/decodes segment info file */
83   public abstract SegmentInfoFormat segmentInfoFormat();
84   
85   /** Encodes/decodes document normalization values */
86   public abstract NormsFormat normsFormat();
87
88   /** Encodes/decodes live docs */
89   public abstract LiveDocsFormat liveDocsFormat();
90   
91   /**
92    * 根据名字在已有的Codec实例中寻找符合
93    * */
94   /** looks up a codec by name */
95   public static Codec forName(String name) {
96   if (loader == null) {
97       throw new IllegalStateException("You called Codec.forName() before all Codecs could be initialized. "+
98         "This likely happens if you call it from a Codec's ctor.");
99   }
100   return loader.lookup(name);
101   }
102   
103   /**
104    * 返回有效的Codecs实例
105    * */
106   /** returns a list of all available codec names */
107   public static Set availableCodecs() {
108   if (loader == null) {
109       throw new IllegalStateException("You called Codec.availableCodecs() before all Codecs could be initialized. "+
110         "This likely happens if you call it from a Codec's ctor.");
111   }
112   return loader.availableServices();
113   }
114   
115   /**
116    * 更新Codec实例列表,Codec实例列表只能添加,不能删除与更改。
117    * */
118   /**
119    * Reloads the codec list from the given {@link ClassLoader}.
120    * Changes to the codecs are visible after the method ends, all
121    * iterators ({@link #availableCodecs()},...) stay consistent.
122    *
123    * NOTE: Only new codecs are added, existing ones are
124    * never removed or replaced.
125    *
126    * This method is expensive and should only be called for discovery
127    * of new codecs on the given classpath/classloader!
128    */
129   public static void reloadCodecs(ClassLoader classloader) {
130   loader.reload(classloader);
131   }
132   
133   /**
134    * 默认为Lucene46,也就是说默认调用的是org.apache.lucene.codecs.lucene46.Lucene46Codec
135    * */
136   private static Codec defaultCodec = Codec.forName("Lucene46");
137   
138   /**
139    * 返回默认的Codec实例
140    * */
141   /** expert: returns the default codec used for newly created
142    *{@link IndexWriterConfig}s.
143    */
144   // TODO: should we use this, or maybe a system property is better?
145   public static Codec getDefault() {
146   return defaultCodec;
147   }
148   
149   /**
150    * 设置默认的Codec实例
151    * */
152   /** expert: sets the default codec used for newly created
153    *{@link IndexWriterConfig}s.
154    */
155   public static void setDefault(Codec codec) {
156   defaultCodec = codec;
157   }
158
159   /**
160    * returns the codec's name. Subclasses can override to provide
161    * more detail (such as parameters).
162    */
163   @Override
164   public String toString() {
165   return name;
166   }
167 }
  代码比较简单明了,接下来再看下NamedSPILoader.NamedSPI,它封装了JAVA SPI的实现:



1 package org.apache.lucene.util;
2
3 /*
4* Licensed to the Apache Software Foundation (ASF) under one or more
5* contributor license agreements.See the NOTICE file distributed with
6* this work for additional information regarding copyright ownership.
7* The ASF licenses this file to You under the Apache License, Version 2.0
8* (the "License"); you may not use this file except in compliance with
9* the License.You may obtain a copy of the License at
10*
11*   http://www.apache.org/licenses/LICENSE-2.0
12*
13* Unless required by applicable law or agreed to in writing, software
14* distributed under the License is distributed on an "AS IS" BASIS,
15* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16* See the License for the specific language governing permissions and
17* limitations under the License.
18*/
19
20 import java.util.Collections;
21 import java.util.Iterator;
22 import java.util.Map;
23 import java.util.LinkedHashMap;
24 import java.util.Set;
25 import java.util.ServiceConfigurationError;
26
27 /**
28* Helper class for loading named SPIs from classpath (e.g. Codec, PostingsFormat).
29* @lucene.internal
30*/
31 public final class NamedSPILoader implements Iterable {
32
33   /**
34    * SPI service Map,存放服务对应的实例类。
35    * */
36   private volatile Map services = Collections.emptyMap();
37   private final Class clazz;
38
39   public NamedSPILoader(Class clazz) {
40   this(clazz, Thread.currentThread().getContextClassLoader());
41   }
42   
43   public NamedSPILoader(Class clazz, ClassLoader classloader) {
44   this.clazz = clazz;
45   // if clazz' classloader is not a parent of the given one, we scan clazz's classloader, too:
46   final ClassLoader clazzClassloader = clazz.getClassLoader();
47   if (clazzClassloader != null && !SPIClassIterator.isParentClassLoader(clazzClassloader, classloader)) {
48       reload(clazzClassloader);
49   }
50   reload(classloader);
51   }
52   
53   /**
54    * 更新SPI MAP services。遍历META-INF/services文件,如果services MAP没有该实例,则新建实例,并放入services MAP
55    * */
56   /**
57    * Reloads the internal SPI list from the given {@link ClassLoader}.
58    * Changes to the service list are visible after the method ends, all
59    * iterators ({@link #iterator()},...) stay consistent.
60    *
61    * NOTE: Only new service providers are added, existing ones are
62    * never removed or replaced.
63    *
64    * This method is expensive and should only be called for discovery
65    * of new service providers on the given classpath/classloader!
66    */
67   public synchronized void reload(ClassLoader classloader) {
68   final LinkedHashMap services = new LinkedHashMap(this.services);
69   final SPIClassIterator loader = SPIClassIterator.get(clazz, classloader);
70   while (loader.hasNext()) {
71       final Class
页: [1]
查看完整版本: Solr4.8.0源码分析(7)之Solr SPI