Source: zhenbianshu. Making. IO

The problem

As mentioned in the previous article from the Spring environment to the Spring Cloud configuration, we are using The Spring Cloud for dynamic configuration, which is implemented by injecting the dynamic configuration through @Value into a dynamic configuration Bean. This Bean is annotated as @refreshScope, and these dynamically configured beans are uniformly destroyed after configuration changes.

Spring Cloud’s ContextRefresher then loads the changed configuration into the ApplicationContext as a new Spring Environment, Since Scoped beans are Lazy Init, they will be recreated using a new Environment the next time they are used.

While this dynamic configuration loading process makes our service more flexible, it also brings great risks. First of all, in terms of business, changing the configuration is not as “heavyweight” as going online. There is no need to go back to QA for regression testing, which may cause a series of strange bugs that go undiscovered for a long time. Moreover, Spring Cloud has no “fallback” mechanism, so if there is a problem with the configured data type, The service will not be available.

For this reason, I proposed an issue to Spring Cloud, but the author thinks that the change is too big, and it is not necessary to change.

In fact, I also understand the dilemma of this issue, everyone is responsible for their own configuration changes, even if the framework supports fallback, but the error swallow, configuration changes do not take effect and nothing changes may not meet the user’s expectations. As a result, getting the configuration that the user wants to change as correct as possible is the new goal.

Based on this requirement, I added a dynamically configured validator, but some of the code in the implementation came from Github, so this article summarized the ideas and helped me understand all the code.

The overall train of thought

Since the framework layer can’t do much, my plan is to take these configurations out, construct a separate Java class, and create a new ApplicationContext outside the service to try to initialize a Spring Bean from the constructed Java class. If there is an error during initialization of the Spring Bean, there is a configuration problem.

Dynamic compilation

Construct Java classes through configuration

The first step is to construct a Java class from the.properties file, but the problem is that we don’t know how the configuration will be used, how it will be handled by Spring EL, or what type it will be converted to.

The strategy I’m using here is to add comments to the configuration, which use a format to declare the EL expression and the type of field to be generated. Of course, this implementation is a bit low, and some people suggest putting this information in the key of the configuration item, which will be optimized later.

After parsing each field into the prepared class template, a config.java class string is generated, which is then compiled into bytecode and loaded by Spring into beans.

JavaCompiler

The javax.util.JavaCompiler class is provided for dynamic compilation of Java classes. The complex process of “write file — command line compile — class load — clean file” is eliminated.

The following is an example of a typical JavaCompiler application:

JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
JavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null);
CompilationTask task = javaCompiler.getTask(out, fileManager, diagnosticListener, options, classes, compilationUnits);
task.call();
FileObject outputFile = fileManager.getFileForOutput(null, null, null, null);
outputFile.getCharContent(true);
Copy the code

The process is as follows: JavaCompiler manages input and output files through a JavaFileManager, and submits an asynchronous CompilationTask to the getTask() method for code compilation. JavaCompiler gets the.java file content from the passed compilationUnits via getCharContent(). Write the compiled result to CompiledByteCode by calling the openOutputStream() method.

Delegate pattern

Since the default implementation of JavaCompiler is done in files, which is not what I wanted, I needed input and output to be in memory, so I had to change the implementation of JavaCompiler. JavaCompiler, JavaFileManager, and JavaFileObject(Input/Output) are implemented in delegate mode. Have been the JavaFileManager ForwardingJavaFileManager implementation, JavaFileObject SimpleJavaFileObject implementation, rewrite part after we inherit its realization method.

I refer to the source: github.com/trung/InMem…

Spring Bean instantiation

To the Config class instance into beans, we can defined it in XML, at the end of the compiled to create a simple FileSystemXmlApplicationContext instantiate this Bean in the XML.

Class loader

The first step is to get Spring to load the compiled bytecode, which requires the ClassLoader to work with it. The default implementation of the ClassLoader does not know how to load the bytecode we have compiled in memory, so we have to add a new ClassLoader. The implementation is also very simple, inherit the ClassLoader abstract class, and implement the findClass method.

class MemoryClassLoader extends ClassLoader { @Override protected Class<? > findClass(String name) throws ClassNotFoundException {// put the CompiledByteCode into the classBytes of the classLoader In the field. byte[] buf = classBytes.get(name); if (buf == null) { return super.findClass(name); } return defineClass(name, buf, 0, buf.length); }}Copy the code

Configuration and Implementation

Since the initialization of the Config Bean depends on dynamic configuration, we also need to add these configurations to the Spring environment as well. We know that the Spring environment configuration is composed of multiple PropertySource, just add an implementation to it. You can then call the Refresh () method of the Application to initialize the context, and the Config Bean is set to lazy loading. Don’t forget to get it to be created.

The final code looks like this:

FileSystemXmlApplicationContext applicationContext = new FileSystemXmlApplicationContext();
applicationContext.setClassLoader(memoryClassLoader);
applicationContext.setConfigLocation("classpath*:/test.xml");
Map<String, Object> propertyMap = buildDynamicPropertyMap();
MapPropertySource mapPropertySource = new MapPropertySource("validate_source", propertyMap);
applicationContext.getEnvironment().getPropertySources().addFirst(mapPropertySource);
applicationContext.refresh();
applicationContext.getBean("config");
Copy the code

summary

In the process of completing the small project, I reviewed a lot of knowledge and tried design patterns that are rarely used in business code, which was full of challenges.

Of course, it is not convenient to configure, error message is not clear enough, did not solve the problem of configuring namespace, leave it to later slowly optimize ~

Recent hot articles recommended:

1.1,000+ Java Interview Questions and Answers (2021)

2. I finally got the IntelliJ IDEA activation code thanks to the open source project. How sweet!

3. Ali Mock is officially open source, killing all Mock tools on the market!

4.Spring Cloud 2020.0.0 is officially released, a new and disruptive version!

5. “Java Development Manual (Songshan version)” the latest release, quick download!

Feel good, don’t forget to click on + forward oh!