文章目录
手写IOC
概述
什么是IOC:ioc可以抽象的理解为是一容器,而它所存储的内容就是Bean对象,打个比方:你可以把IOC当成水杯,而里面的水就是Bean对象。
**IOC可以做什么:**在Spring中IOC一般是用来进行 创建对象(Bean对象的创建) 和将 方法或者属性进行注入(属性注入),这样子可以避免代码大量的 耦合,
一、手写IOC前置技术栈回顾
1、Bean对象的创建
①:前置准备
1、在子包中的 resources 文件中创建xml文件用来创建bean对象
2、在创建两个类(一个实现接口,一个是测试类)和一个接口
如图:
3、创建依赖:springframework
<!-- sprint context依赖-->
<!-- 当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.2</version>
</dependency>
<!-- junit-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.3</version>
</dependency>
②:将类和接口创建方法
在接口中创建任意一个方法,方便后面测试
如图:
接口:
实现类:
⑤:在xml里面创建bean对象
id:是bean名,
class:需要bean对象绑定类的相对路径
在创建bean对象的时候需要注意:
1、不可创建两个class相同的bean对象(也就是位置相同的bean对象)
2、两个类都实现一个接口此时若是以接口类型去接收,会直接报错,所以需要使用bean名去接收或者以类型和bean名同时接收。
<bean id="beanI" class="org.example.bean.BeanIImpl"></bean>
⑥:在测试类中接收bean对象,并调用bean对象绑定对象(BeanIImpl)的方法
1、使用创建的依赖来进行导入bean.xml(这里我使用的是我创建的bean对象的文件名,你们需要使用的是自己创建bean对象的文件名)配置
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
2、调用context中的方法getBean(),并接收bean对象调用方法。
- 方法一:使用bean名(这里的bean名就是上面创建bean对象中的id属性的值)进行接收
//以bean名(这里的bean名就是上面创建bean对象中的id属性的值)进行接受
BeanI bean = context.getBean(“main”);
//调用方法
bean.run();
- 方法二:使用类型进行接收
//以类型进行接受
BeanI bean = context.getBean(BeanI.class);
//调用方法
bean.run();
- 方法三:使用bean名和类型一起进行接收
//使用bean名和类型一起进行接收
BeanI beanI = context.getBean("beanI", BeanI.class);
//调用方法
bean.run();
2、基础属性注入
前置准备
创建一个类(在创建几个属性及方法,及实现get、set、有/无参、toString方法)方便后面进行创建。
如图:
创建xml文件,并创建bean对象,进行注入
基础注入需要的标签
property标签(用于set注入):赋值里面的两个属性:name(属性名)、value(注入属性的值)
constructor-arg标签(用于构造器注入):赋值里面的两个属性:name(属性名)、value(注入属性的值)
<!--set方法注入-->
<bean id="book" class="org.example.beanDi.Book">
<property name="author" value="chyb"></property>
<property name="bname" value="book"></property>
</bean>
<!-- 构造器方法注入-->
<bean id="book2" class="org.example.beanDi.Book">
<constructor-arg name="Bname" value="book2"></constructor-arg>
<constructor-arg name="author" value="chyb"></constructor-arg>
<!--特殊值注入(<、>)-->
<!--方法一:使用<,>-->
<!--<constructor-arg name="other" value="<>"></constructor-arg>-->
<!--方法二:使用<![CDADA[ <、> ]]>-->
<!--<constructor-arg name="other" value="<>"></constructor-arg>-->
<!--特殊值注入(null)-->
<constructor-arg name="other">
<null/>
</constructor-arg>
</bean>
测试属性注入
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
//基本属性注入
Book book = (Book) context.getBean("book");
System.out.println(book);
Object book2 = context.getBean("book2");
System.out.println(book2);
3、对象、数组注入
前置准备
在上面的准备类中在添加一个方法,在创建一个有数组属性的类
如图:
有数组属性的类
创建xml文件,并创建bean对象,进行注入
array标签:此标签是用来注入数组类型的,在此标签内可以创建几个value标签在里面赋值,每一个value就表示一个索引值。
property标签中的res属性:此属性的功能就是进行对象注入,里面传的值为你创建bean对象的名称,(注意在对象注入时,你所需要注入对象也是需要进行bean注入的)
<!-- 对象注入-->
<!-- 方法一:外部bean注入-->
<bean id="dept" class="org.example.beanDi.dept">
<property name="deptName" value="消防部"></property>
</bean>
<bean id="emp" class="org.example.beanDi.emp">
<property name="dept" ref="dept"></property>
<property name="name" value="jarry "></property>
<property name="age" value="50"></property>
</bean>
<!-- 方法二:内部bean注入-->
<bean id="dept2" class="org.example.beanDi.dept">
<property name="deptName" value="消防部"></property>
</bean>
<bean id="emp2" class="org.example.beanDi.emp">
<property name="dept">
<bean id="dept" class="org.example.beanDi.dept">
<property name="deptName" value="财务部"></property>
</bean>
</property>
<property name="name" value="jarry "></property>
<property name="age" value="50"></property>
</bean>
<!-- 方法三:级联赋值-->
<bean id="dept3" class="org.example.beanDi.dept">
<property name="deptName" value="消防部"></property>
</bean>
<bean id="emp3" class="org.example.beanDi.emp">
<property name="dept" ref="dept3"></property>
<property name="dept.deptName" value="技术研发部"></property>
<property name="name" value="jarry "></property>
<property name="age" value="50"></property>
</bean>
<!--数组类型注入-->
<bean id="deptArr" class="org.example.beanDi.dept">
<property name="deptName" value="消防部"></property>
</bean>
<bean id="empArr" class="org.example.beanDi.emp">
<property name="dept" ref="deptArr"></property>
<property name="name" value="jarry "></property>
<property name="age" value="50"></property>
<property name="hobby">
<array>
<value>篮球</value>
<value>跑步</value>
<value>睡觉</value>
</array>
</property>
</bean>
测试属性注入
//这里我是将对象、数组bean对象创建在bean-di.xml文件中
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml");
// 对象注入
// 方法一: 外部注入
emp emp = (emp)context.getBean("emp");
emp.work();
// 方法二: 内部注入
emp emp2 = (emp)context.getBean("emp2");
emp2.work();
// 方法三: 级联赋值
emp emp3 = (emp)context.getBean("emp3");
emp3.work();
// 数组注入
emp empArr = (emp)context.getBean("empArr");
System.out.println(empArr);
4、Map、List注入
前置准备
创建两个类,其中有一个类的属性类型是另外一个类,且此属性是使用List类型存储(此两个类是用来测试List注入)
创建两个类,其中有一个类的属性类型是另外一个类,且此属性是使用Map类型的value存储(此两个类是用来测试Map注入)
创建如图:
创建xml文件,并创建bean对象,进行注入
list标签:子标签是ref并赋值此标签的bean属性,也就是bean对象名
map标签:子标签是entry此标签代表着Map类型的一个键值对,其中里面也会有两个标签一个是key标签用来进行注入key值,另一个是ref标签用来赋值map类型的value值。
<!--list注入bean对象-->
<bean id="emp" class="org.example.beanDi.emp">
<property name="name" value="Tom "></property>
<property name="age" value="53"></property>
</bean>
<bean id="emp2" class="org.example.beanDi.emp">
<property name="name" value="jarry "></property>
<property name="age" value="50"></property>
</bean>
<bean id="deptList" class="org.example.beanDi.dept">
<property name="deptName" value="财务部"></property>
<property name="empList">
<list>
<ref bean="emp"></ref>
<ref bean="emp2"></ref>
</list>
</property>
</bean>
<!--map注入bean依赖-->
<bean id="teacherOne" class="org.example.beanDi.teacher">
<property name="teacherName" value="jarry"></property>
<property name="techaerId" value="10086"></property>
</bean>
<bean id="teacherTwo" class="org.example.beanDi.teacher">
<property name="teacherName" value="Tom"></property>
<property name="techaerId" value="911"></property>
</bean>
<bean id="student" class="org.example.beanDi.student">
<property name="SId" value="10001"></property>
<property name="SName" value="张三"></property>
<property name="map">
<map>
<entry>
<key>
<value>101001</value>
</key>
<ref bean="teacherOne"></ref>
</entry>
<entry>
<key>
<value>110</value>
</key>
<ref bean="teacherTwo"></ref>
</entry>
</map>
</property>
</bean>
测试属性注入
ApplicationContext context = new ClassPathXmlApplicationContext("bean-di-mapList.xml");
//list注入
dept deptList = (dept) context.getBean("deptList");
deptList.info();
// Map注入
student student = context.getBean("student", student.class);
student.log();
5、反射
什么是反射?:它允许程序在运行时(而不是编译时)动态地获取类的信息,包括类的成员变量、方法、构造函数等,并且能够动态地创建对象、调用方法和访问或修改成员变量的值。
反射代码回顾
前置准备:一个类如图:
1、在创建一个测试类获取class对象(字节码)
方法一;
Class carO1 = Car.class;
方法二;Class carO2 = new Car().getClass();
方法三;Class carO3 = Class.forName("org.example.reflect.Car");
(参数是对象的全路径)
2、对class对象进行创建对象、属性等反射操作。
//反射:
// 获取构造方法
@Test
public void test2() throws Exception{
Class carO1 = Car.class;
// 获取所有构造器
// 1、获取所有public构造器
Constructor[] publicC = carO1.getConstructors();
System.out.println("获取所有public构造器");
for(Constructor c: publicC){
System.out.println("方法名:"+c.getName()+"参数个数: "+c.getParameterCount());
}
// 2、获取所有的构造器(包括private)
Constructor[] AllC = carO1.getDeclaredConstructors();
System.out.println("获取所有构造器");
for(Constructor c: AllC){
System.out.println("方法名:"+c.getName()+"参数个数: "+c.getParameterCount());
}
// 指定有参构造创建对象
// 1、当构造器为public时
try {
Constructor c2 = carO1.getConstructor(String.class, Integer.class, String.class);
Car car1 = (Car)c2.newInstance("理想", 100, "黑色");
System.out.println(car1);
}catch (NoSuchMethodException e){
System.out.println("此构造器并不public构造器");
}
// 2、当构造器为private时
Constructor c3 = carO1.getDeclaredConstructor(String.class, Integer.class, String.class);
c3.setAccessible(true);
Car car2 = (Car)c3.newInstance("理想", 50, "蓝色");
System.out.println(car2);
}
// 3、获取属性
@Test
public void test3() throws Exception{
Class carZZ = Car.class;
Car c1 = (Car)carZZ.getDeclaredConstructor().newInstance();
// 获取所有的public属性(包括父类里面的public属性)
// Field[] fields = carZZ.getFields();
// 获取所有自身的属性和父类中的public属性(不包括父类中的private属性)
Field[] declaredFields = carZZ.getDeclaredFields();
for (Field field: declaredFields){
if (field.getName().equals("name")){
field.setAccessible(true);
field.set(c1,"五菱宏光");
}
System.out.println(field.getName());
System.out.println(c1);
}
}
// 4、获取方法
@Test
public void test4() throws Exception{
Car car = new Car("奔驰", 10, "黑色");
Class classZZ = car.getClass();
// 1、public方法
Method[] methods = classZZ.getMethods();
for (Method me:methods){
if (me.getName().equals("toString")){
String invoke = (String) me.invoke(car);
System.out.println("toString执行了:"+invoke);
}
}
// 2、private
Method[] methodeAll = classZZ.getDeclaredMethods();
for (Method m:methodeAll){
// 执行方法run
if(m.getName().equals("run")){
m.setAccessible(true);
m.invoke(car);
}
}
}
6、自动装配:@Autowired、@Resource
(这里可以看:《Springboot 三层架构(Controller(控制层)、Dao(数据访问层)、Service(业务逻辑层))》)
二、代码实现IOC
前置准备:
1、创建Service和Dao层
如图:
类内容
2、创建两个注解(一个是用来进行创建bean对象,一个用来进行属性注入)和一个实现类(用来模拟包扫描的操作)
如图:
注解内容
编写代码加解析
在AnnotationApplicationContext类中开始写关于包扫描的代码
private static String rootPath;
private Map<Class,Object> beanFactory = new HashMap<>();
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
// 创建有参构造,传递包路径,设置包扫描规则
// 当前包及其子包,那个类有@Bean注解,就把这个类通过反射实例化
public AnnotationApplicationContext(String basePackage){
try{
// 获取包的绝对路径
// 1、将传递过来的包中的 . 转换成 \
String packgePath = basePackage.replaceAll("\\.","\\\\");
// 这段代码主要用于通过当前线程的上下文类加载器来查找并获取与指定包路径(packgePath)相关的资源。这些资源可以是各种类型,比如配置文件、图片、文本文件等,具体取决于应用程序对资源的设置和存储方式。
// 简单莱索就是可以获取到路径(packgePath)上的所有资源(图片、文件、属性...),且返回来的类型是Enumeration<URL>
// Enumeration是 Java 中的一个接口,用于遍历一系列元素。在这里,这些元素是java.net.URL类型的对象。
// URL(统一资源定位符)对象用于定位互联网或本地文件系统中的资源。在这个上下文中,每个URL对象指向一个与指定包路径(packgePath)相关的资源。例如,如果packgePath是com.example.config,返回的URL对象可能指向该包路径下的配置文件、资源文件等。
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packgePath);
while (urls.hasMoreElements()){
URL url = urls.nextElement();
// 获取此路径上的文件资源(url.getFile()):
// 例如,如果 URL 是 http://example.com/path/to/file.txt?param=value,那么 getFile() 会返回 /path/to/file.txt?param=value,即包含了路径以及可能存在的查询参数等信息的字符串。
// URLDecoder.decode(String s, String enc):此方法是用来进行解码的,s是需要被解码的内容,enc是指按照指定编码格式进行解码(列如UTF-8)
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
System.out.println(filePath);
// 截取包上上一级的所有绝对路径(先进行截取后面需要此变量)
rootPath = filePath.substring(0,filePath.length() - packgePath.length());
// 包扫描(判断此包中的所有文件是否有被注解@Bean标识,若被标识则进行Bean创建,若没有则否)
loadBean(new File(rootPath));
}
}catch (IOException e){
throw new RuntimeException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 属性注入
loadDi();
}
创建bean的方法 loadBean()
// 包扫描的,实例化
public void loadBean(File filePath) throws Exception{
// 1、判断当前是否是文件
if(filePath.isDirectory()){
// 2、获取文件夹里面的所有内容
File[] childrenFiles = filePath.listFiles();
// 3、判断文件里面为空,直接返回
if (childrenFiles.length == 0 || childrenFiles == null){return;}
// 4、如果文件里面不为空,遍历文件夹中的所有内容
for (File children : childrenFiles){
//4.1遍历得到每个File对象,继续判断,如果还是文件夹,递归
if (children.isDirectory()){
loadBean(children);
}else {
// 4.2遍历得到File对象不是文件夹,是文件
// rootPath实例:/D:/桌面/spring6/spring-writerIoc/target/classes/
// 4.3、得到包路径+类名称部分-字符串截取
String pathWithClass = children.getAbsolutePath()
.substring(rootPath.length()-1);
// (这里-1是因为.getAbsolutePath()所需要的地址前面是没有:"/",所以需要进行-1,然后向children字符串中后面截取,也就是:包路径到此文件的所有路径,
// 例如:children的值为:D:\桌面\spring6\spring-writerIoc\target\classes\org\example\anno\Bean.class
// 截取后的路径为pathWithClass:org\example\anno\Bean.class)
// 4.4、判断当前文件类型是否.class(contains判断是否包含“字符串”)
if (pathWithClass.contains(".class")){
// 4.5、如果是.class类型,把路径\替换成.,把.class去掉
// org.example.Service.UserService
String allName = pathWithClass.replaceAll("\\\\", "\\.")
.replaceAll(".class", "");
//4.6.1、获取包类的Class(字节码文件)
Class<?> clazz = Class.forName(allName);
//4.6.2、判断不是接口
if (!clazz.isInterface()){
//4.6.3、判断类上面是否有注解@Bean,
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null){
//4.6.4、实例化(反射)
Object instance = clazz.getConstructor().newInstance();
// 4.7、把对象实例化之后,放到map集合beanFactory
//4.7.1、判断类如果有接口,让接口Class作为map的key值
if(clazz.getInterfaces().length > 0 ){
beanFactory.put(clazz.getInterfaces()[0],instance);
}else {
beanFactory.put(clazz,instance);
}
}
}
}
}
}
}
}
属性注入方法loadDi():
// 属性注入
private void loadDi(){
// 实例化对象在beanFactory的map集合里面
// 1、遍历beanFactory的map集合,
// 这里将beanFactory的类型(Map:映射)转换为Set(集合)类型的原因:1、利用Set的特性去重。2、方便遍历
// [注意这里的传换原理就是相当于将每一个键值对都封装成一个对象进行存储到Set变量中]
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entry:entries){
// 2、获取map集合每个对象(value),每个对象属性获取到
Object obj = entry.getValue();
//获取到对象class
Class<?> classZZ = obj.getClass();
//获取到每个对象的属性
Field[] declaredFields = classZZ.getDeclaredFields();
//3、遍历得到每个对象属性数组,得到每个属性
for (Field field:declaredFields){
//4、判断属性上面是否有@Di注解
Di annotation = field.getAnnotation(Di.class);
if (annotation != null){
//如果有私有属性,设置可以可以设置值(反射)
field.setAccessible(true);
//5、如果有@Di注解,把对象进行设置(注入)
try {
field.set(obj,beanFactory.get(field.getType()));
}catch (IllegalAccessException e){
throw new RuntimeException(e);
}
}
}
}
}
测试代码
ApplicationContext context = new AnnotationApplicationContext("org.example");
// 创建Bean测试
// 注意这里getBean里面传的参数是此类实现的接口,不要写是实现类。
UserService userService = (UserService) context.getBean(UserService.class);
System.out.println(userService);
userService.run();
// 属性注入测试
UserDao userDao = (UserDao) context.getBean(UserDao.class);
System.out.println(userDao);
userDao.add();