Cabbage Java self study room covers core knowledge

1. Java – SPI mechanisms

SPI (Service Provider Interface) is a set of interfaces provided by Java to be implemented or extended by third parties. It can be used to enable framework extensions and replace components. The PURPOSE of SPI is to find Service implementations for these extended apis.

Common SPIs are JDBC, JCE, JNDI, JAXP, and JBI. Interfaces to these SPIs are provided by Java core libraries, and implementations of SPI are included in the CLASSPATH as jar packages that Java applications depend on.

1.1. Basic ideas

The Java SPI is actually a dynamic loading mechanism implemented by a combination of “interface-based programming + policy pattern + configuration files”.

Each abstraction of system design, there are often many different implementation schemes. In object-oriented design, it is generally recommended to program based on interface between modules, not hard coding between modules. Once a specific implementation class is involved in the code, it violates the principle of pluggability, and if an implementation needs to be replaced, the code needs to be changed.

A service discovery mechanism is needed in order to realize the dynamic specification during module assembly. The Java SPI provides a mechanism for finding a service implementation for an interface. Similar to the IOC idea of moving control of assembly out of the program, this mechanism is especially important in modular design. So the core idea of SPI is decoupling.

1.2. Usage scenarios

Application Programming Interfaces (apis) In most cases, the implementer formulates the Interface and implements the Interface. The caller only relies on the Interface invocation and does not have the right to choose different implementations. In terms of users, apis are used directly by application developers.

The Service Provider Interface (SPI) allows the caller to formulate the Interface specification and provide it to the external for implementation. The caller selects the required external implementation during invocation. In terms of users, SPI is used by framework extenders.

1.3. Specific provisions

The convention of the Java SPI is that when a service provider provides an implementation of the service interface, it creates a file named after the service interface in the META-INF/services/ directory of the JAR package. The file contains the implementation class of the service interface. When an external application assembs the module, it can find the implementation class name in the meta-INF /services/ jar file and load the instantiation to complete the module injection. Based on such a convention, it is easy to find the implementation class of the service interface without having to specify it in the code. The JDK provides a utility class for service implementation lookup: java.util.Serviceloader.

1.4. Code examples

Since it is an SPI, the interface must be defined first. The second is to define the implementation class of the interface.

  1. Create a Promotion interface:
public interface Promotion {

    BigDecimal getDiscount();

}
Copy the code
  1. Create a new GrouponPromotion implementation class:
public class GrouponPromotion implements Promotion { @Override public BigDecimal getDiscount() { System.out.println(" coupon discount $1 "); return new BigDecimal(1); }}Copy the code
  1. Create a new SeckillPromotion implementation class:
public class SeckillPromotion implements Promotion { @Override public BigDecimal getDiscount() { System.out.println(" seckill binary "); return new BigDecimal(2); }}Copy the code
  1. Create a meta-INF /services/ folder under resources:

  1. To create a new file called interface name com. Yly. Region. Spi. Promotion, content is as follows:
com.yly.region.spi.impl.GrouponPromotion
com.yly.region.spi.impl.SeckillPromotion
Copy the code
  1. Create a new Test class, Test, and run it with java.util.ServiceLoader.
public class Test { public static void main(String[] agrs) { ServiceLoader<Promotion> loaders = ServiceLoader.load(Promotion.class); for (Promotion promotion : loaders) { promotion.getDiscount(); }}}Copy the code
  1. You can see the result:
Group purchase discount 1 second kill 2Copy the code

2. SPI application and principle of Dubbo

Essentially, the Service Provider Interface (SPI) configures the fully qualified name of the Interface implementation class in a file. The Service loader reads the configuration file and loads the implementation class. This lets you dynamically replace the implementation class for the interface at run time.

To learn Dubbo source, SPI mechanism must understand. Now that we have seen the usage of Java SPI, let’s look at the usage of Dubbo SPI, and then analyze the source code of Dubbo SPI.

2.1. GetExtensionLoader ()

