├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── linkedkeeper
│ └── spi
│ └── ExtensionLoader.java
└── test
├── java
└── demo
│ ├── ExtensionSPITest.java
│ ├── JavaSPITest.java
│ ├── SpringSPITest.java
│ ├── java
│ ├── Bumblebee.java
│ ├── OptimusPrime.java
│ └── Robot.java
│ └── spring
│ ├── AOrderService.java
│ ├── BOrderService.java
│ ├── COrderService.java
│ ├── OrderService.java
│ ├── WareService.java
│ └── WareServiceImpl.java
└── resources
├── META-INF
├── services
│ └── demo.java.Robot
├── spi
│ └── demo.spring.OrderService
└── spring.factories
└── spring.xml
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, Francis Zhang
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # spi-imp
2 | 基于 SPI 原理实现自定义动态 Bean 加载到 Spring 容器的实现逻辑
3 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 | 4.0.0
6 |
7 | spi-imp
8 | com.linkedkeeper
9 | 1.0-SNAPSHOT
10 | jar
11 |
12 | pi-imp
13 |
14 |
15 | UTF-8
16 |
17 |
18 |
19 |
20 | org.slf4j
21 | slf4j-log4j12
22 | 1.7.7
23 |
24 |
25 |
26 | org.springframework
27 | spring-context
28 | 3.2.12.RELEASE
29 |
30 |
31 | org.springframework
32 | spring-test
33 | 3.2.12.RELEASE
34 | test
35 |
36 |
37 | junit
38 | junit
39 | 4.10
40 | test
41 |
42 |
43 |
44 |
45 |
46 |
47 | org.apache.maven.plugins
48 | maven-compiler-plugin
49 | 3.1
50 |
51 | 1.7
52 | 1.7
53 |
54 |
55 |
56 | org.apache.maven.plugins
57 | maven-source-plugin
58 | 2.2.1
59 |
60 |
61 | attach-sources
62 |
63 | jar
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/src/main/java/com/linkedkeeper/spi/ExtensionLoader.java:
--------------------------------------------------------------------------------
1 | package com.linkedkeeper.spi;
2 |
3 | import org.springframework.beans.BeansException;
4 | import org.springframework.beans.factory.support.BeanDefinitionBuilder;
5 | import org.springframework.beans.factory.support.BeanDefinitionRegistry;
6 | import org.springframework.beans.factory.support.DefaultListableBeanFactory;
7 | import org.springframework.beans.factory.support.GenericBeanDefinition;
8 | import org.springframework.context.ApplicationContext;
9 | import org.springframework.context.ApplicationContextAware;
10 | import org.springframework.context.ConfigurableApplicationContext;
11 | import org.springframework.util.StringUtils;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.InputStreamReader;
15 | import java.net.URL;
16 | import java.nio.charset.StandardCharsets;
17 | import java.util.Enumeration;
18 | import java.util.HashMap;
19 | import java.util.Map;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import java.util.concurrent.ConcurrentMap;
22 |
23 | import org.slf4j.Logger;
24 | import org.slf4j.LoggerFactory;
25 |
26 | public class ExtensionLoader implements ApplicationContextAware {
27 |
28 | private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);
29 |
30 | private ApplicationContext context;
31 |
32 | private static final ConcurrentMap, Map> EXTENSION_LOADERS = new ConcurrentHashMap<>();
33 | private static final String SERVICES_DIRECTORY = "META-INF/spi/";
34 |
35 | public Map getExtensionLoader(Class> type) {
36 | if (type == null) {
37 | throw new IllegalArgumentException("Extension type == null");
38 | }
39 | if (!type.isInterface()) {
40 | throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
41 | }
42 | Map loader = EXTENSION_LOADERS.get(type);
43 | if (loader == null) {
44 | synchronized (ExtensionLoader.class) {
45 | loader = EXTENSION_LOADERS.get(type);
46 | if (loader == null) {
47 | EXTENSION_LOADERS.putIfAbsent(type, loadExtensionClass(type.getName()));
48 | loader = EXTENSION_LOADERS.get(type);
49 | }
50 | }
51 | }
52 | return loader;
53 | }
54 |
55 | private Map loadExtensionClass(String type) {
56 | Map extensionClasses = new HashMap<>();
57 | loadDirectory(extensionClasses, SERVICES_DIRECTORY, type);
58 | return extensionClasses;
59 | }
60 |
61 | private void loadDirectory(Map extensionClasses, String dir, String type) {
62 | String fileName = dir + type;
63 | try {
64 | Enumeration urls;
65 | ClassLoader classLoader = findClassLoader();
66 | if (classLoader != null) {
67 | urls = classLoader.getResources(fileName);
68 | } else {
69 | urls = ClassLoader.getSystemResources(fileName);
70 | }
71 | if (urls != null) {
72 | while (urls.hasMoreElements()) {
73 | URL resourcesURL = urls.nextElement();
74 | loadResources(extensionClasses, classLoader, resourcesURL);
75 | }
76 | }
77 | } catch (Throwable t) {
78 | logger.error("Exception occurred when loading extension class (interface: " +
79 | type + ", description file: " + fileName + ").", t);
80 | }
81 | }
82 |
83 | private void loadResources(Map extensionClasses, ClassLoader classLoader, URL resourceURL) {
84 | try {
85 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
86 | String line;
87 | while ((line = reader.readLine()) != null) {
88 | final int ci = line.indexOf('#');
89 | if (ci >= 0) {
90 | line = line.substring(0, ci);
91 | }
92 | line = line.trim();
93 | if (line.length() > 0) {
94 | try {
95 | String name = null;
96 | int i = line.indexOf('=');
97 | if (i > 0) {
98 | name = line.substring(0, i).trim();
99 | line = line.substring(i + 1).trim();
100 | }
101 | if (line.length() > 0) {
102 | loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
103 | }
104 | } catch (Throwable t) {
105 | IllegalStateException e = new IllegalStateException("Failed to load extension class (class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
106 | }
107 | }
108 | }
109 | }
110 | } catch (Throwable t) {
111 | logger.error("Exception occurred when loading extension class (class file: " + resourceURL + ") in " + resourceURL, t);
112 | }
113 | }
114 |
115 | private static ClassLoader findClassLoader() {
116 | return DefaultListableBeanFactory.class.getClassLoader();
117 | }
118 |
119 | private void loadClass(Map extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException {
120 | if (StringUtils.isEmpty(name)) {
121 | throw new IllegalStateException("No such extension name for the class " + name + " in the config " + resourceURL);
122 | }
123 | saveInExtensionClass(extensionClasses, clazz, name);
124 | }
125 |
126 | private void saveInExtensionClass(Map extensionClasses, Class> clazz, String name) {
127 | Object o = extensionClasses.get(name);
128 | if (o == null) {
129 | Object bean = parseClassToSpringBean(name, clazz);
130 | extensionClasses.put(name, bean);
131 | } else {
132 | throw new IllegalStateException("Duplicate extension name " + name + " on " + clazz.getName() + " and " + clazz.getName());
133 | }
134 | }
135 |
136 | private Object parseClassToSpringBean(String name, Class> obj) {
137 | String beanName = obj.getSimpleName().concat(name);
138 |
139 | BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(obj);
140 | GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();
141 | definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_NAME);
142 | getRegistry().registerBeanDefinition(beanName, definition);
143 |
144 | return context.getBean(beanName);
145 | }
146 |
147 | public BeanDefinitionRegistry getRegistry() {
148 | ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context;
149 | return (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
150 | }
151 |
152 | @Override
153 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
154 | this.context = applicationContext;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/test/java/demo/ExtensionSPITest.java:
--------------------------------------------------------------------------------
1 | package demo;
2 |
3 | import com.linkedkeeper.spi.ExtensionLoader;
4 | import demo.spring.OrderService;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.test.context.ContextConfiguration;
9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 |
11 |
12 | @RunWith(SpringJUnit4ClassRunner.class)
13 | @ContextConfiguration({"classpath:spring.xml"})
14 | public class ExtensionSPITest {
15 |
16 | @Autowired
17 | private ExtensionLoader loader;
18 |
19 | @Test
20 | public void sayHello() throws Exception {
21 | OrderService japan = (OrderService) loader.getExtensionLoader(OrderService.class).get("japan");
22 | japan.getOrder("hello.");
23 | OrderService china = (OrderService) loader.getExtensionLoader(OrderService.class).get("china");
24 | china.getOrder("worder.");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/demo/JavaSPITest.java:
--------------------------------------------------------------------------------
1 | package demo;
2 |
3 | import demo.java.Robot;
4 | import org.junit.Test;
5 |
6 | import java.util.ServiceLoader;
7 |
8 | public class JavaSPITest {
9 |
10 | @Test
11 | public void sayHello() throws Exception {
12 | ServiceLoader serviceLoader = ServiceLoader.load(Robot.class);
13 | System.out.println("Java SPI");
14 | for (Robot robot : serviceLoader) {
15 | robot.sayHello();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/java/demo/SpringSPITest.java:
--------------------------------------------------------------------------------
1 | package demo;
2 |
3 |
4 | import demo.spring.OrderService;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.springframework.core.io.support.SpringFactoriesLoader;
8 | import org.springframework.test.context.ContextConfiguration;
9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 |
11 | import java.util.List;
12 |
13 | @RunWith(SpringJUnit4ClassRunner.class)
14 | @ContextConfiguration({"classpath:spring.xml"})
15 | public class SpringSPITest {
16 |
17 | @Test
18 | public void sayHello() throws Exception {
19 | List services = SpringFactoriesLoader.loadFactories(OrderService.class, null);
20 | for (OrderService service : services) {
21 | service.getOrder("worder.");
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/demo/java/Bumblebee.java:
--------------------------------------------------------------------------------
1 | package demo.java;
2 |
3 | public class Bumblebee implements Robot {
4 |
5 | @Override
6 | public void sayHello() {
7 | System.out.println("Hello, I am Bumblebee.");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/demo/java/OptimusPrime.java:
--------------------------------------------------------------------------------
1 | package demo.java;
2 |
3 | public class OptimusPrime implements Robot {
4 |
5 | @Override
6 | public void sayHello() {
7 | System.out.println("Hello, I am Optimus Prime.");
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/demo/java/Robot.java:
--------------------------------------------------------------------------------
1 | package demo.java;
2 |
3 | public interface Robot {
4 |
5 | void sayHello();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/AOrderService.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 |
5 | public class AOrderService implements OrderService {
6 |
7 | @Autowired
8 | private WareService wareService;
9 |
10 | @Override
11 | public void getOrder(String msg) {
12 | wareService.getWare("a order -> " + msg);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/BOrderService.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 |
5 | public class BOrderService implements OrderService {
6 |
7 | @Autowired
8 | private WareService wareService;
9 |
10 | @Override
11 | public void getOrder(String msg) {
12 | wareService.getWare("b order -> " + msg);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/COrderService.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | public class COrderService implements OrderService {
4 |
5 | @Override
6 | public void getOrder(String msg) {
7 | System.out.println("c order -> " + msg);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/OrderService.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | public interface OrderService {
4 |
5 | void getOrder(String msg);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/WareService.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | public interface WareService {
4 |
5 | void getWare(String msg);
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/demo/spring/WareServiceImpl.java:
--------------------------------------------------------------------------------
1 | package demo.spring;
2 |
3 | public class WareServiceImpl implements WareService {
4 |
5 | @Override
6 | public void getWare(String msg) {
7 | System.out.println("getWare print: " + msg);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/test/resources/META-INF/services/demo.java.Robot:
--------------------------------------------------------------------------------
1 | demo.java.OptimusPrime
2 | demo.java.Bumblebee
--------------------------------------------------------------------------------
/src/test/resources/META-INF/spi/demo.spring.OrderService:
--------------------------------------------------------------------------------
1 | china=demo.spring.AOrderService
2 | japan=demo.spring.BOrderService
--------------------------------------------------------------------------------
/src/test/resources/META-INF/spring.factories:
--------------------------------------------------------------------------------
1 | demo.spring.OrderService=demo.spring.COrderService,demo.spring.AOrderService
--------------------------------------------------------------------------------
/src/test/resources/spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------