[JAVA reflection] simply implement Spring's IOC+DI

Why do you want to realize this function

If you have nothing to do, you just want to play with the class injection implementation of Spring. This article is only to better learn and understand the IOC+DI implementation principle of Spring. It is not used in project practice. If there is something wrong, please correct it. If you don't like it, please bypass it.

Achieve the purpose

Without introducing the Spring framework, the function interface is automatically injected into the business class
Don't talk nonsense, just do it!

realization

thinking

  • Scan all class files under this package to get the full list of class names
  • Put all classes marked with MyService annotation into the container through reflection
  • Then inject the attribute marked with MyAutowired annotation into the implementation class through reflection. For the case of two implementation classes of one interface, the implementation class is obtained only by name here

Create Annotation

  • MyService is used for annotation classes
package com.icodesoft.service;

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 MyService {

}
  • MyAutowired for annotation properties
package com.icodesoft.service;

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 MyAutowired {
    String name();
}

Create functional interfaces and implementation classes

  • Functional interface
package com.icodesoft.service;

public interface IService {
    String getName();
}
  • Implementation class user
package com.icodesoft.service;

@MyService
public class UserService implements IService {
    @Override
    public String getName() {
        return "I User Service Class has been successfully injected!";
    }
}
  • Implementation class proudct
package com.icodesoft.service;

@MyService
public class ProductService implements IService {
    @Override
    public String getName() {
        return "I Product Service Class has been successfully injected!";
    }
}

Create business class

  • TestService business class
package com.icodesoft.service;

@MyService
public class TestService {
    @MyAutowired(name = "productService")
    private IService service;

    public String getServiceName() {
        return this.service.getName();
    }
}

So far, all our business classes only depend on the function interface rather than the implementation class. Let's implement how to inject the function implementation class into the private IService service of TestService class; Attribute

Create a class MyFactoryBean. I put the class container and injection function into this class. Spectators can award others to realize a single function separately

package com.icodesoft;

import com.icodesoft.service.MyAutowired;
import com.icodesoft.service.MyService;
import org.apache.commons.lang3.ArrayUtils;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.*;

/**
 * Scan all class files under this package to get the full list of class names
 * Put all classes marked with MyService annotation into the container through reflection
 * Then inject the attributes marked with MyAutowired annotation into the implementation class through reflection
 */
public class MyFactoryBean {

    public static Map<String, Object[]> allBean = new HashMap<>();
    private static String packagePath; // Full package name, such as com icodesoft. service

    public static List<String> scan(String packageName) throws Exception {
        packagePath = packageName.replaceAll("\\.", "/");
        List<String> list = null;
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> urls = classLoader.getResources(packagePath);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url.getProtocol().equals("file")) {

                // Scan all class files under this package to get the full list of class names
                list = scanFiles(url.getPath(), packageName);
            }
        }
        System.out.println("*******: " + list);
        // Put all classes marked with MyService annotation into the container through reflection
        initBeans(list);

        // Then inject the attributes marked with MyAutowired annotation into the implementation class through reflection
        di();
        return list;
    }

    private static void di() throws Exception {
        for (String className : allBean.keySet()) {
            Class<?> clzz = Class.forName(className);
            if (clzz.isInterface()) continue;
            Object o = allBean.get(className)[0];
            Field[] declaredFields = clzz.getDeclaredFields();
            for (Field field : declaredFields) {
                field.setAccessible(true);
                if (field.isAnnotationPresent(MyAutowired.class)) {
                    MyAutowired annotation = field.getAnnotation(MyAutowired.class);
                    String name = annotation.name();
                    Class<?> fieldType = field.getType();
                    Object[] beans = allBean.get(fieldType.getName());
                    if (fieldType.isInterface() && beans.length > 1) {
                        if (name == null || name.trim().isEmpty())
                            throw new ClassNotFoundException("When the interface has multiple implementation classes, it must be specified name value");
                        for (Object bean : beans) {
                            if (name.equalsIgnoreCase(bean.getClass().getSimpleName())) {
                                field.set(o, bean);
                                break;
                            }
                        }
                    } else {
                        field.set(o, beans[0]);
                    }
                }
            }
        }
    }

    private static void initBeans(List<String> classNames) {
        for (String className : classNames) {
            try {
                Class<?> clzz = Class.forName(className);

                if (clzz.isInterface() || clzz.isAnnotation() || Modifier.isPrivate(clzz.getDeclaredConstructor().getModifiers()))
                    continue;

                if (clzz.isAnnotationPresent(MyService.class)) {
                    Object object = clzz.getDeclaredConstructor().newInstance();
                    Class<?>[] interfaces = clzz.getInterfaces();
                    if (interfaces.length > 0) {
                        for (Class<?> classInterface : interfaces) {
                            if (allBean.containsKey(classInterface.getName())) {
                                Object[] objects = allBean.get(classInterface.getName());
                                allBean.put(classInterface.getName(), ArrayUtils.add(objects, object));
                            } else {
                                allBean.put(classInterface.getName(), new Object[]{object});
                            }
                        }
                    }
                    allBean.put(className, new Object[]{object});
                }

            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    // Scan all class files under this package to get the full list of class names
    private static List<String> scanFiles(String path, String basePkg) {
        File direc = new File(path);
        List<String> classNames = new ArrayList<>();
        File[] files = direc.listFiles();
        if (null == files) {
            return classNames;
        }

        for (int i = 0; i < files.length; ++i) {
            File file = files[i];
            if (file.isDirectory()) {
                List<String> list = scanFiles(file.getAbsolutePath(), basePkg + "." + file.getName());
                classNames.addAll(list);
            } else if (file.getName().endsWith(".class")) {
                String className = file.getName().substring(0, file.getName().lastIndexOf("."));
                if (-1 != className.lastIndexOf("$")) {
                    continue;
                }
                String result = basePkg + "." + className;
                classNames.add(result);
            }
        }
        return classNames;
    }
}

Let's run and play to see if we can succeed

public class DemoApplication {
    static {
        try {
            MyFactoryBean.scan("com.icodesoft.service");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws Exception {
        System.out.println(": " + MyFactoryBean.allBean);
        TestService testService = (TestService) MyFactoryBean.allBean.get(TestService.class.getName())[0];
        String serviceName = testService.getServiceName();
        System.out.println("==================>serviceName: " + serviceName);

    }
}

result

You can see that the program runs normally and the function class has been successfully injected

Posted by heropage on Fri, 13 May 2022 22:12:38 +0300