In the Dubbo SPI sample method, we first get an instance of the ExtensionLoader interface through the getExtensionLoader() method, GetExtensionLoader (); getExtension(); getExtensionLoader();

/** * {@link org.apache.dubbo.rpc.model.ApplicationModel}, {@code DubboBootstrap} and this class are * at present designed to be singleton or static (by itself totally static or uses some static fields). * So the instances returned from them are of process or classloader scope. If you want to support * multiple dubbo servers in a single process, you may need to refactor these three classes. * <p> * Load dubbo extensions * <ul> * <li>auto inject dependency extension </li> * <li>auto wrap extension in wrapper </li> * <li>default extension is an adaptive instance</li> * </ul> * * @ see < a href = "http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#Service%20Provider" > Service Provider in Java 5</a> * @see org.apache.dubbo.common.extension.SPI * @see org.apache.dubbo.common.extension.Adaptive * @see org.apache.dubbo.common.extension.Activate */ public class ExtensionLoader<T> { // ... /** * Extended class loader cache, which is the extension point ExtendsLoader instance cache; Key = extension interface value= extension Class loader */ private static final ConcurrentMap<Class<? >, ExtensionLoader<? >> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); @SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { // If (type == null) {throw new IllegalArgumentException("Extension type == null"); } // Check whether the type class passed is an interface if (! type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!" ); } // Check if the class passed in has an @spi annotation if (! withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!" ); ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); If (loader == null) {// No new ExtensionLoader instance, PutIfAbsent (type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }}Copy the code

GetExtensionLoader () validates the interface passed in, including the @spi annotation validation, which is why @spi is added to the interface. Then it retrieves the ExtensionLoader of this interface type from the EXTENSION_LOADERS cache. If not, it creates an ExtensionLoader of this interface type into the cache and returns the ExtensionLoader.

public class ExtensionLoader<T> { // ... private ExtensionLoader(Class<? > type) { this.type = type; // Type is usually not ExtensionFactory, AdaptiveExtensionFactory objectFactory = (type == extensionFactory.class? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }}Copy the code

Note that the ExtensionLoader object is created as follows: ExtensionLoader. GetExtensionLoader () to obtain ExtensionFactory interface development, through getAdaptiveExtension from expanding class for target class. It sets the objectFactory constant corresponding to this interface to AdaptiveExtensionFactory. Since the Adaptive annotation is added to the AdaptiveExtensionFactory class, the reason for the AdaptiveExtensionFactory annotation will be explained in a later article, and objectFactory will also be used later.

2.2. GetExtension ()

When through ExtensionLoader. GetExtensionLoader () apply to the interface of Loader Loader, again through the getExtension () method to obtain need to expand the class object. The entire execution process of this method is shown in the figure below:

Refer to the execution flow chart, the source code of the extended class object is as follows:

public class ExtensionLoader<T> { // ... /** * extension point instance cache key= extension point name, Value = Extend instance Holder instance */ private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>(); /** * Find the extension with the given name. If the specified name is not found, then {@link IllegalStateException} * will be thrown. */ @SuppressWarnings("unchecked") public T getExtension(String name) { return getExtension(name, true); 1. Check if * 2 exists in the cache. @param Name Extension key * @return */ public T getExtension(String name, boolean wrap) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) {// Get the default extension implementation class on the @spi annotation, such as @spi (" XXX ") return getDefaultExtension(); } final Holder<Object> Holder = getOrCreateHolder(name); Object instance = holder.get(); If (instance == null) {synchronized (holder) {instance = holder.get(); If (instance == null) {// Create an extension instance instance = createExtension(name, wrap); // Set the instance to holder.set(instance); } } } return (T) instance; } /** * get or create a Holder Object */ private Holder<Object> getOrCreateHolder(String name) {// First get the Holder Object from the extended instance cache with the extension name Holder<Object> holder = cachedInstances.get(name); If (holder = = null) {/ / without access to the new deposit an empty holder instance cache cachedInstances. PutIfAbsent (name, the new holder < > ()); holder = cachedInstances.get(name); } return holder; }}Copy the code

The logic of the above code is relatively simple: first check the cache and create the extended object if the cache is not hit. Extensive extension point caching is included in Dubbo. This is the typical use of space for time. And one of the reasons for Dubbo’s strong performance, including

  • Extension point Class cache. When Dubbo SPI obtains extension points, it preferentially reads them from the cache. If they do not exist in the cache, it loads the configuration file.
  • Extended point instance caching, Dubbo caches not only classes but also instances of classes. Each time an instance is fetched from the cache, it is loaded from the configuration, instantiated, and cached in memory.

2.3. CreateExtension ()

Let’s look at the process of creating an extended object:

public class ExtensionLoader<T> { // ... /** * The extended instance is cached in memory; Key = extension class; Value = Extended Class */ private static final ConcurrentMap<Class<? >, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64); /** * Create an extension class instance, including the following steps * 1. Get all the extension classes through getExtensionClasses, and get the map of the extension class from the configuration file * 2. Create an extension through reflection * 3. Inject dependencies into the extension * 4. Wrap the extension object in the corresponding Wrapper object (AOP) * @param name The key of the extension class in the configuration file you need to obtain * @return Extension class instance */ @SuppressWarnings("unchecked") private T CreateExtension (String name, Boolean wrap) {createExtension(String name, Boolean wrap) {Class<? > clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try {T instance = (T) extension_instance.get (clazz); PutIfAbsent (clazz, clazz.newinstance ()); // If (instance == null) {// If (instance == null) {// If (instance == null) {// If (instance == null) {// If (instance == null) {// If (instance == null) { Instance = (T) extension_instance.get (clazz); } // Inject a dependency into the instance, using setter methods to automatically inject the corresponding property instance injectExtension(instance); List<Class<? >> wrapperClassesList = new ArrayList<>(); if (cachedWrapperClasses ! = null) { wrapperClassesList.addAll(cachedWrapperClasses); wrapperClassesList.sort(WrapperComparator.COMPARATOR); Collections.reverse(wrapperClassesList); } the if (CollectionUtils isNotEmpty (wrapperClassesList)) {/ / cycle to create Wrapper instance, form a Wrapper packaging chain for (Class <? > wrapperClass : wrapperClassesList) { Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class); if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && ! ArrayUtils.contains(wrapper.mismatches(), name))) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}}} // Initialize the instance and return initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); }}}Copy the code

The steps for creating extended class objects are as follows:

  1. Load all extension classes from the configuration file using getExtensionClasses, and get the target extension class by name.
  2. Create extended objects through reflection;
  3. Inject dependencies into extended objects;
  4. Wrap the extension object in the corresponding Wrapper object.

The third and fourth steps are the concrete implementation of Dubbo IOC and AOP. Let’s start by focusing on the logic of the getExtensionClasses() method.

2.4. GetExtensionClasses ()

Before obtaining the extension class by name, the mapping map between the extension name and the extension class needs to be resolved according to the configuration file, and then the corresponding extension class can be extracted from the map according to the name of the extension. GetExtensionClasses () ¶

public class ExtensionLoader<T> { // ... /** * extension point Class cache key= extension, value= corresponding Class object */ private final Holder<Map<String, Class<? >>> cachedClasses = new Holder<>(); /** * private map <String, Class<? >> getExtensionClasses() {// Get loaded extension points from cache class Map<String, class <? >> classes = cachedClasses.get(); If (classes == null) {synchronized (cachedClasses) {classes = cachedclasses.get (); If (classes == null) {// loadExtensionClasses = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }}Copy the code

LoadExtensionClasses () is used to load extended classes if the cache is not hit. The cache avoids multiple reads of the configuration file.

2.5. LoadExtensionClasses ()

The loadExtensionClasses() method loads the configuration file.

public class ExtensionLoader<T> { // ... private static volatile LoadingStrategy[] strategies = loadLoadingStrategies(); /** * Load all {@link Prioritized prioritized} {@link LoadingStrategy Loading Strategies} via {@link ServiceLoader} * * @return non-null * @since 2.7.7 */ private static LoadingStrategy[] loadLoadingStrategies() {return stream(load(LoadingStrategy.class).spliterator(), false) .sorted() .toArray(LoadingStrategy[]::new); } /** * synchronized in getExtensionClasses */ private Map<String, Class<? >> loadExtensionClasses() { cacheDefaultExtensionName(); Map<String, Class<? >> extensionClasses = new HashMap<>(); // load the configuration file in the specified folder according to the LoadingStrategy. strategies) { loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); } return extensionClasses; }}Copy the code

The loadExtensionClasses() method does two things altogether:

  1. First the method call cacheDefaultExtensionName () to the SPI annotations parsing, acquire and cache interface @ SPI annotations on expanding class in cachedDefaultName by default.
  2. Then call the loadDirectory() method to load the specified folder configuration file.

SPI annotation parsing process is relatively simple, the source code is as follows. Only one default extension class is allowed.

public class ExtensionLoader<T> { // ... / * * * extract and cache the default extension name if the exists * / private void cacheDefaultExtensionName () {/ / for SPI annotations, The type variable here is passed in when the getExtensionLoader method is called, representing the interface class final SPI defaultAnnotation = type.getannotation (spi.class); if (defaultAnnotation == null) { return; } String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); // Check whether the SPI annotation content is valid (at most one default implementation class), If (names.length > 1) {throw new IllegalStateException("More than 1 default extension name on extension "+ type.getName() + ": " + Arrays.toString(names)); } // Set the default extension class name if (names.length == 1) {cachedDefaultName = names[0]; }}}}Copy the code

LoadExtensionClasses () loads configuration files from meta-INF /dubbo/internal/, meta-INF /dubbo/, meta-INF /services/. The method source code is as follows:

public class ExtensionLoader<T> { // ... private void loadDirectory(Map<String, Class<? >> extensionClasses, String dir, String type) { loadDirectory(extensionClasses, dir, type, false, false); } /** * load configuration file contents * @param extensionClasses extension class map * @param dir folder path * @param type interface name * @param ExtensionLoaderClassLoaderFirst whether to load the first ExtensionLoader this * / private void loadDirectory (Map < String, Class <? >> extensionClasses, String dir, String type, boolean extensionLoaderClassLoaderFirst, boolean overridden, String... ExcludedPackages) {// fileName = folder path + type fully qualified fileName = dir + type; try { Enumeration<java.net.URL> urls = null; ClassLoader ClassLoader = findClassLoader(); // try to load from ExtensionLoader's ClassLoader first if (extensionLoaderClassLoaderFirst) { // To obtain load ExtensionLoader. The class of this class class loader this extensionLoaderClassLoader = ExtensionLoader. Class. GetClassLoader (); / / if extensionLoaderClassLoaderFirst = true, and the two different class loaders, / / is preferred to use extensionLoaderClassLoader if (this. GetSystemClassLoader ()! = extensionLoaderClassLoader) { urls = extensionLoaderClassLoader.getResources(fileName); }} / / according to the file name to load all the files with the same if (urls = = null | |! urls.hasMoreElements()) { if (classLoader ! = null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } } if (urls ! = null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); LoadResource (extensionClasses, classLoader, resourceURL, overridden, excludedPackages); } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); }}}Copy the code

First, find the configuration file in the folder. The file name must be the fully qualified name of the interface. Use the class loader to get the file resource link, and then parse the configured implementation classes in the configuration file to add them to extensionClasses. Let’s continue looking at how loadResource() loads resources:

public class ExtensionLoader<T> { // ... private void loadResource(Map<String, Class<? >> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL, boolean overridden, String... excludedPackages) { try { try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) { String line; String clazz = null; While ((line = reader.readline ())! Final int ci = line.indexof ('#'); final int ci = line.indexof ('#'); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); If (I > 0) {name = line.subString (0, I).trim(); clazz = line.substring(i + 1).trim(); } else { clazz = line; } if (StringUtils.isNotEmpty(clazz) && ! IsExcluded (clazz, excludedPackages)) {// Load classes by reflection, LoadClass (extensionClasses, resourceURL, class.forname (clazz, true, classLoader), name, overridden); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } } catch (Throwable t) { logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); }}}Copy the code

The loadResource() method is used to read and parse the configuration file. It reads the configuration file line by line, delimited by equals =, intercepts the key and value, and loads the class through reflection. Finally, it loads the extension point of the implementation class into the map using the loadClass() method. And cache the classes loaded into it. The loadClass() method is implemented as follows:

public class ExtensionLoader<T> { // ... /** * Load the extension point implementation class class into the map, Caching classes * cachedAdaptiveClass, cachedWrapperClasses, cachedNames, etc. * @param extensionClasses loads containers for config file classes * @param resourceURL config file resourceURL * @param clazz extension point implementation class * @param name extension point implementation class name, Throws NoSuchMethodException */ private void loadClass(Map<String, Class<? >> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, Throws NoSuchMethodException {// Check whether the configured implementation class implements the Type interface if (! type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error occurred when loading extension class (interface:  " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface."); } // Check whether the target class has Adaptive annotations, indicating that the class is an Adaptive implementation class. Cache to cachedAdaptiveClass if (clazz. IsAnnotationPresent (Adaptive. Class)) {/ / testing are clazz Wrapper type, judgment is whether there is parameter for the interface class constructor, Cache to cachedWrapperClasses cacheAdaptiveClass(Clazz, overridden); } else if (isWrapperClass(clazz)) { cacheWrapperClass(clazz); } else {// Check if clazz has a default constructor and throw an exception clazz.getconstructor (); // If the name of the key in the configuration file is empty, try to get the name from the Extension annotation, Or use a lowercase class name as name if (stringutils.isempty (name)) {name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); }} // Split name with a comma into a String array String[] names = name_separate.split (name); CacheActivateClass (clazz, names[0]); if (arrayUtils.isnotempty (names)) {// If (arrayUtils.isnotempty (names)) {// If (arrayUtils.isnotempty (names)) {cacheActivateClass(clazz, names[0]); For (String n: names) {// Cache extension point implementation class and extension point name mapping cacheName(clazz, n); SaveInExtensionClass (extensionClasses, clazz, n, overridden); } } } } }Copy the code

LoadClass () implements the classification caching function of extension points, such as wrapper classes, adaptive extension point implementation classes, and common extension point implementation classes, respectively.

2.6. @ the Adaptive

Note that the Adaptive extension point implementation class @adaptive annotation, the source code of the annotation is as follows:

/** * Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance. * * @see ExtensionLoader * @see URL */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { /** * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed * in the URL, and the parameter names are given by this method. * <p> * If the specified parameters are not found from {@link URL}, then the default extension will be used for * dependency injection (specified in its interface's {@link SPI}). * <p> * For example, given <code>String[] {"key1", "key2"}</code>: * <ol> * <li>find parameter 'key1' in URL, use its value as the extension's name</li> * <li>try 'key2' for extension's name if 'key1' is not found (or its value is  empty) in URL</li> * <li>use default extension if 'key2' doesn't exist either</li> * <li>otherwise, throw {@link IllegalStateException}</li> * </ol> * If the parameter names are empty, then a default parameter name is generated from interface's * class name with the rule: divide classname from capital char into several parts, and separate the parts with * dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is * <code>String[] {"yyy.invoker.wrapper"}</code>. * * @return parameter names in URL */ String[] value() default {}; }Copy the code

The annotation determines which adaptive extension class is injected. The target extension class is determined by the parameter in the URL. The parameter key in the URL is given by the value of the annotation, and the value of the key is the name of the target extension class.

  • If there are multiple values in the annotation, the URL is searched for the corresponding key according to the subscript from small to large. Once found, the value of the key is used as the target extension class name.
  • If none of these values have corresponding keys in the URL, use the default values on the SPI.

The @Adaptive annotation can be applied to classes and methods. In most cases, the annotation is applied to methods. When the @Adaptive annotation is applied to classes, Dubbo will not generate proxy classes for this class. When annotations are on a method (interface method), Dubbo generates a proxy class for that method. Adaptive annotation in the interface method, it means that the extended loading logic needs to be automatically generated by the framework. Annotations on the class indicate that the extended loading logic is manually coded.

LoadClass () scans the classes above. In Dubbo, there are only two classes annotated by @Adaptive, which are AdaptiveCompiler and AdaptiveExtensionFactory. The loadClass() method sets the cache to cacheAdaptiveClass, causing the interface’s cacheAdaptiveClass not to be empty. This extension class is used by default and has the highest priority.

Back to the main thread, when the loadClass() method is executed, all the extended classes in the configuration file have been loaded into the map, and at this point, the analysis of the cache class loading process is complete.

2.7. Dubbo AOP

When the injectExtension(T instance) method is executed, the Wrapper wrapper is executed in createExtension(String Name). Similar to AOP in Spring, Dubbo uses the decorator pattern.

if (wrap) { List<Class<? >> wrapperClassesList = new ArrayList<>(); if (cachedWrapperClasses ! = null) { wrapperClassesList.addAll(cachedWrapperClasses); wrapperClassesList.sort(WrapperComparator.COMPARATOR); Collections.reverse(wrapperClassesList); } if (CollectionUtils.isNotEmpty(wrapperClassesList)) { for (Class<? > wrapperClass : wrapperClassesList) { Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class); if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && ! ArrayUtils.contains(wrapper.mismatches(), name))) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); }}}}Copy the code

CachedWrapperClasses (” ++ “, “++”, “++”, “++”, “++”, “++”); It is cached to cachedWrapperClasses.

Perform wrapperClass. GetConstructor (type). NewInstance (instance) will obtain the wrapper class constructor, method of the parameters is the interface type, and through reflection generated a contains the packing of the expanding class instance object, The wrapper object’s dependencies are then injected through injectExtension. This loop results in a Wrapper chain. Note here that wrapper classes that are later in the configuration file are wrapped in relative outer layers.

Let’s take a similar approach to Dubbo AOP and modify the code in Section 1 to see what happens:

  1. Add POM file dependencies first:
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> The < version > 2.7.9 < / version > < / dependency >Copy the code
  1. Modify the Promotion interface with the @spi tag:
import org.apache.dubbo.common.extension.SPI;
import java.math.BigDecimal;

@SPI
public interface Promotion {

    BigDecimal getDiscount();

}
Copy the code
  1. Create a new PromotionWrapper class:
public class PromotionWrapper implements Promotion { private Promotion promotion; public PromotionWrapper(Promotion promotion) { this.promotion = promotion; } @override public BigDecimal getDiscount() {system.out.println (" before operation "); BigDecimal result = promotion.getDiscount(); System.out.println(" after operation "); return result; }}Copy the code
  1. Modify the file com. Yly. Region. Spi. Promotion, content is as follows:
groupon=com.yly.region.spi.impl.GrouponPromotion
seckill=com.yly.region.spi.impl.SeckillPromotion
com.yly.region.spi.PromotionWrapper
Copy the code
  1. Create a new test class, TestAOP, and run it using Dubbo’s ExtensionLoader.
public class TestAOP { public static void main(String[] agrs) { ExtensionLoader<Promotion> extensionLoader = ExtensionLoader.getExtensionLoader(Promotion.class); Promotion promotion = extensionLoader.getExtension("groupon"); System.out.println(promotion.getDiscount()); }}Copy the code
  1. You can see the result:
Group purchase discount before operation 1 yuan after operationCopy the code

The results are as expected, implementing the Wrapper class’s faceted functionality.