Ioc
IOC(控制反转) 就是 依赖倒置原则的一种代码设计思路。就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现。
Spring IOC容器通过xml,注解等其它方式配置类及类之间的依赖关系,完成了对象的创建和依赖的管理注入。实现IOC的主要设计模式是工厂模式。
一、主要实现的功能
- 创建自定义注解@WqxBean,该注解的功能是:被该注解标记的类,会被注册到ioc容器中
- 创建自定义注解@Di,该注解的功能是:被该注解标记的属性,将会从ioc容器中取出对应的实例化对象,使用该对象将被标记的属性初始化。
二、实现的步骤
- 1.创建模块 wqx-spring
- 2.创建两个测试需要用到的类service,dao
- 3.创建两个注解 @WqxBean @Di
- 4.创建ioc容器接口
- 5.实现ioc容器接口
2.1 创建模块
2.2 创建两个测试需要用到的接口及其实现类service,dao
interface UserDao.class
package wqx.dao;/*** @author Watching* * @date 2023/9/5* * Describe:*/
public interface UserDao {public void run();
}
class UserDaoImpl.class
package wqx.dao.impl;import wqx.anno.WqxBean;
import wqx.dao.UserDao;/*** @author Watching* * @date 2023/9/5* * Describe:*/
@WqxBean
public class UserDaoImpl implements UserDao {@Overridepublic void run() {System.out.println("userDao-run...");}
}
interface UserService.class
package wqx.service;/*** @author Watching* * @date 2023/9/5* * Describe:*/
public interface UserService {public void add();
}
class UserServiceImpl.class
package wqx.service.impl;import wqx.anno.Di;
import wqx.anno.WqxBean;
import wqx.dao.UserDao;
import wqx.dao.impl.UserDaoImpl;
import wqx.service.UserService;/*** @author Watching* * @date 2023/9/5* * Describe:*/
@WqxBean
public class UserServiceImpl implements UserService {@DiUserDaoImpl userDaoImpl;@Overridepublic void add() {System.out.println("add...");userDaoImpl.run();}
}
2.3 创建两个自定义注解
@WqxBean
package wqx.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Watching* * @date 2023/9/5* * Describe:该自定义注解用于注册javabean进ioc容器,效果类似于@Component*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface WqxBean {
}
@Di
package wqx.anno;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Watching* * @date 2023/9/5* * Describe:该注解用于依赖注入,效果类似于@Resource*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Di {
}
2.4 创建BeanFactory接口的子接口ApplicationContext
2.4.1 IoC容器在Spring的实现
Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式:
①BeanFactory
这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
②ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。
③ApplicationContext的主要实现类
类型名 | 简介 |
---|---|
ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 |
FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 |
ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 |
WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 |
ApplicationContext
接口中创建了一个map集合,这个map集合就是ioc容器,在实现类中如果扫描到目标包下的类有标记@WqxBean注解,则将该类的实例化对象放进ioc容器,key为该类的Class对象,value为该类的实例化对象。
package wqx.BeanFactory;import java.util.HashMap;
import java.util.Map;/*** @author Watching* * @date 2023/9/5* * Describe:*/
public interface ApplicationContext {//创建Map集合存放Bean对象(ioc容器Map<Class<?>, Object> iocContainer = new HashMap<>();public Object getBean(Class<?> clazz);
}
2.5 实现ApplicationContext接口
实现ApplicationContext接口之后,在实现类中,我们需要做三件事
- 实现getBean()方法,提供通过对象的Class对象获取实例化对象的方法
- 完成@WqxBean注解功能的实现
- 完成@Di注解功能的实现
在开发中,我们一般使用以下方式获取容器中的bean(将配置文件传入构造器函数),所以我们也可以为我们创建的AnnotationApplicationContext类创建一个构造器,构造器参数为要被扫描的包,在构造器中我们会扫描该包及其子包,如果发现包中的类上有@WqxBean注解,则将该类添加进ioc容器。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");/*** Singleton:单实例(在容器启动完成之前就已经创建好了,保存在容器中了,任何时候获取都是获取之前创建好的那个对象)*/User user = (User) applicationContext.getBean("UserSingleton");User user1 = (User) applicationContext.getBean("UserSingleton");
完整代码:
package wqx.BeanFactory.impl;import wqx.BeanFactory.ApplicationContext;
import wqx.anno.Di;
import wqx.anno.WqxBean;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** @author Watching* * @date 2023/9/5* * Describe:*/
public class AnnotationApplicationContext implements ApplicationContext {//1.通过Class类型直接获取Bean对象@Overridepublic Object getBean(Class<?> clazz) {return iocContainer.get(clazz);}//2.设置包扫描规则//当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化//创建有参构造函数,通过构造器传递包路径public AnnotationApplicationContext(String basePackage) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {String packagePath = basePackage.replaceAll("\\.", "\\\\");Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);//获取PackagePath的绝对路径while (urls.hasMoreElements()) {URL url = urls.nextElement();//需要将url中被转码的部分解码String filePath = URLDecoder.decode(url.getFile(), "utf-8");//获取包名前面的物理磁盘路径名String rootPath = filePath.substring(0, filePath.length() - basePackage.length());//1.扫描传入的文件路径下的所有文件,找到带有@WqxBean注解的类,将该类注册进ioc容器loadBean(filePath, rootPath);}//依赖注入loadDi();}//扫描类上有@WqxBean注解的类,使用反射创建该类的对象并存入map集合private void loadBean(String filePath, String rootPath) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {File file = new File(filePath);//1.判断是否是文件夹if (file.isDirectory()) {//1.1 如果是文件夹,则判断是否为空File[] files = file.listFiles();if (files == null || files.length == 0) {//1.1.1 如果为空,则直接退出return;}//1.2 如果不为空,则遍历文件夹for (File f : files) {if (f.isDirectory()) {//1.3如果遇到文件夹,则递归进入loadBean(f.getAbsolutePath(), rootPath);} else {//2.如果是文件,则扫描是否存在注解//2.1 获取文件路径,判断文件是否为.class类型if (f.getAbsolutePath().endsWith(".class")) {//2.2 将文件路径中的\替换为. 并去除.class,得到全类名String packageClassName = f.getAbsolutePath().substring(rootPath.length() - 1, f.getAbsolutePath().length() - ".class".length()).replaceAll("\\\\", "\\.");//2.3 通过反射获取类上的注解,判断是否存在 @WqxBeanClass<?> clazz = Class.forName(packageClassName);WqxBean annotation = clazz.getAnnotation(WqxBean.class);//2.4 如果存在@WqxBean注解,则使用反射创建该对象并将其存放进Map集合中if (!clazz.isInterface() && annotation != null) {//存入ioc容器,key为类的class对象Object o = clazz.getConstructor().newInstance();iocContainer.put(clazz,o);}}}}}}/*** 实现注解注入*/private void loadDi() throws IllegalAccessException {Set<Map.Entry<Class<?>, Object>> entries = iocContainer.entrySet();//1.从容器中取出所有k-v对象for (Map.Entry<Class<?>, Object> entry : entries) {//2.获取容器中已经实例化好的对象Object obj = entry.getValue();//3.获取clazz对象的所有属性//3.1 获取obj实例化对象的Class对象clazzClass<?> clazz = entry.getKey();//3.2 通过clazz对象获取obj对象的所有属性Field[] declaredFields = clazz.getDeclaredFields();//4.遍历所有属性,判断属性上是否有@Di注解for (Field declaredField : declaredFields) {Di di = declaredField.getAnnotation(Di.class);//4.1 如果有Di注解,那么将容器中的对应value值赋给他,value值是一个被实例化的对象if (di != null) {declaredField.setAccessible(true);//私有属性被修改前,Accessible需要被设置为true//4.2 从ioc容器中获取declaredField属性对应的实例化对象Object o = iocContainer.get(declaredField.getType());//4.3 将实例化对象赋值给declareField代表的属性//public void set(Object obj, Object value) obj:被修改的字段所属的对象 value:被修改的字段的新值declaredField.set(obj,o);}}}}
}
2.6 测试
我们已经在UserDaoImpl类和UserServiceImpl类上添加了***@WqxBean***注解,那么这两个类会被注册进spring容器。
以及在UserServiceImpl中使用@Di将UserDaoImpl注入进来,可以在UserServiceImpl中使用UserDaoImpl的方法了
测试类
package wqx.Test;import wqx.BeanFactory.ApplicationContext;
import wqx.BeanFactory.impl.AnnotationApplicationContext;
import wqx.service.UserService;
import wqx.service.impl.UserServiceImpl;import java.io.IOException;
import java.lang.reflect.InvocationTargetException;/*** @author Watching* * @date 2023/9/5* * Describe:*/
public class Test {public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {//通过构造方法创建Beanfactory对象,并将要扫描的包的包名传进构造函数ApplicationContext applicationContext = new AnnotationApplicationContext("wqx");//通过getBean(Class<?> clazz)方法获取目标对象UserService userService= (UserServiceImpl) applicationContext.getBean(UserServiceImpl.class);System.out.println(userService);//输出该对象//调用userService的add方法,add方法中使用USerDaoimpl调用了userDaoImpl的方法userService.add();}
}
结果:
成功