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 {
@Override
public 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 {
@Di
UserDaoImpl userDaoImpl;
@Override
public 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对象
@Override
public 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 通过反射获取类上的注解,判断是否存在 @WqxBean
Class<?> 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对象clazz
Class<?> 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();
}
}
结果:
成功