Simple handwriting simulation spring underlying principle

catalogue

Supplementary notes

Preparation of basic engineering

Implement ApplicationContext

Implement @ ComponentScan annotation

Implement @ Component annotation

spring principle specific simple simulation

Supplementary notes

Before starting, add the generation time of a bean. In the previous code for getting started with spring, it was written like this

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

The generated bean is generated in the first line of code. The second line of code only goes to the singleton pool to obtain the bean, but it is normally loaded. If the bean to be generated adopts lazy loading, it will no longer be generated in the first line of code. If it is a singleton bean, we will be the same every time we go to getBean, However, if it is a multi instance bean (also known as a prototype bean), each time you go to get, you create a new bean

Preparation of basic engineering

Let's implement the above method of obtaining bean s

Implement ApplicationContext

package own.study.spring;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) {
        this.config = config;
    }

    public Object getBean (String beanName) {

        return null;
    }

}

Make a configuration class

package own.study.spring;

public class Appconfig {
}

After configuring the class, we also need a test class

package own.study.spring;

public class Test {

    public static void main(String[] args) {
        MyApplicationContext myApplicationContext = new MyApplicationContext(Appconfig.class);
        UserService userService = (UserService) myApplicationContext.getBean("userService");
    }

}

When writing the test class, I found that I also need a class to be used as a bean

package own.study.spring;

public class UserService {

    public void printMessage () {
        System.out.println("this is userService's method uotput");
    }

}

It seems that we have implemented it in this way, but it certainly can't be used. After all, there are annotations. Of course, we said that we can't use ready-made spring annotations for manual simulation implementation, so let's also implement annotations

Implement @ ComponentScan annotation

package own.study.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScan {

    String value() default "";

}

In this way, we can use our own annotations

package own.study.spring;

@ComponentScan(value = "own.study.spring")
public class Appconfig {
}

Implement @ Component annotation

package own.study.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
    
    String value() default "";
    
}
package own.study.spring;

@Component(value = "userService")
public class UserService {

    public void printMessage () {
        System.out.println("this is userService's method uotput");
    }

}

spring principle specific simple simulation

In the first line, you must not directly create beans. You must select which beans need to be scanned and which beans need to be generated

In other words, we need to get the scan path first. We can get it from the passed in configuration class to judge whether there is @ ComponentScan annotation

package own.study.spring;

import java.lang.annotation.Annotation;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) {
        this.config = config;
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            System.out.println("sacnPath = " + sacnPath);
        }
    }

    public Object getBean (String beanName) {

        return null;
    }

}

You can see that after we get the address and the customized scanning path, we can go to the corresponding target directory to get all the class files, and then analyze whether there are comments on these class files. If we carefully read the startup parameter information of idea, we can also see that it is the class file of the target to be used

  Because this directory is a separator, we have to change the format of the path

package own.study.spring;

import java.io.File;
import java.lang.annotation.Annotation;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) {
        this.config = config;
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");
            System.out.println("sacnPath = " + sacnPath);
        }
    }

    public Object getBean (String beanName) {

        return null;
    }

}

 

  Then we can get all the files in the whole path

package own.study.spring;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) {
        this.config = config;
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");

            System.out.println("sacnPath = " + sacnPath);

            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    System.out.println(tmpFile.getAbsolutePath());
                }
            }
        }
    }

    public Object getBean (String beanName) {

        return null;
    }

}

  Now that we have the class file, we can load the class and judge whether there are annotations. Note that the class loader can only load our previous path with '.' as the separator, so we have to replace it (here, because my package name conflicts with the path name, I changed the package name from own to pri)

package pri.study.spring;

import java.io.File;
import java.net.URL;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) throws ClassNotFoundException {
        this.config = config;
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");

            System.out.println("sacnPath = " + sacnPath);

            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    System.out.println(tmpFile.getAbsolutePath());
                    String absolutePath = tmpFile.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("pri"), absolutePath.indexOf(".class")).replace("\\", ".");
                    System.out.println("absolutePath = " + absolutePath);

                    Class<?> aClass = classLoader.loadClass(absolutePath);
                    System.out.println(aClass.isAnnotationPresent(Component.class));

                }
            }

        }

    }

    public Object getBean (String beanName) {

        return null;
    }

}

 

  Look, we got the annotated class, indicating that it is a bean. Of course, it can't be generated directly yet. We need to judge whether it is a single instance bean or a multi instance (prototype) bean

