This is the sixth day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

The general idea: Scan the annotations in the class and use reflection to make the class instance exist in the Map.

Concrete implementation:

All the details are annotated.

You define a class to hold the basic information about the bean.

public interface XBeanDefinition { final static String SINGLETON = "singleton"; // Final static String PROTOTYPE = "PROTOTYPE "; / / prototype Class <? > getBeanClass(); boolean isSingleton(); boolean isPrototype(); String getInitMethodName(); }Copy the code
Public class GenericBeanDefinition implements XBeanDefinition{private String implements MethodName; / / Class private Class <? > clazz; / / the default for the SINGLETON pattern private String scope = XBeanDefinition. SINGLETON. public void setInitMethodName(String initMethodName) { this.initMethodName = initMethodName; } public Class<? > getClazz() { return clazz; } public void setClazz(Class<? > clazz) { this.clazz = clazz; } public Class<? > getBeanClass() { return this.clazz; } public boolean isSingleton() { return Objects.equals(this.scope,XBeanDefinition.SINGLETON); } public boolean isPrototype() { return Objects.equals(this.scope,XBeanDefinition.PROTOTYPE); } public String getInitMethodName() { return null; }}Copy the code

Implement a simple bean factory. The interface defined has only one getBean method.

public interface XBeanFactory {

