IoC, the core of Spring
IoC control reverses the permission of Inverse of Control to create objects. Objects needed in Java programs are no longer created by programmers, but are created by IoC containers.
IoC core idea
Implement IoC in the way of Java Web
1,pom.xml
<dependencies> <!-- introduce Servlet rely on --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> </dependencies> <!-- set up Maven of JDK The default version is 5. It needs to be manually changed to 8 --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> <packaging>war</packaging>
2. Create Servlet
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WebServlet("/hello") public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Exception { resp.getWriter().write("Spring"); } }
3. Deploy to Tomcat
4. Servlet [i.e. Controller], Service and Dao [MVC three-tier architecture]
When requirements change, Java code may need to be modified frequently, which is inefficient. How to solve it?
Static factory
package com.southwind.factory; import com.southwind.dao.HelloDao; import com.southwind.dao.impl.HelloDaoImpl; public class BeanFactory { public static HelloDao getDao(){ return new HelloDaoImpl(); } }
service layer
private HelloDao helloDao = BeanFactory.getDao();
The above methods can not solve our problems. When the requirements change, we still need to modify the code. How to do it
Can you switch classes without changing the Java code?
How external profiles
Write the specific implementation class to the configuration file, and the Java program only needs to read the configuration file.
Common configuration files: XML, YAML, Properties, JSON
1. Define external profile
Here, the configuration file is placed in src/main/resources/factory.properties
helloDao=com.southwind.dao.impl.HelloDaoImpl
2. The Java program reads this configuration file
package com.southwind.factory; import com.southwind.dao.HelloDao; import com.southwind.dao.impl.HelloDaoImpl; import com.southwind.dao.impl.HelloDaoImpl2; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Properties; public class BeanFactory { private static Properties properties; static { properties = new Properties(); try { properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties")); } catch (IOException e) { e.printStackTrace(); } } public static Object getDao(){ String value = properties.getProperty("helloDao"); //Create objects with reflection mechanism try { Class clazz = Class.forName(value); Object object = clazz.getConstructor(null).newInstance(null); return object; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null; } }
3. Modify Service
private HelloDao helloDao = (HelloDao) BeanFactory.getDao();
Beans in Spring IoC are singletons
package com.southwind.factory; import com.southwind.dao.HelloDao; import com.southwind.dao.impl.HelloDaoImpl; import com.southwind.dao.impl.HelloDaoImpl2; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; import java.util.Properties; public class BeanFactory { private static Properties properties; private static Map<String,Object> cache = new HashMap<>(); static { properties = new Properties(); try { properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties")); } catch (IOException e) { e.printStackTrace(); } } public static Object getDao(String beanName){ //First determine whether there are bean s in the cache if(!cache.containsKey(beanName)){ synchronized (BeanFactory.class){ if(!cache.containsKey(beanName)){ //Cache bean s //Create objects with reflection mechanism try { String value = properties.getProperty(beanName); Class clazz = Class.forName(value); Object object = clazz.getConstructor(null).newInstance(null); cache.put(beanName, object); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } } return cache.get(beanName); } }
1,private HelloDao helloDao = new HelloDaoImpl(); 2,private HelloDao helloDao = (HelloDao)BeanFactory.getDao("helloDao");
1. It is strongly dependent / tightly coupled and cannot be modified after compilation. It has no scalability.
2. Weak dependency / loose coupling, which can still be modified after compilation, so that the program has better scalability.
Summary:
Programmers give up the permission to create objects and give the permission to create objects to BeanFactory. The idea of giving control to others is to control inversion IoC.
Use of Spring IoC
XML and annotations, but XML has been gradually eliminated. At present, the mainstream development is based on annotations, and Spring Boot is based on annotations.
package com.southwind.spring.entity; import lombok.Data; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component("myOrder") public class Order { @Value("xxx123") private String orderId; @Value("1000.0") private Float price; }
package com.southwind.spring.entity; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Data @Component public class Account { @Value("1") private Integer id; @Value("Zhang San") private String name; @Value("22") private Integer age; @Autowired @Qualifier("order") private Order myOrder; }
package com.southwind.spring.test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test { public static void main(String[] args) { //Load IoC container ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.southwind.spring.entity"); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); System.out.println(applicationContext.getBeanDefinitionCount()); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); System.out.println(applicationContext.getBean(beanDefinitionName)); } } }
IoC annotation based execution principle
Idea of handwritten code:
1. Customize a MyAnnotationConfigApplicationContext and pass in the package to be scanned in the constructor.
2. Get all classes under this package.
3. Traverse these classes, find the Class with @ Component annotation, obtain its Class and corresponding beanName, package it into a BeanDefinition and store it in the Set. This opportunity is the raw material automatically loaded by IoC.
4. Traverse the Set, create objects through the reflection mechanism, and detect whether the attribute has added @ Value annotation. If so, assign values to the attribute, and then store these dynamically created objects in the cache in the form of k-v.
5. Methods such as getBean are provided, and the corresponding bean can be retrieved through beanName.
code implementation
package com.southwind.myspring; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class BeanDefinition { private String beanName; private Class beanClass; } package com.southwind.myspring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } package com.southwind.myspring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Component { String value() default ""; } package com.southwind.myspring; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.*; public class MyAnnotationConfigApplicationContext { private Map<String,Object> ioc = new HashMap<>(); private List<String> beanNames = new ArrayList<>(); public MyAnnotationConfigApplicationContext(String pack) { //Traverse the package to find the target class (raw material) Set<BeanDefinition> beanDefinitions = findBeanDefinitions(pack); //Create bean s from raw materials createObject(beanDefinitions); //Automatic loading autowireObject(beanDefinitions); } public void autowireObject(Set<BeanDefinition> beanDefinitions){ Iterator<BeanDefinition> iterator = beanDefinitions.iterator(); while (iterator.hasNext()) { BeanDefinition beanDefinition = iterator.next(); Class clazz = beanDefinition.getBeanClass(); Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { Autowired annotation = declaredField.getAnnotation(Autowired.class); if(annotation!=null){ Qualifier qualifier = declaredField.getAnnotation(Qualifier.class); if(qualifier!=null){ //byName try { String beanName = qualifier.value(); Object bean = getBean(beanName); String fieldName = declaredField.getName(); String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1); Method method = clazz.getMethod(methodName, declaredField.getType()); Object object = getBean(beanDefinition.getBeanName()); method.invoke(object, bean); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }else{ //byType } } } } } public Object getBean(String beanName){ return ioc.get(beanName); } public String[] getBeanDefinitionNames(){ return beanNames.toArray(new String[0]); } public Integer getBeanDefinitionCount(){ return beanNames.size(); } public void createObject(Set<BeanDefinition> beanDefinitions){ Iterator<BeanDefinition> iterator = beanDefinitions.iterator(); while (iterator.hasNext()) { BeanDefinition beanDefinition = iterator.next(); Class clazz = beanDefinition.getBeanClass(); String beanName = beanDefinition.getBeanName(); try { //Created object Object object = clazz.getConstructor().newInstance(); //Complete the assignment of the attribute Field[] declaredFields = clazz.getDeclaredFields(); for (Field declaredField : declaredFields) { Value valueAnnotation = declaredField.getAnnotation(Value.class); if(valueAnnotation!=null){ String value = valueAnnotation.value(); String fieldName = declaredField.getName(); String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1); Method method = clazz.getMethod(methodName,declaredField.getType()); //Complete data type conversion Object val = null; switch (declaredField.getType().getName()){ case "java.lang.Integer": val = Integer.parseInt(value); break; case "java.lang.String": val = value; break; case "java.lang.Float": val = Float.parseFloat(value); break; } method.invoke(object, val); } } //Store in cache ioc.put(beanName, object); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } } public Set<BeanDefinition> findBeanDefinitions(String pack){ //1. Get all classes under the package Set<Class<?>> classes = MyTools.getClasses(pack); Iterator<Class<?>> iterator = classes.iterator(); Set<BeanDefinition> beanDefinitions = new HashSet<>(); while (iterator.hasNext()) { //2. Traverse these classes to find the annotated classes Class<?> clazz = iterator.next(); Component componentAnnotation = clazz.getAnnotation(Component.class); if(componentAnnotation!=null){ //Gets the value of the Component annotation String beanName = componentAnnotation.value(); if("".equals(beanName)){ //Gets the lowercase initial of the class name String className = clazz.getName().replaceAll(clazz.getPackage().getName() + ".", ""); beanName = className.substring(0, 1).toLowerCase()+className.substring(1); } //3. Encapsulate these classes into BeanDefinition and load them into the collection beanDefinitions.add(new BeanDefinition(beanName, clazz)); beanNames.add(beanName); } } return beanDefinitions; } } package com.southwind.myspring; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class MyTools { public static Set<Class<?>> getClasses(String pack) { // Collection of the first class Set<Class<?>> classes = new LinkedHashSet<Class<?>>(); // Loop iteration boolean recursive = true; // Get the name of the package and replace it String packageName = pack; String packageDirName = packageName.replace('.', '/'); // Define a collection of enumerations and loop to handle things in this directory Enumeration<URL> dirs; try { dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); // Loop iteration while (dirs.hasMoreElements()) { // Get next element URL url = dirs.nextElement(); // Get the name of the agreement String protocol = url.getProtocol(); // If it is saved on the server as a file if ("file".equals(protocol)) { // Gets the physical path of the package String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // Scan the files under the whole package as files and add them to the collection findClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // If it is a jar package file // Define a JarFile System.out.println("jar Type of scan"); JarFile jar; try { // Get jar jar = ((JarURLConnection) url.openConnection()).getJarFile(); // Get an enumeration class from this jar package Enumeration<JarEntry> entries = jar.entries(); findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes); } catch (IOException e) { // log.error("error in getting file from jar package while scanning user-defined view"); e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } return classes; } private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, final boolean recursive, Set<Class<?>> classes) { // Do the same loop iteration while (entries.hasMoreElements()) { // An entity in the jar can be a directory and other files in some jar packages, such as META-INF files JarEntry entry = entries.nextElement(); String name = entry.getName(); // If it starts with / if (name.charAt(0) == '/') { // Get the following string name = name.substring(1); } // If the first half is the same as the defined package name if (name.startsWith(packageDirName)) { int idx = name.lastIndexOf('/'); // If it ends with "/", it is a package if (idx != -1) { // Get the package name and replace "/" with "." packageName = name.substring(0, idx).replace('/', '.'); } // If it can be iterated and is a package if ((idx != -1) || recursive) { // If it is a. class file and not a directory if (name.endsWith(".class") && !entry.isDirectory()) { // Remove the following ". Class" to get the real class name String className = name.substring(packageName.length() + 1, name.length() - 6); try { // Add to classes classes.add(Class.forName(packageName + '.' + className)); } catch (ClassNotFoundException e) { // . error("error adding user-defined view class. Class file of this class cannot be found"); e.printStackTrace(); } } } } } } private static void findClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) { // Get the directory of this package and create a File File dir = new File(packagePath); // If it does not exist or is not a directory, it will be returned directly if (!dir.exists() || !dir.isDirectory()) { // log.warn("there are no files under user-defined package name" + packageName + "); return; } // If it exists, get all files under the package, including directories File[] dirfiles = dir.listFiles(new FileFilter() { // If the custom filtering rules can be looped (including subdirectories) or files ending in. Class (compiled java class files) @Override public boolean accept(File file) { return (recursive && file.isDirectory()) || (file.getName().endsWith(".class")); } }); // Cycle all files for (File file : dirfiles) { // If it is a directory, continue scanning if (file.isDirectory()) { findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes); } else { // If it is a java class file, remove the following. Class and leave only the class name String className = file.getName().substring(0, file.getName().length() - 6); try { // Add to collection // classes.add(Class.forName(packageName + '.' + // className)); // After replying to the classmate's reminder, there are some disadvantages in using forName here, which will trigger the static method. The classLoader's load function is not used classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className)); } catch (ClassNotFoundException e) { // log.error("error adding user-defined view class. Class file of this class cannot be found"); e.printStackTrace(); } } } } } package com.southwind.myspring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Qualifier { String value(); } package com.southwind.myspring; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Value { String value(); } package com.southwind.myspring.entity; import com.southwind.myspring.Component; import com.southwind.myspring.Value; import lombok.Data; @Data @Component("myOrder") public class Order { @Value("xxx123") private String orderId; @Value("1000.5") private Float price; } package com.southwind.myspring.entity; import com.southwind.myspring.Autowired; import com.southwind.myspring.Component; import com.southwind.myspring.Qualifier; import com.southwind.myspring.Value; import lombok.Data; @Data @Component public class Account { @Value("1") private Integer id; @Value("Zhang San") private String name; @Value("22") private Integer age; @Autowired @Qualifier("myOrder") private Order order; } package com.southwind.myspring; public class Test { public static void main(String[] args) { MyAnnotationConfigApplicationContext applicationContext = new MyAnnotationConfigApplicationContext("com.southwind.myspring.entity"); System.out.println(applicationContext.getBeanDefinitionCount()); String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); System.out.println(applicationContext.getBean(beanDefinitionName)); } } }