package pri.study.spring;

import java.io.File;
import java.net.URL;

public class MyApplicationContext {

    private Class config;

    public MyApplicationContext (Class config) throws ClassNotFoundException {
        this.config = config;
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");

            System.out.println("sacnPath = " + sacnPath);

            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    System.out.println(tmpFile.getAbsolutePath());
                    String absolutePath = tmpFile.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("pri"), absolutePath.indexOf(".class")).replace("\\", ".");
                    System.out.println("absolutePath = " + absolutePath);

                    Class<?> aClass = classLoader.loadClass(absolutePath);
                    // Determine whether the @ Component annotation is included
                    if (aClass.isAnnotationPresent(Component.class)) {

                        // Determine whether the @ Scope annotation is included
                        if (aClass.isAnnotationPresent(Scope.class)) {

                            Scope annotation = aClass.getAnnotation(Scope.class);
                            String value = annotation.value();

                            //todo determines whether it is a single case or multiple cases (prototype)

                        }

                    }

                }
            }

        }

    }

    public Object getBean (String beanName) {

        return null;
    }

}
package pri.study.spring;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Scope {

    String value() default "";

}

Suppose, after we judge that it is a single instance, can we start creating bean s?

Think about a problem. We use the getBean() method to obtain and pass in a beanname, which returns an object, so we use a beanname to obtain the corresponding class. If we find the class, we still have to judge whether it is single instance or multiple instances, and we have to carry out the same logic as the content of the construction method, which is very troublesome and unnecessary, Can we execute a bunch of logic stored bean features in the construction method? Let getBean() simply judge whether it is a single instance or multiple instances. A single instance directly obtains the bean without executing too much repetitive logic

In order to ensure the simplicity of the construction method, we extract a method separately from the process of scanning judgment

private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();



 private void scanpath(Class config) throws ClassNotFoundException {
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");


            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    String absolutePath = tmpFile.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("pri"), absolutePath.indexOf(".class")).replace("\\", ".");

                    Class<?> aClass = classLoader.loadClass(absolutePath);

                    // Determine whether the @ Component annotation is included
                    if (aClass.isAnnotationPresent(Component.class)) {

                        // Get the name of the custom bean
                        Component component = aClass.getAnnotation(Component.class);
                        String beanName = component.value();

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanType(aClass);

                        // Determine whether the @ Scope annotation is included
                        if (aClass.isAnnotationPresent(Scope.class)) {
                            Scope annotation = aClass.getAnnotation(Scope.class);
                            String value = annotation.value();
                            beanDefinition.setScope(value);
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);

                    }

                }
            }

        }
    }

Here, we also have one more thing, that is, BeanDefinition, the definition of beans

package pri.study.spring;

public class BeanDefinition {

    private Class beanType;
    private String scope;
    private boolean isLazy;

    public Class getBeanType() {
        return beanType;
    }

    public void setBeanType(Class beanType) {
        this.beanType = beanType;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public boolean isLazy() {
        return isLazy;
    }

    public void setLazy(boolean lazy) {
        isLazy = lazy;
    }
}

After parsing and scanning the corresponding path, we already know the situation of all beans. We record them and put them into a map. The bean name is key and defined as value. At this time, we continue to write the construction method and judge. If it is a single example, we will generate beans. For convenience, we will make another map, Store the beanName and the corresponding bean. Think about it carefully. Is it convenient to put a map in the getBean() method?

        // Create singleton bean
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String key = entry.getKey();
            BeanDefinition beanDefinition = beanDefinitionMap.get(key);

            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(key, beanDefinition);
                singletonPool.put(key, bean);
            }

        }

Why only one case? Because multiple instances need to be recreated every time, there is no need to create them. You can judge whether they are multiple instances when getBean(). If they are multiple instances, we will create the bean again

So the question is, how to create beans? Since both single and multiple instances need to create beans, we extract a common method

    private Object createBean (String beanName, BeanDefinition beanDefinition) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = beanDefinition.getBeanType();
        Constructor constructor = clazz.getConstructor();
        Object bean = constructor.newInstance();
        return bean;
    }

