the meaning of the frame
For programmers, we usually know many concepts, such as components, modules, systems, frameworks, architectures, etc., and in this article we focus on frameworks .
- A framework is essentially a collection of practical experiences. That is, the actual combat experience accumulated by the predecessors in the actual development process has been accumulated into a set of practical tools to avoid you repeating the creation of wheels during the development process, especially to help you shield the scenes or problems that you can encounter in daily life. , the meaning of the framework is to shield the basic complexity of development, shield such common things, and establish strict coding standards, so that framework users can use it out of the box, and only need to focus on the difference, that is, the realization of the business level. In short, frameworks do only one thing, and that is to simplify development . Then on this basis, some security, efficiency, performance, elasticity, management, expansion, decoupling, etc. may be considered.
Understanding Spring Core
As a framework, Spring's purpose is also: simplifying development , but in the process of simplifying development, Spring has made a special design, that is, Bean management , which is also the core of Spring's design, and the design of Bean life cycle management is cleverly decoupled The relationship between beans.
So the core features of Spring are decoupling and simplification .
The Spring Framework diagram is very clear, basically depicting the core of the Spring Framework:
- kernel
- Epitaxy
Simply put, Spring has designed a core container Core Container, which mainly manages the Bean life cycle, and then introduces Core, Context, SpEL and other tools into the core container in order to serve these business beans. Then on the basis of the core container, in order to integrate more capabilities, for example, in order to expand the data access capabilities, JDBC, ORM, OXM, JMS, Transactions, etc. are added, and in order to expand the Web capabilities, WebSocket, Servlet, Web, Portlet, etc. are added. , In order to integrate the use of RequestMapping or Servlet into the business Bean, AOP is introduced, including the introduction (finally providing) enhancements such as Aspects, Instrumentation, and Messageing.
So take a closer look, Spring is to integrate capabilities like database access, Web support, caching, messaging, etc. into business beans, and provide some testing support. In summary, there are two points to understand Spring:
- Bean management: Decoupling Bean relationships. It is understood as the core, from the definition, creation, management of Beans, etc., this is the business Bean.
- Functional enhancements: decoupling functions, declarative simplification. It is understood as an extension. On the basis of business beans, the ability to access libraries is required, that is, function enhancement.
It basically embodies two core features, one is decoupling and the other is simplification .
Bean management itself is doing decoupling and decoupling. This decoupling refers to the association between beans and beans. Beans are connected to each other through interface protocols. As for how many implementation classes each interface has, it will not If there is any impact, only a single point channel is reserved between beans, isolated from each other through interfaces, and the relationship is handed over to Spring management, which avoids some coupling between the implementation class and the implementation class, even if the method is increased or decreased, and the reference is changed. not to contaminate each other.
Functional enhancement itself is doing simplification , such as declarative simplification, like declarative programming, the user only needs to tell the framework what he wants, regardless of how the framework is implemented. In addition, in terms of simplification, there is also convention over configuration (of course, this is exactly the design in SpringBoot). Convention over configuration is actually agreed that there is no need to do complicated configuration, for example, what component or capability do you introduce like redis or kafka, you don't need to configure in advance, because springboot has been configured for you by default, out of the box.
So how to understand the Spring framework features? Just decoupling and simplifying .
As for SpringBoot, a simple understanding is that an SPI extensibility mechanism and version management are added on the basis of the Spring framework, making it easier to use and simplifying upgrades.
As for SpringCloud, a simple understanding is that because SpringBoot's dependencies can be well managed and extensions can be pluggable, so on the basis of SpringBoot, a lot of capabilities related to the microservice architecture are integrated, such as integrating many components. With SpringCloud full ecology.
After a basic understanding of Spring features, we return to the core design of Spring, IoC and AOP .
IoC
We said that one of the features of Spring is decoupling , so what exactly is decoupling?
Inversion of Control ( IoC for short) is a design principle in object-oriented programming that can be used to reduce the coupling between computer codes. The most common way is called Dependency Injection ( DI ), and there is another way called "Dependency Lookup" (Dependency Lookup, used by EJB and Apache Avalon). With inversion of control, objects are passed references to the objects they depend on when they are created by an external entity that governs all objects in the system. It can also be said that dependencies are injected into objects.
In short, the original mutual call between beans and beans has become unified deployment by the IoC container. If the IoC container is not used to manage the business beans uniformly, when your application is deployed, modified, and iterated, the business beans will invade the code implementation and call each other.
So the question is, do all systems need to introduce IOC?
The IoC container works for iteration . If your application does not have iteration , that is, the system is unchanged for ten thousand years, then there is no need to introduce IoC , because every time you introduce a technology, it will inevitably increase the complexity, so The additional introduction of IoC will also increase the complexity of your overall application, so if there is no iteration , you can directly write Class A to refer to Class B, and Class B to refer to Class C without introducing IoC . Be sure to understand what problem is behind each technology, and keep two principles in mind when doing architecture design: appropriate and simple . Of course, most of our applications are actually iterative , and there may be changes in class implementation, mutual references, and even interface protocols, so it is generally appropriate to introduce IoC (if the interface protocol changes, that is, parameters or return values) If there is a change, it still needs to change the code between classes).
Specifically, IoC is equivalent to handing over the creation process of Bean instances to Spring management. Whether it is through XML, JavaConfig, or annotation methods, the final work of instantiation is handed over to Spring, and then the beans call each other through interfaces. The instantiation process involves injection . No matter what method is used to instantiate a bean, there are two types of injection :
- Setter injection : Set by setter, which happens after the object is instantiated .
- Constructor injection : With constructor injection, parameters/instances must be prepared before the object is instantiated .
setter injection:
- It is more similar to the traditional JavaBean writing, and it is easier for program developers to understand and accept. Setting dependencies through setter methods is more intuitive and natural.
- For complex dependencies, if constructor injection is used, the constructor will be too bloated and difficult to read. When Spring creates a Bean instance, it needs to instantiate all instances of its dependencies at the same time, resulting in performance degradation. Using set-value injection can avoid these problems.
- Especially in the case where some member variables are optional, the multi-parameter constructor is more cumbersome.
Constructor injection:
- Constructor injection can determine the injection order of dependencies in the constructor, and prioritize the injection of dependencies.
- Construct injection is more useful for beans whose dependencies do not need to change. Because there is no setter method, all dependencies are set in the constructor, and there is no need to worry about the subsequent code destroying the dependencies.
- Dependencies can only be set in the constructor, and only the creator of the component can change the dependencies of the component. For the caller of the component, the dependencies inside the component are completely transparent, which is more in line with the principle of high cohesion.
Both of these injection methods use reflection .
reflection
Learn about reflection related classes and their meanings:
- java.lang.Class: represents the entire bytecode. Represents a type and represents the entire class.
- java.lang.reflect.Method: Represents the method bytecode in the bytecode. Represents a method in a class.
- java.lang.reflect.Constructor: Represents the constructor bytecode in the bytecode. Represents a constructor in a class.
- java.lang.reflect.Field: represents the attribute bytecode in the bytecode. Represents member variables (static variables + instance variables) in a class.
The java.lang.reflect package provides a number of reflection classes for getting or setting instance objects. Simply put, reflection can:
- Determine the class to which any object belongs at runtime ;
- Construct an object of any class at runtime;
- Determine the member variables and methods of any class at runtime;
- call a method of any object at runtime;
- Generate dynamic proxies .
IoC and reflection , just create and process the instance of Bean, and follow-up function enhancement , function enhancement depends on AOP .
AOP
The full name of AOP is Aspect-Oriented Programming, which is literally translated into aspect-oriented programming in Chinese. It has become a relatively mature programming idea and can be used to solve the cross-focus problems distributed in various modules in the application system. In lightweight J2EE application development, using AOP to flexibly handle some system-level services with cross-cutting properties , such as transaction processing, security checking, caching, object pool management, etc., has become a very suitable solution.
Why do you need AOP
When we want to perform some logging, permission control, performance statistics, etc., in traditional applications, we may code in the required objects or methods, and for example, permission control and performance statistics are mostly repeated, so that the code contains There is a lot of repetitive code . Even if someone says that I extract the common part, there must be calls and repetitions. For example, we may only perform performance statistics when necessary, and delete these codes after diagnosis; and log records, such as Record some method access logs, data access logs, etc., which will penetrate into each method to be accessed; there is also permission control, which must be audited at the beginning of method execution, think about how terrible and how boring these are. If Spring is used, these logging, permission control, and performance statistics are separated from business logic. Through the aspect-oriented programming supported by Spring, these functions can be dynamically added where these functions are needed, without infiltrating each required method or object; Some people may say, we can use "proxy design pattern" or "wrapper design pattern", you can use these, but you still need to create proxy objects programmatically, or you need to couple these proxy objects, and use Spring Aspect Oriented Programming It can provide a better way to complete the above functions, generally through the configuration method, and does not need to add any additional code to the existing code, the existing code focuses on business logic.
Therefore, AOP is inserted into the main process in a cross-sectional manner, and Spring AOP aspect-oriented programming can help us achieve uncoupling:
- Performance monitoring, record the call time before and after the method call, the method execution is too long or the timeout alarm.
- Cache proxy, which caches the return value of a method, and obtains it directly from the cache when the method is executed next time.
- Software cracking, using AOP to modify the judgment logic of the software verification class.
- Record the log, record the system operation log before and after the method is executed.
- Workflow system, workflow system needs to mix business code and process engine code for execution, then we can use AOP to separate it and dynamically hook up the business.
- Permission verification, verify whether you have permission to execute the current method before the method is executed, if not, throw an exception without permission to execute, and there is business code capture.
- and many more
AOP actually divides an aspect from the application, and then inserts some "enhancements" into this aspect, and finally generates a proxy object that adds new functions. Note that it is a proxy object, which is the basis of Spring AOP implementation. This proxy object only has some more functions than the original object (Bean), such as Bean preprocessing , Bean postprocessing , exception handling and so on. The purpose of an AOP proxy is to weave aspects into a target object .
How AOP is implemented
Earlier we said that the implementation of IoC relies on reflection and then decoupling. What does AOP rely on to achieve?
AOP, in simple terms, is to enhance some functions for objects. We need to see which ports or stages Java has reserved for us, allowing us to weave some enhanced functions.
We can implement AOP at several levels.
compile time
- Principle: The source code is injected before the compiler compiles, and the bytecode after the source code is compiled will naturally contain this part of the injected logic.
- Representative works such as: lombok, mapstruct (modified through pluggable annotation processing API at compile time).
Runtime, before bytecode is loaded
- Principle: The bytecode needs to be loaded by the classloader ( class loader ), then we can modify the bytecode before it is loaded by the custom class loader by customizing the class loader.
- Representative works such as: javasist, java.lang.instrument, ASM (manipulate bytecode).
- Many agents such as Skywaking and Arthas do this, pay attention to distinguish between static agent and dynamic agent .
- JVMTI is a tool that JVM provides to operate native methods. Instrument is a java interface that provides you to manipulate JVMTI . For details, see java.lang.instrument.Instrumentation
Runtime, after the bytecode is loaded
- Principle: After the bytecode is loaded by the class loader, the bytecode file is dynamically constructed to generate a subclass of the target class , and the aspect logic is added to the subclass.
- Representative works such as: jdk proxy, cglib.
According to the classification, it can basically be understood as:
category | principle | advantage | shortcoming |
---|---|---|---|
static AOP | At compile time , aspects are compiled directly into the target bytecode file in the form of bytecode | No performance impact on the system | Not flexible enough |
Dynamic AOP | At runtime , after the target class is loaded, the proxy class is dynamically generated for the interface, and the aspect is woven into the proxy class | Dynamic proxy mode, more flexible than static AOP | The focus of the entry needs to implement the interface, which has a little performance impact on the system |
Dynamic bytecode generation | At runtime , after the target class is loaded, the bytecode file is dynamically constructed to generate a subclass of the target class , and the aspect logic is added to the subclass | You can weave without an interface | Weaving is not possible when the instance method of the extending class is final. The performance is basically the worst, because it needs to generate subclasses nested one level, and the cglib used by spring does this, so the performance is relatively poor |
custom class loader | At runtime , before the bytecode is loaded by the custom class loader, add the aspect logic to the target bytecode, such as Ali's Pandora | Can weave most classes | If other class loaders are used in the code, those classes will not be woven in |
bytecode conversion | At runtime , before all class loaders load bytecode, intercept | All classes can be weaved | - |
Of course , in theory, the earlier weaving, the better the performance. Static AOPs such as lombok and mapstruct are basically modified before the compilation period, so the performance is very good, but the flexibility will of course be poor, and the operation cannot be obtained. Some information at the time, so it needs to be weighed and compared.
Briefly describe the 5 categories:
Of course, I have compiled a detailed brain map, which can be opened directly on the web page.
"Brain Map: Java Implementation of AOP Ideas":
https://www.processon.com/embed/62333d1ce0b34d074452eec2
<iframe id="embed_dom" name="embed_dom" frameborder="0" style="display:block;width:100%; height:250px;" src="https://www.processon.com/embed/62333d1ce0b34d074452eec2 "></iframe>
1. Static AOP
Occurs at compile time , modifying the source code through the Pluggable Annotation Processing API.
When javac is compiled, an abstract syntax tree (AST) will be generated according to the source code, and java allows you to participate in modifying the source code by opening the Pluggable Annotation Processing API, and finally generate bytecode. A typical representative is lombok .
2. Dynamic AOP ( dynamic proxy )
Occurs at runtime , after the bytecode is loaded , classes and methods have been loaded into the method area.
A typical representative is JDK Proxy .
public static void main(String[] args) {
// 需要代理的接口,被代理类实现的多个接口,都必须在这里定义
Class[] proxyInterface = new Class[]{IBusiness.class,IBusiness2.class};
// 构建AOP的Advice,这里需要传入业务类的实例
LogInvocationHandler handler = new LogInvocationHandler(new Business());
// 生成代理类的字节码加载器
ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();
// 织入器,织入代码并生成代理类
IBusiness2 proxyBusiness =
(IBusiness2)Proxy.newProxyInstance(classLoader, proxyInterface, handler);
// 使用代理类的实例来调用方法
proxyBusiness.doSomeThing2();
((IBusiness)proxyBusiness).doSomeThing();
}
The proxy implements the InvocationHandler interface, and the final implementation logic is in the invoke method. After the proxy class is generated, as long as the method of the target object is called, it will preferentially enter the invoke method of the proxy class to perform enhanced verification and other behaviors.
public class LogInvocationHandler implements InvocationHandler{
private Object target; // 目标对象
LogInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行原有逻辑
Object rev = method.invoke(target,args);
// 执行织入的日志,你可以控制那些方法执行切入逻辑
if (method.getName().equals("doSomeThing2")){
// 记录日志
}
return rev;
}
}
Of course, the performance of dynamic agents is relatively poor. After all, there is an additional layer of agents, and each additional layer is definitely more difficult to optimize.
Although dynamic proxies dynamically generate proxy classes through interfaces at runtime, which brings some flexibility, this flexibility brings two problems:
- The first proxy class must implement an interface, and an exception will be thrown if the interface is not implemented.
- The second performance impact is because the dynamic proxy is implemented using the reflection mechanism. First of all, the reflection is definitely slower than the direct call. After testing, each proxy class consumes more than 10 milliseconds more than the static proxy. Secondly, the use of reflection to generate a large number of class files may cause Full GC to cause performance impact, because the bytecode file will be stored in the method area (or persistent generation, already in the meta space after JDK1.8 ) of the JVM runtime zone after loading, when the method When the area is full, it will cause Full GC , so when you use dynamic proxies a lot, you can set the persistent generation to a larger size to reduce the number of Full GCs .
For the detailed principles and processes of dynamic agents, it is recommended to read "Understanding Java Dynamic Agents in One Article" .
3. Dynamic bytecode generation
Occurs at runtime , after the bytecode is loaded , a subclass of the target class is generated, and the aspect logic is added to the subclass, so using Cglib to implement AOP does not need to be based on the interface.
At this point, the classes and methods have also been loaded into the method area.
A typical representative is Cglib (the bottom layer is also based on ASM operation bytecode). Cglib is a powerful and high-performance Code generation class library. It can extend Java classes and implement Java interfaces during runtime. It encapsulates Asm , so use The Asm jar needs to be imported before Cglib .
public static void main(String[] args) {
byteCodeGe();
}
/**
* 动态字节码生成
*/
public static void byteCodeGe() {
//创建一个织入器
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(Business.class);
//设置需要织入的逻辑
enhancer.setCallback(new LogIntercept());
//使用织入器创建子类
IBusiness2 newBusiness = (IBusiness2) enhancer.create();
newBusiness.doSomeThing2();
}
/**
* 记录日志
*/
public static class LogIntercept implements MethodInterceptor {
@Override
public Object intercept(
Object target,
Method method,
Object[] args,
MethodProxy proxy) throws Throwable {
//执行原有逻辑,注意这里是invokeSuper
Object rev = proxy.invokeSuper(target, args);
//执行织入的日志
if (method.getName().equals("doSomeThing")) {
System.out.println("recordLog");
}
return rev;
}
}
Spring adopts the JDK dynamic proxy mechanism to implement AOP by default. When the dynamic proxy is unavailable (the proxy class has no interface), the CGlib mechanism is used. The disadvantages are:
- Only methods can be cut in, not interfaces, fields, static static code blocks, and private private methods.
- Calling methods of each other in the same class will not use the proxy class. Because to use the proxy class, the bean must be obtained from the Spring container. The method of calling each other in the same class is called through the this keyword, and spring basically cannot modify the logic in the jvm .
- Final classes cannot be proxied using CGlib because subclasses cannot be generated.
4. Custom class loader
Occurs at runtime , before the bytecode is loaded , directly modify the method of some classes before the class is loaded into the JVM, and weave the cut-in logic into this method, and then hand the modified bytecode file to the virtual machine run.
A typical representative is javasist , which can obtain the method of the specified method name, and insert the code logic before and after execution.
Javassist is a framework for editing bytecode that allows you to easily manipulate bytecode. It can define or modify Class at runtime. The principle of using Javassist to implement AOP is to directly modify the method that needs to be cut in before the bytecode is loaded. This is more efficient than using Cglib to implement AOP, and there are not many restrictions. The implementation principle is as follows:
We use the system class loader to start our custom class loader, and add a class loading listener to this class loader. When the listener finds that the target class is loaded, it will weave the cut-in logic. Let's take a look at the implementation of AOP using Javassist. Code:
/***启动自定义的类加载器****/
//获取存放CtClass的容器ClassPool
ClassPool cp = ClassPool.getDefault();
//创建一个类加载器
Loader cl = new Loader();
//增加一个转换器
cl.addTranslator(cp, new MyTranslator());
//启动MyTranslator的main函数
cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);
// 类加载监听器
public static class MyTranslator implements Translator {
public void start(ClassPool pool) throws
NotFoundException, CannotCompileException {
}
/**
* 类装载到JVM前进行代码织入
*/
public void onLoad(ClassPool pool, String classname) {
if (!"model$Business".equals(classname)) {
return;
}
//通过获取类文件
try {
CtClass cc = pool.get(classname);
//获得指定方法名的方法
CtMethod m = cc.getDeclaredMethod("doSomeThing");
//在方法执行前插入代码
m.insertBefore("{ System.out.println(\"recordLog\"); }");
} catch (NotFoundException e) {
} catch (CannotCompileException e) {
}
}
public static void main(String[] args) {
Business b = new Business();
b.doSomeThing2();
b.doSomeThing();
}
}
CtClass is an abstract description of a class file. You can also use insertAfter() to insert code at the end of a method, or use insertAt() to insert code at a specified line.
Implementing AOP with a custom class loader is superior in performance to dynamic proxies and Cglib because it does not generate new classes, but it still has the problem that if other class loaders load classes, those classes will will not be blocked.
5. Bytecode conversion
A custom class loader that implements AOP can only intercept the bytecode loaded by itself, so is there a way to monitor the bytecode loaded by all class loaders? Yes, with Instrumentation, which is a new feature provided by Java 5 , with Instrumentation , developers can build a bytecode converter that does the conversion before the bytecode is loaded.
Occurs at runtime , before the bytecode is loaded , with the Instrumentation API provided by Java 1.5 . The Instrumentation API is like a backdoor pre-placed by the JVM , which can intercept programs running on the JVM and modify the bytecode.
This method is naturally provided by the Java API. In java.lang.instrumentation , even javasist is based on this implementation.
A proxy that implements the ClassFileTransformer interface is used to change the runtime bytecode ( class File ), this change occurs before the JVM loads the class, and is valid for all class loaders. The term class File is defined in Virtual Machine Specification 3.1 and refers to byte arrays of bytecodes, not class files in the file system. There is only one method in the interface:
/**
* 字节码加载到虚拟机前会进入这个方法
*/
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer)
throws IllegalClassFormatException;
// 把 classBeingRedefined 重定义之后再交还回去
ClassFileTransformer needs to be added to the Instrumentation instance to take effect.
be careful
When a modification is made to the bytecode in the JVM, the virtual machine also informs all threads to stop by means of a safe point, because the modification will affect the class structure.
start process
Bean life cycle management, basically from scratch (IoC), from existence to enhancement (AOP).
Any Bean has only three forms in the Spring container, definition , instance , and enhancement .
Observing from Bean definition information, bean relationship is defined by xml , properties , yaml , json define properties , bean relationship and properties constitute bean definition, and BeanDefinitionReader is responsible for scanning definition information to generate bean definition object BeanDefinition . On this basis, it is allowed to enhance the definition of BeanDefinition (there are many usage scenarios for Mybatis and Spring).
After the bean definition is completed, it starts to instantiate objects, fills properties, etc. through reflection. At the same time, many enhanced ports are reserved again, and a complete object is finally generated.
Instantiation process and L3 cache
From definition to extension, then reflection instantiation, to enhancement, there will be references to each state.
Therefore, Spring designs a three-level cache , which, to put it bluntly, corresponds to three forms of the storage bean life cycle:
- definition
- example
- enhance
Summarize
Spring is reflection + bytecode enhancement .
- Reflection, for IoC and decoupling
- Bytecode enhancements for simplicity and declarative programming
With a deep understanding of the two core features of Spring, the design and use of all the syntactic sugars of spring, springboot, and springcloud will naturally be clear.
refer to
- Understanding Java Agents
- Java 1.5 - java.lang.instrument
- ASM bytecode instrumentation
- arthas
- ASM
- cglib
- javassist
- Javassist/ASM Audit Log
- bytebuddy tutorial
- Performance Comparison of cglib, Javassist, JDK Proxy and Byte Buddy
- Inversion of Control
- AOP implementation mechanism
- Summary of Spring AOP
- What are javaAgent, ASM, javassist, ByteBuddy?
Starter subscription
The technical content is recorded here, released from time to time, first published in
- Pan Shenlian's personal website
- WeChat public account: Pan Pan and his friends
(End of this article)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。