    Object getBean(String beanName);
}
Copy the code

Define an interface that specifically registers beans. Primarily, it operates on the methods defined by the bean

Public interface XBeanDefinitionRegistry {// Register bean with factory void registryBeanDefinition(String beanName, XBeanDefinition beanDefinition) throws Exception ; // getBeanDefinition XBeanDefinition getBeanDefinition(String beanName); BeanDefinition Boolean containBeanDefinition(String beanName); }Copy the code

Implementation of the default factory

public class DefaultBeanFactory implements XBeanFactory,XBeanDefinitionRegistry{ public Map<String,Object> beanMap = new  ConcurrentHashMap<>(); Private Map<String,XBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); Public void registryBeanDefinition(String beanName, Throws Exception {object.requirenonNULL (beanName,"beanName cannot be empty "); If (beanDefinitionMap. Either containsKey (beanName)) {throw new Exception (" existing "beanName + + GetBeanDefinition (beanName)); } beanDefinitionMap.put(beanName,beanDefinition); } @Override public XBeanDefinition getBeanDefinition(String beanName) { return beanDefinitionMap.get(beanName); } @Override public boolean containBeanDefinition(String beanName) { return beanDefinitionMap.containsKey(beanName); } @override public Object getBean(String beanName) {try {return doGetBean(beanName); } catch (Exception e) { e.printStackTrace(); } return null; } private Object doGetBean(String beanName) throws Exception {object.requirenonNULL (beanName,"beanName cannot be empty "); Object instance = beanMap.get(beanName); if(instance ! = null) {return instance; } // Get beanDefinition XBeanDefinition beanDefinition beandefinitionmap.get (beanName); Class<? > clazz = beanDefinition.getBeanClass(); Instance = clazz.newinstance (); String methodName = beanDefinition.getInitMethodName(); if(null ! Method = clazz.getMethod(methodName, null); method.invoke(instance,null); } if(beanDefinition.isSingleton()) { beanMap.put(beanName,instance); } return instance; }}Copy the code

Define a few simple annotations

@Target(elementType. FIELD) @Retention(RetentionPolicy.runtime) @Documented Public @Interface {}Copy the code
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface XBean { String value() default ""; //bean id String initMethod() default ""; // Specify the initialization method name}Copy the code
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XComponent {
    String value() default "";
}
Copy the code
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface XConfiguration {
    String value() default "";
}
Copy the code

Implement a simple startup class. By reading the path of the scan class in the configuration file. Then, by reflecting the instance object, we store it in the map.

Public class XSpringApplication {// Default is defaultBeanFactory private static defaultBeanFactory beanFactory = new DefaultBeanFactory(); Public static void run(String configFilePath) {// ConfigUtil.loadConfig(configFilePath); // ConfigUtil.loadConfig(configFilePath); / / get need scanning path String scanPackage = ConfigUtil. ContextConfig. GetProperty (" scan - package "); // ScannerUtil.doscanner (scanPackage); resolve(ScannerUtil.classNameList); Private static void resolve(List<String> classNameList) {if(classnamelist.isempty ()) {return; } try { for(String className : classNameList) { Class<? > clazz = Class.forName(className); if(clazz.getDeclaredAnnotations().length ! {Object instance = clazz.newinstance (); Method[] methods = clazz.getMethods(); Field[] fields = clazz.getFields(); String beanName = ""; if(clazz.isAnnotationPresent(XComponent.class)) { beanName = clazz.getAnnotation(XComponent.class).value(); If (beanname.equals ("")) // If beanName = "" {beanName = toLowerFirstCase(clazz.getSimplename ()); } } else if(clazz.isAnnotationPresent(XConfiguration.class)) { beanName = toLowerFirstCase(clazz.getSimpleName()); } GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setClazz(clazz); / / registered bean the beanFactory. RegistryBeanDefinition (beanName beanDefinition); beanFactory.beanMap.put(beanName,instance); listMethod(methods,instance); // iterate over all methods listFields(fields,instance); }}} Catch (Exception e) {e.printStackTrace(); } } public static Object getBean(String beanName) { return beanFactory.getBean(beanName); } /** * Get the first lowercase name of the class ** @param className className * @return java.lang.String */ private static String toLowerFirstCase(String className) { char[] charArray = className.toCharArray(); charArray[0] += 32; return String.valueOf(charArray); } private static String getBeanName(String className) { String[] arr = className.split("\."); return toLowerFirstCase(arr[arr.length-1]); } private static void listMethod(Method[] methods , Object instance) { try{ for(Method method : // go through all the methods, Determine whether or not to use the @ Bane annotation {if (method. IsAnnotationPresent (XBean. Class)) / / to determine whether a method using XBean annotation {/ / get the return value type String type = method.getGenericReturnType().getTypeName(); String beanName = ""; String val = method.getAnnotation(XBean.class).value(); if(!" ".equals(val)) { beanName = val; } else { beanName = getBeanName(type); } Class classN = Class.forName(type); GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setClazz(classN); beanFactory.registryBeanDefinition(beanName,beanDefinition); Object bean = method.invoke(instance, method.getParameters()); Beanfactory.beanmap.put (beanName,bean); }}} catch (Exception e) {e.printStackTrace(); } } private static void listFields(Field[] fields , Object instance) throws Exception { for(Field field : fields) { if(! field.isAnnotationPresent(XAutowired.class)) { continue; } else { field.setAccessible(true); Class<? > aClass = field.getType(); String beanName = toLowerFirstCase(aclass.getSimplename ()); // Check whether aclass is registered if(! The beanFactory. ContainBeanDefinition (beanName)) {throw new Exception (" the attribute object is not registered in the spring container "); } // The aclass is already registered with Spring. // The spring container will determine if a sample Object of the aclass already exists. If not, an Object bean will be instantiated. field.set(instance,bean); // Inject the property}}}}Copy the code

There are also utility classes for reading configuration file information.

public class ConfigUtil { public static Properties contextConfig = new Properties(); @param configPath configPath public static void loadConfig(String configPath) {InputStream inputStream = ConfigUtil.class.getClassLoader().getResourceAsStream(configPath); try { contextConfig.load(inputStream); } catch (IOException e) { e.printStackTrace(); } finally { if(inputStream ! = null) { try { inputStream.close(); } catch (IOException e) {e.printStackTrace(); } } } } }Copy the code
public class ScannerUtil { public static List<String> classNameList = new ArrayList<>(); /** * @param scanPackage */ public static void doScanner(String scanPackage) {URL resourcePath = ScannerUtil.class.getResource("/" + scanPackage.replaceAll("\.", "/")); if(null == resourcePath) { return ; } File classPath = new File(resourcePath.getFile()); // for(File File: Classpath.listfiles ()) {if(file.isDirectory()) // The folder is recursively query {doScanner(scanPackage+"." + file.getName()); } else {// Reflection to determine whether there is a componton annotation if(! file.getName().endsWith(".class")) { continue; } String className = (scanPackage + "." + file.getName()).replace(".class",""); classNameList.add(className); }}}}Copy the code

Testing:

@XComponent(value = "user") public class User { private String uid; private String userName; public String getUid() { return uid; } public void setUid(String uid) { this.uid = uid; } @Override public String toString() { return "User{" + "uid='" + uid + ''' + ", userName='" + userName + ''' + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; }}Copy the code

Test @ XCompant

public class Test {
    public static void main(String[] args) {
        XSpringApplication.run("application.properties");
        User user = (User)XSpringApplication.getBean("user");

//        ConfigurationTest test = (ConfigurationTest)XSpringApplication.getBean("configurationTest");
//        System.out.println(test.user);
    }
Copy the code

 

Test the XBean and comment out the User class annotations

@XConfiguration public class ConfigurationTest { // @XAutowired // public User user; @XBean(value = "user") public User getUser() { User user = new User(); user.setUid("123456"); user.setUserName("swq"); return user; } @XBean public Student getStudent() { return new Student(); }}Copy the code

Test Autowired

@XConfiguration public class ConfigurationTest { @XAutowired public User user; @XBean(value = "user") public User getUser() { User user = new User(); user.setUid("123456"); user.setUserName("swq"); return user; } @XBean public Student getStudent() { return new Student(); }}Copy the code
public class Test { public static void main(String[] args) { XSpringApplication.run("application.properties"); User user = (User)XSpringApplication.getBean("user"); System.out.println(user); ConfigurationTest test = (ConfigurationTest)XSpringApplication.getBean("configurationTest"); System.out.println(test.user); }}Copy the code

Only the scanning classpath is configured in the configuration file

Scan-package =com.swq // Class path to be scannedCopy the code

Conclusion:

Implementing a simple IOC container relies mostly on reflection. Of course, the ioc implementation of Spring is a lot of details, to understand the implementation of Spring still need to read the source code.