For simplicity, we directly use the construction method of null parameters

At this point, we are finished and can use our spring normally

So the code of the whole xxapplicationcontext is

package pri.study.spring;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class MyApplicationContext {

    private Class config;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    // Define a singleton pool
    private Map<String, Object> singletonPool = new HashMap<>();

    public MyApplicationContext (Class config) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        this.config = config;

        // scanning
        scanpath(config);

        // Create singleton bean
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String key = entry.getKey();
            BeanDefinition beanDefinition = beanDefinitionMap.get(key);

            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(key, beanDefinition);
                singletonPool.put(key, bean);
            }

        }

    }

    private Object createBean (String beanName, BeanDefinition beanDefinition) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = beanDefinition.getBeanType();
        Constructor constructor = clazz.getConstructor();
        Object bean = constructor.newInstance();
        return bean;
    }

    private void scanpath(Class config) throws ClassNotFoundException {
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");


            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    String absolutePath = tmpFile.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("pri"), absolutePath.indexOf(".class")).replace("\\", ".");

                    Class<?> aClass = classLoader.loadClass(absolutePath);

                    // Determine whether the @ Component annotation is included
                    if (aClass.isAnnotationPresent(Component.class)) {

                        // Get the name of the custom bean
                        Component component = aClass.getAnnotation(Component.class);
                        String beanName = component.value();

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanType(aClass);

                        // Determine whether the @ Scope annotation is included
                        if (aClass.isAnnotationPresent(Scope.class)) {
                            Scope annotation = aClass.getAnnotation(Scope.class);
                            String value = annotation.value();
                            beanDefinition.setScope(value);
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);

                    }

                }
            }

        }
    }

    public Object getBean (String beanName) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("bean name is error");
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")) {
            // The description is a single example
            return singletonPool.get(beanName);
        } else {
            // The description is a multi instance (prototype) bean, which needs to be created every time
            return createBean(beanName, beanDefinition);
        }
    }

}

Let's try

  It also supports single instance and multiple instances

Single example:

Multiple cases

Multiple bean s

  However, in this way, we have to customize the bean name every time we use annotations, otherwise an error will be reported, but we don't need to specify so when we actually use spring. We optimize it

// Get the name of the custom bean
                        Component component = aClass.getAnnotation(Component.class);
                        String beanName = component.value();

                        if ("".equals(beanName)) {
                            beanName = Introspector.decapitalize(aClass.getSimpleName());
                        }

Let's try to see if it's normal

 

 ---------------------------------- Split line----------------------------------

Now we can create a bean. There is also a very important thing in spring, dependency injection

Let's implement the annotation first

package pri.study.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {

}

Of course it can't be used now. If you don't believe it, we'll see the results

package pri.study.spring;

@Component(value = "userService")
@Scope(value = "aad")
public class UserService {

    @Autowired
    private OrderService orderService;

    public void printMessage () {
        System.out.println("this is userService's method uotput");
        System.out.println("orderService = " + orderService);
    }

}
package pri.study.spring;

import java.lang.reflect.InvocationTargetException;

public class Test {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        MyApplicationContext myApplicationContext = new MyApplicationContext(Appconfig.class);
        UserService userService = (UserService) myApplicationContext.getBean("userService");
        userService.printMessage();
    }

}

  Think about the creation process of spring bean s. Is there a step of dependency injection before generating beans? Therefore, we need to modify our createBean method

    private Object createBean (String beanName, BeanDefinition beanDefinition) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = beanDefinition.getBeanType();
        Constructor constructor = clazz.getConstructor();
        Object bean = null;

        bean = constructor.newInstance();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                field.set(bean, getBean(field.getName()));
            }
        }

        return bean;
    }

Of course, this is a simple implementation, which is taken directly from the singleton pool. There will be problems

1. Circular dependency, which is not within the scope of this sharing, is considered OK for the time being

2. The process is scanned first. Suppose that the bean userService is created and the createBean is created, but the internally injected orderService has not been generated, we will get an empty; So we can add a judgment. If it is empty, create a bean

    public Object getBean (String beanName) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new RuntimeException("bean name is error");
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition.getScope().equals("singleton")) {
            // The description is a single example
            Object singleBean = singletonPool.get(beanName);
            if (Objects.isNull(singleBean)) {
                singleBean = createBean(beanName, beanDefinition);
                singletonPool.put(beanName, singleBean);
            }
            return singleBean;
        } else {
            // The description is a multi instance (prototype) bean, which needs to be created every time
            return createBean(beanName, beanDefinition);
        }
    }

