Skip to content

Commit 988f376

Browse files
committed
Added SpringFactoriesLoader
1 parent 77c9321 commit 988f376

File tree

6 files changed

+327
-0
lines changed

6 files changed

+327
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.core;
18+
19+
import java.io.IOException;
20+
import java.net.URL;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.Collections;
24+
import java.util.Enumeration;
25+
import java.util.List;
26+
import java.util.Properties;
27+
28+
import org.apache.commons.logging.Log;
29+
import org.apache.commons.logging.LogFactory;
30+
31+
import org.springframework.core.io.UrlResource;
32+
import org.springframework.core.io.support.PropertiesLoaderUtils;
33+
import org.springframework.util.Assert;
34+
import org.springframework.util.ClassUtils;
35+
import org.springframework.util.StringUtils;
36+
37+
/**
38+
* General purpose factory loading mechanism.
39+
*
40+
* <p>The {@code SpringFactoriesLoader} loads and instantiates factories of a given type
41+
* from a given file location. If a location is not given, the {@linkplain
42+
* #DEFAULT_FACTORIES_LOCATION default location} is used.
43+
*
44+
* <p>The file should be in {@link Properties} format, where the keys is the fully
45+
* qualified interface or abstract class name, and the value is a comma separated list of
46+
* implementations. For instance:
47+
* <pre class="code">
48+
* example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
49+
* </pre>
50+
* where {@code MyService} is the name of the interface, and {@code MyServiceImpl1} and
51+
* {@code MyServiceImpl2} are the two implementations.
52+
*
53+
* @author Arjen Poutsma
54+
* @since 3.2
55+
*/
56+
public abstract class SpringFactoriesLoader {
57+
58+
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
59+
60+
/** The location to look for the factories. Can be present in multiple JAR files. */
61+
public static final String DEFAULT_FACTORIES_LOCATION = "META-INF/spring.factories";
62+
63+
/**
64+
* Loads the factory implementations of the given type from the default location, using
65+
* the given class loader.
66+
*
67+
* <p>The returned factories are ordered in accordance with the {@link OrderComparator}.
68+
*
69+
* @param factoryClass the interface or abstract class representing the factory
70+
* @param classLoader the ClassLoader to use for loading, can be {@code null} to use the
71+
* default
72+
* @throws IllegalArgumentException in case of errors
73+
*/
74+
public static <T> List<T> loadFactories(Class<T> factoryClass,
75+
ClassLoader classLoader) {
76+
return loadFactories(factoryClass, classLoader, null);
77+
}
78+
79+
/**
80+
* Loads the factory implementations of the given type from the given location, using the
81+
* given class loader.
82+
*
83+
* <p>The returned factories are ordered in accordance with the {@link OrderComparator}.
84+
*
85+
* @param factoryClass the interface or abstract class representing the factory
86+
* @param classLoader the ClassLoader to use for loading, can be {@code null} to
87+
* use the default
88+
* @param factoriesLocation the factories file location, can be {@code null} to use the
89+
* {@linkplain #DEFAULT_FACTORIES_LOCATION default}
90+
* @throws IllegalArgumentException in case of errors
91+
*/
92+
public static <T> List<T> loadFactories(Class<T> factoryClass,
93+
ClassLoader classLoader,
94+
String factoriesLocation) {
95+
Assert.notNull(factoryClass, "'factoryClass' must not be null");
96+
97+
if (classLoader == null) {
98+
classLoader = ClassUtils.getDefaultClassLoader();
99+
}
100+
if (factoriesLocation == null) {
101+
factoriesLocation = DEFAULT_FACTORIES_LOCATION;
102+
}
103+
104+
List<String> factoryNames =
105+
loadFactoryNames(factoryClass, classLoader, factoriesLocation);
106+
107+
if (logger.isTraceEnabled()) {
108+
logger.trace(
109+
"Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
110+
}
111+
112+
List<T> result = new ArrayList<T>(factoryNames.size());
113+
for (String factoryName : factoryNames) {
114+
result.add(instantiateFactory(factoryName, factoryClass, classLoader));
115+
}
116+
117+
Collections.sort(result, new OrderComparator());
118+
119+
return result;
120+
}
121+
122+
private static List<String> loadFactoryNames(Class factoryClass,
123+
ClassLoader classLoader,
124+
String factoriesLocation) {
125+
126+
String factoryClassName = factoryClass.getName();
127+
128+
try {
129+
List<String> result = new ArrayList<String>();
130+
Enumeration urls = classLoader.getResources(factoriesLocation);
131+
while (urls.hasMoreElements()) {
132+
URL url = (URL) urls.nextElement();
133+
Properties properties =
134+
PropertiesLoaderUtils.loadProperties(new UrlResource(url));
135+
String factoryClassNames = properties.getProperty(factoryClassName);
136+
result.addAll(Arrays.asList(
137+
StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
138+
}
139+
return result;
140+
}
141+
catch (IOException ex) {
142+
throw new IllegalArgumentException(
143+
"Unable to load [" + factoryClass.getName() +
144+
"] factories from location [" +
145+
factoriesLocation + "]", ex);
146+
}
147+
148+
149+
}
150+
151+
@SuppressWarnings("unchecked")
152+
private static <T> T instantiateFactory(String instanceClassName,
153+
Class<T> factoryClass,
154+
ClassLoader classLoader) {
155+
try {
156+
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
157+
if (!factoryClass.isAssignableFrom(instanceClass)) {
158+
throw new IllegalArgumentException(
159+
"Class [" + instanceClassName + "] is not assignable to [" +
160+
factoryClass.getName() + "]");
161+
}
162+
return (T) instanceClass.newInstance();
163+
}
164+
catch (ClassNotFoundException ex) {
165+
throw new IllegalArgumentException(
166+
factoryClass.getName() + " class [" + instanceClassName +
167+
"] not found", ex);
168+
}
169+
catch (LinkageError err) {
170+
throw new IllegalArgumentException(
171+
"Invalid " + factoryClass.getName() + " class [" + instanceClassName +
172+
"]: problem with handler class file or dependent class", err);
173+
}
174+
catch (InstantiationException ex) {
175+
throw new IllegalArgumentException(
176+
"Could not instantiate bean class [" + instanceClassName +
177+
"]: Is it an abstract class?", ex);
178+
}
179+
catch (IllegalAccessException ex) {
180+
throw new IllegalArgumentException(
181+
"Could not instantiate bean class [" + instanceClassName +
182+
"Is the constructor accessible?", ex);
183+
}
184+
}
185+
186+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.core;
18+
19+
/**
20+
* Used by {@link SpringFactoriesLoaderTests}
21+
*
22+
* @author Arjen Poutsma
23+
*/
24+
public interface DummyFactory {
25+
26+
String getString();
27+
28+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.core;
18+
19+
/**
20+
* Used by {@link SpringFactoriesLoaderTests}
21+
*
22+
* @author Arjen Poutsma
23+
*/
24+
public class MyDummyFactory1 implements DummyFactory, Ordered {
25+
26+
public int getOrder() {
27+
return 1;
28+
}
29+
30+
public String getString() {
31+
return "Foo";
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.core;
18+
19+
/**
20+
* Used by {@link org.springframework.core.SpringFactoriesLoaderTests}
21+
*
22+
* @author Arjen Poutsma
23+
*/
24+
public class MyDummyFactory2 implements DummyFactory, Ordered {
25+
26+
public int getOrder() {
27+
return 2;
28+
}
29+
30+
public String getString() {
31+
return "Bar";
32+
}
33+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2002-2012 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* 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+
17+
package org.springframework.core;
18+
19+
import java.util.List;
20+
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertTrue;
23+
import org.junit.Test;
24+
25+
/** @author Arjen Poutsma */
26+
public class SpringFactoriesLoaderTests {
27+
28+
@Test
29+
public void loadFactories() {
30+
List<DummyFactory> factories = SpringFactoriesLoader
31+
.loadFactories(DummyFactory.class, null,
32+
"org/springframework/core/springFactoriesLoaderTests.properties");
33+
34+
assertEquals(2, factories.size());
35+
assertTrue(factories.get(0) instanceof MyDummyFactory1);
36+
assertTrue(factories.get(1) instanceof MyDummyFactory2);
37+
}
38+
39+
@Test(expected = IllegalArgumentException.class)
40+
public void loadInvalid() {
41+
SpringFactoriesLoader.loadFactories(String.class, null,
42+
"org/springframework/core/springFactoriesLoaderTests.properties");
43+
}
44+
45+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
org.springframework.core.DummyFactory=org.springframework.core.MyDummyFactory2,org.springframework.core.MyDummyFactory1
2+
java.lang.String=org.springframework.core.MyDummyFactory1

0 commit comments

Comments
 (0)