preface
If you want to achieve the same effect, please keep consistent with the code in this article. The logic is not complex. After the effect is completed, you can modify it according to your own ideas. The code in this article is mainly implemented, and there must be some cases that have not been handled.
cause
A JAVA framework called Spring is often used in daily development. As long as an annotation is added, it can be registered as a bean and handed over to the framework for management. When using classes registered as beans, you don't need new to create objects, but @ Autowired can be added to complete automatic injection.
1. What does the framework manage?
A: don't answer first
2. How can automatic injection be used without new?
A: according to java The basic knowledge of this should be the value assigned by reflection. The idea is to get the annotation on the field through reflection, if any@Autowired The value is assigned.
Start implementation
Since most of the JDK used is 1.8, the code is in jdk1 8, and the development tools use IDEA. By the way, this is not to make wheels repeatedly, but to satisfy curiosity. By the way, practice the knowledge of annotation and reflection.
1. New Maven project, directory structure
2. Let's create a new class SpringApplicationContext under the spring package, and run the code in this class when it starts later.
3. If you write a startup class following the Springboot project, you must add a static run method to the class we just wrote.
Startup class
public class SpringApplication { public static void main(String[] args) { //Take Class as a parameter to facilitate reflection SpringApplicationContext.run(SpringApplication.class); } }
SpringApplicationContext class
public static void run(Class configClass){ }
4. Preparations are completed
- Since you want to use reflection to assign values to class fields, you have to know which classes need to be assigned. Naturally, you need to traverse the class files in the compiled directory and get their annotations.
//Add an annotation to specify the package path to be managed by 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(); }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //Component annotation @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Component { String value() default ""; }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //Describes whether the bean is a singleton @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Scope { String value(); }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //Identify that this field needs to be automatically injected @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.FIELD}) public @interface Autowired { String value() default ""; }
//The object that describes the Bean public class BeanDefinition { private Class clazz; private String scope; public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } public String getScope() { return scope; } public void setScope(String scope) { this.scope = scope; } }
import com.spring.ComponentScan; import com.spring.SpringApplicationContext; import com.test.service.UserService; @ComponentScan("com.test.service") public class SpringApplication { public static void main(String[] args) { SpringApplicationContext.run(SpringApplication.class); UserService userService = (UserService) SpringApplicationContext.getBean("userService"); userService.test(); } }
//Add a method scan in run. Exceptions can be thrown or caught according to the idea prompt when writing scan without writing first public class SpringApplicationContext { //The following is used to cache the information we traverse during scanning private static ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); //Store scanned singleton objects private static ConcurrentHashMap<String,Object> singleObjects = new ConcurrentHashMap<>(); public static void run(Class configClass){ //Scan startup class annotation try { scan(configClass); } catch (IOException | URISyntaxException e) { e.printStackTrace(); } }
/** * To scan class files, we need to prepare conditions, such as file path */ private static void scan(Class configClass) throws IOException, URISyntaxException { //Get annotation object ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class); //Gets the value in the annotation String path = componentScan.value(); //Convert annotation path to file path path = path.replace(".", File.separator); //Get the class loader to load the scanned classes. (ps: there are a lot to learn about class loaders) ClassLoader classLoader = SpringApplicationContext.class.getClassLoader(); //Gets the compiled resource path URL resource = classLoader.getResource(path); //Convert to URI, that is, the input parameter after URI resourceUri = resource.toURI(); //Determine whether it is a folder, and then traverse if(Files.isDirectory(Paths.get(resourceUri))){ //Directly call the file traversal method provided by JDK Files.walkFileTree(Paths.get(resourceUri),new SimpleFileVisitor<Path>(){ //Override the method being traversed @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { //If you are traversing a file, start the management operation if(!Files.isDirectory(file)){ //Get full path of file String fileName = file.toAbsolutePath().toString(); //Convert it to a format that can be used as an input parameter String className = fileName.replace(File.separator,".").substring(fileName.indexOf("com"),fileName.indexOf(".class")); //If the file is class bytecode file, which is what we compiled if(fileName.endsWith(".class")){ //Start getting Class object Class<?> clazz = null; try { //Get Class object clazz = classLoader.loadClass(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } //Start the reflection operation to determine whether there is an annotation Component on the class if(clazz.isAnnotationPresent(Component.class)){ //An annotation indicates that this is a Bean and needs to be managed Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class); //The bean name here is written manually for convenience. String beanName = componentAnnotation.value(); //You need an object to describe the Bean (for example, whether it is a singleton or not) BeanDefinition beanDefinition = new BeanDefinition(); //Store the clazz information in the object describing the Bean beanDefinition.setClazz(clazz); //If there is Scope annotation on the object, further judgment is required if(clazz.isAnnotationPresent(Scope.class)){ //Get annotation object Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class); beanDefinition.setScope(scopeAnnotation.value()); }else { //If no annotation is added, it is a single example by default beanDefinition.setScope("singleton"); } //Cache the information we get through a parallel Map beanDefinitionMap.put(beanName,beanDefinition); } } } return FileVisitResult.CONTINUE; } }); } }
//Go back to the run method and continue writing public static void run(Class configClass){ //Scan startup class annotation try { scan(configClass); } catch (IOException | URISyntaxException e) { e.printStackTrace(); } //After scanning the bytecode file, start field injection and other management operations, and traverse the cached information for(Map.Entry<String,BeanDefinition> entry : beanDefinitionMap.entrySet()){ String beanName = entry.getKey(); BeanDefinition beanDefinition = entry.getValue(); if("singleton".equals(beanDefinition.getScope())){ //Prevent code bloating and separate the operation of bean generation into one method Object bean = createBean(beanName,beanDefinition); //Store the generated object into the singleton pool singleObjects.put(beanName,bean); } } }
//Generate bean s and assign values to the fields that need to be automatically injected first private static Object createBean(String beanName,BeanDefinition beanDefinition){ //Get the Class object from the description bean object Class<?> clazz = beanDefinition.getClazz(); try{ //Instantiate by reflection Object instance = clazz.getDeclaredConstructor().newInstance(); //Traverse fields on Class for(Field declaredFiled : clazz.getDeclaredFields()){ //If a field traversed has Autowired annotation, it will be assigned if(declaredFiled.isAnnotationPresent(Autowired.class)){ //Get the field name, get the bean assignment from the cache, and write a separate method Object bean = getBean(declaredFiled.getName()); declaredFiled.setAccessible(true); declaredFiled.set(instance,bean); } } return instance; } catch (InvocationTargetException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; }
//Method to get Bean public static Object getBean(String beanName){ //Determine whether there is this bean if(beanDefinitionMap.containsKey(beanName)){ BeanDefinition beanDefinition = beanDefinitionMap.get(beanName); //If it is a singleton, remove it from the singleton pool if("singleton".equals(beanDefinition.getScope())){ Object singleObject = singleObjects.get(beanName); return singleObject; }else { Object bean = createBean(beanName,beanDefinition); return bean; } }else { throw new NullPointerException(); } }
5. Test whether it is useful according to daily coding
- Simulate the business code under the service package
import com.spring.Component; @Component("orderService") public class OrderService { }
public interface UserService { void test(); }
import com.spring.Autowired; import com.spring.Component; @Component("userService") public class UserServiceImpl implements UserService{ @Autowired private OrderService orderService; //If automatic injection fails, null will be printed @Override public void test() { System.out.println("test method:"+orderService); } }
import com.spring.ComponentScan; import com.spring.SpringApplicationContext; import com.test.service.UserService; //Start the class to get the cached bean and call its test method to see if the object is printed @ComponentScan("com.test.service") public class SpringApplication { public static void main(String[] args) { SpringApplicationContext.run(SpringApplication.class); UserService userService = (UserService) SpringApplicationContext.getBean("userService"); userService.test(); } }
Summary
There is still some code to be added. Of course, automatic injection has been written. Does the Spring framework do this? I click on the Spring source code and find that I can't read it in 10 minutes and don't bother to study it carefully because it is nested layer by layer. It's important to score recently. Work also needs a good ability to collect and filter information, such as using a good search engine.
Think about it:
- How to solve circular dependency
- The implementation of controller is spring MVC (keyword: dispatcherservlet)