In my last article, I introduced the creation process of spring bean s and initialization. Let's also implement it

package pri.study.spring;


public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
    
}

So how to implement the things after initialization? Look at the following. We can judge whether there is an implementation after the createBean. If there is, we can force the call. This is also mentioned in the previous document

    private Object createBean (String beanName, BeanDefinition beanDefinition) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = beanDefinition.getBeanType();
        Constructor constructor = clazz.getConstructor();
        Object bean = null;

        bean = constructor.newInstance();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                field.set(bean, getBean(field.getName()));
            }
        }

        // Determines whether InitializingBean is implemented. If it is implemented, it will turn strongly and then invoke.
        if (bean instanceof InitializingBean) {
            ((InitializingBean) bean).afterPropertiesSet();
        }

        return bean;
    }

 ---------------------------------- Split line----------------------------------  

In spring, there is also a very important interface, BeanPostProcessor. This interface has two methods, postProcessBeforeInitialization and postProcessAfterInitialization. The methods of this interface are for all beans, that is, if this interface is implemented, all beans will call the rewritten two methods

As for how to call it, it's actually simple. We just need to judge whether there is an implementation of this interface and implement it before bean generation. Then we save it and use it after bean creation. Because there may be multiple implementations, we put it in the list, and then we traverse this collection and execute it after bean creation

private void scanpath(Class config) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (config.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = (ComponentScan) config.getAnnotation(ComponentScan.class);
            String sacnPath = componentScan.value();
            sacnPath = sacnPath.replace(".", "/");


            ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(sacnPath);
            File file = new File(resource.getFile());

            if (file.isDirectory()) {
                for (File tmpFile : file.listFiles()) {
                    String absolutePath = tmpFile.getAbsolutePath();
                    absolutePath = absolutePath.substring(absolutePath.indexOf("pri"), absolutePath.indexOf(".class")).replace("\\", ".");

                    Class<?> aClass = classLoader.loadClass(absolutePath);

                    // Determine whether the @ Component annotation is included
                    if (aClass.isAnnotationPresent(Component.class)) {

                        // Judge whether a bean implements the BeanPostProcessor interface. If so, save it
                        if (BeanPostProcessor.class.isAssignableFrom(aClass)) {
                            BeanPostProcessor beanPostProcessor = (BeanPostProcessor) aClass.getConstructor().newInstance();
                            beanPostProcessorList.add(beanPostProcessor);
                        }

                        // Get the name of the custom bean
                        Component component = aClass.getAnnotation(Component.class);
                        String beanName = component.value();

                        if ("".equals(beanName)) {
                            beanName = Introspector.decapitalize(aClass.getSimpleName());
                        }

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanType(aClass);

                        // Determine whether the @ Scope annotation is included
                        if (aClass.isAnnotationPresent(Scope.class)) {
                            Scope annotation = aClass.getAnnotation(Scope.class);
                            String value = annotation.value();
                            beanDefinition.setScope(value);
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);

                    }

                }
            }

        }
    }
private Object createBean (String beanName, BeanDefinition beanDefinition) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class clazz = beanDefinition.getBeanType();
        Constructor constructor = clazz.getConstructor();
        Object bean = null;

        bean = constructor.newInstance();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                field.set(bean, getBean(field.getName()));
            }
        }

        // Determines whether InitializingBean is implemented. If it is implemented, it will turn strongly and then invoke.
        if (bean instanceof InitializingBean) {
            ((InitializingBean) bean).afterPropertiesSet();
        }

        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            beanPostProcessor.postProcessAfterInitialization(bean, beanName);
        }

        return bean;
    }

  Take a closer look. The method of BeanPostProcessor has a return value, which means that we can return the bean. Think about it. We can change the bean and then return. Are you familiar with it? It is AOP. We can proxy the specified bean in the method, and then return the proxy object to change the external bean into a proxy object

Keywords: Spring

Added by miancu on Tue, 07 Sep 2021 07:01:03 +0300