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]