Spring框架的IOC是基于Java反射机制实现的,先回顾一下java反射
1.Java反射
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。
要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect,所以,Class对象是反射的根源。
自定义类
package com.atguigu.reflect;
public class Car {
//属性
private String name;
private int age;
private String color;
//无参数构造
public Car() {
}
//有参数构造
public Car(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
//普通方法
private void run() {
System.out.println("私有方法-run.....");
}
//get和set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
'}';
}
}
编写测试类
package com.atguigu.reflect;
import jdk.nashorn.internal.runtime.ECMAException;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLOutput;
public class TestCar {
//1.获取Class对象的多种方式 Class对象:字节码文件
@Test
public void test01() throws Exception{
//1 类名.class
Class clazz1=Car.class;
//2 对象.getClass();
Class clazz2=new Car().getClass();
//3.Class.forName("全路径“);
Class clazz3=Class.forName("com.atguigu.reflect.Car");
//实例化
Car car=(Car)clazz3.getDeclaredConstructor().newInstance();
System.out.println(car);
}
//2.获取构造方法
@Test
public void test02() throws Exception{
Class clazz=Car.class;
//获取所有构造 getConstructors()获取所有public的构造方法
/* Constructor[] constructors=clazz.getConstructors();*/
//getDeclaredConstructors()获取所有public private的构造方法
Constructor[] constructors=clazz.getDeclaredConstructors();
for(Constructor c :constructors){
System.out.println("构造方法的名称:"+c.getName()+ "参数个数:" +c.getParameterCount());
}
//指定有参数构造创建对象
//1.构造是public
// Constructor c1= clazz.getConstructor(String.class,int.class,String.class);
// Car car1=(Car)c1.newInstance("夏利",10,"红色");
// System.out.println(car1);
//2.构造是private
Constructor c2= clazz.getDeclaredConstructor(String.class,int.class,String.class);
c2.setAccessible(true);//设置私有数据是可以访问的
Car car2=(Car)c2.newInstance("五五",15,"红色");
System.out.println(car2);
}
//3.获取属性
@Test
public void test03() throws Exception{
Class clazz=Car.class;
Car car=(Car)clazz.getDeclaredConstructor().newInstance();
//获取所有public属性
// Field[] fields=clazz.getFields();
//获取所有的属性(私有属性)
Field[] fields=clazz.getDeclaredFields();
for(Field field:fields){
if(field.getName().equals("name")){
//设置允许访问
field.setAccessible(true);
field.set(car,"五菱宏光");
}
System.out.println(field.getName());
System.out.println(car);
}
}
//4.获取方法
@Test
public void test04() throws Exception{
Car car=new Car("奔驰",10,"黑色");
Class clazz=car.getClass();
// 1 public方法
Method[] methods=clazz.getMethods();
for(Method m1:methods){
// System.out.println(m1.getName());
//执行方法
if(m1.getName().equals("toString")) {
String invoke=(String) m1.invoke(car);
System.out.println("toString方法执行了:"+invoke);
}
}
// 2 private方法
Method[] methodsAll=clazz.getDeclaredMethods();
for(Method m:methodsAll){
if(m.getName().equals("run")){
m.setAccessible(true);
m.invoke(car);
}
}
}
}
2.实现Spring的IoC
2.1 添加依赖
<dependencies>
<!--junit5测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>
2.2 准备测试需要的bean
IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,下面我们就一步一步手写出Spring框架最核心的部分。
创建UserDao接口
package com.atguigu.dao;
public interface UserDao {
void add();
}
创建UserDaoImpl实现
package com.atguigu.dao.Impl;
import com.atguigu.anno.Bean;
import com.atguigu.dao.UserDao;
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao.......");
}
}
创建UserService接口
package com.atguigu.service;
import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;
public interface UserService {
void add();
}
创建UserServiceImpl实现类
package com.atguigu.service.impl;
import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;
import com.atguigu.dao.UserDao;
import com.atguigu.service.UserService;
@Bean
public class UserServiceImpl implements UserService {
@Di
private UserDao userDao;
@Override
public void add() {
System.out.println("service......");
//调用dao里面的方法
userDao.add();
}
}
2.3 定义注解
通过注解的形式加载bean与实现依赖注入
bean注解@bean,用来创建对象
package com.atguigu.anno;
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 Bean {
}
依赖注入注解@Di,用于注入属性
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//注入属性
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
2.4 定义bean容器接口
package com.atguigu.spring.core;
public interface ApplicationContext {
Object getBean(Class clazz);
}
2.5 编写注解bean容器接口实现
AnnotationApplicationContext基于注解扫描bean,我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下:
package com.atguigu.bean;
import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.SQLOutput;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AnnotationApplicationContext implements ApplicationContext{
//创建一个map集合,放bean的实例对象
private Map<Class,Object> beanFactory=new HashMap<>();
private static String rootPath;
//返回对象
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
//创建有参数构造,传递包的路径,设置包的扫描规则
//当前包及其子包,那个类有@Bean的注解,把这个类通过反射进行实例化
public AnnotationApplicationContext(String basePackage) throws Exception {
try {
String packageDirName =
basePackage.replaceAll("\\.", "\\\\");
Enumeration<URL> dirs =
Thread.currentThread().getContextClassLoader().
getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面路径的部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packageDirName.length());
//包的扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
2.6 java类标识Bean注解
@Bean
public class UserServiceImpl implements UserService
@Bean
public class UserDaoImpl implements UserDao
2.7 依赖注入实现
package com.atguigu.bean;
import com.atguigu.anno.Bean;
import com.atguigu.anno.Di;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.sql.SQLOutput;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class AnnotationApplicationContext implements ApplicationContext{
//创建一个map集合,放bean的实例对象
private Map<Class,Object> beanFactory=new HashMap<>();
private static String rootPath;
//返回对象
@Override
public Object getBean(Class clazz) {
return beanFactory.get(clazz);
}
//创建有参数构造,传递包的路径,设置包的扫描规则
//当前包及其子包,那个类有@Bean的注解,把这个类通过反射进行实例化
public AnnotationApplicationContext(String basePackage) throws Exception {
try {
String packageDirName =
basePackage.replaceAll("\\.", "\\\\");
Enumeration<URL> dirs =
Thread.currentThread().getContextClassLoader().
getResources(packageDirName);
while (dirs.hasMoreElements()) {
URL url = dirs.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
//获取包前面路径的部分,字符串截取
rootPath = filePath.substring(0, filePath.length() - packageDirName.length());
//包的扫描
loadBean(new File(filePath));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
loadDi();
}
//包扫描的过程,实例化
private void loadBean(File fIle) throws Exception {
//1 判断当前的内容是否个文件夹
if(fIle.isDirectory()){
// 2 获取文件夹里面的所有内容
File[] childrenfiles = fIle.listFiles();
// 3 判断文件夹内容为空,直接返回
if(childrenfiles==null||childrenfiles.length==0){
return;
}
//4 如果文件夹不为空,遍历文件夹的所有内容
for(File child:childrenfiles){
// 4.1 遍历得到每个File对象,继续判断,如果还是一个文件夹,递归
if(child.isDirectory()){
//递归
loadBean(child);
}else {
// 4.2 遍历得到的File对象不是一个文件夹,是一个文件,
// 4.3得到包的路径+类的名称-字符串的截取过程
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
//4.4 判断当前文件是否是.class类型
if(pathWithClass.contains(".class")){
// 4.5 如果是.class 类型,把路径\替换成. 把.class去掉
//com.atguigu.service.UserServiceImpl
String allName = pathWithClass.replaceAll("\\\\", ".")
.replace(".class", "");
//4.6 判断类上面是否有@bean的注解,如果有进行实例化过程
//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作为key,如果没有接口,自己的class作为key
if(clazz.getInterfaces().length>0){
beanFactory.put(clazz.getInterfaces()[0],instance);
}else {
beanFactory.put(clazz,instance);
}
}
}
}
}
}
}
}
private void loadDi() throws Exception {
//实例化对象在beanFactory的map集合中
//1.遍历beanFactory的map集合
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class,Object> entry:entries) {
//2. 获取map集合中的每个对象(value),每个对象的属性获取到
Object obj = entry.getValue();
//获取对象的class
Class<?> clazz = obj.getClass();
//获取每个对象中的属性
Field[] declaredFields = clazz.getDeclaredFields();
//3. 遍历得到的每个对象属性数组,得到每个属性
for(Field field:declaredFields){
//4. 判断属性上面是否有@Di注解
Di annotation = field.getAnnotation(Di.class);
if(annotation!=null){
//属性如果是私有的,设置可以设置值
field.setAccessible(true);
//5. 如果有@Di注解,把对象设置(注入)
field.set(obj,beanFactory.get(field.getType()));
}
}
}
}
}
2.8 实现属性注入
@Di
private UserDao userDao;
如果不写依赖注入的@Di注解,那么执行测试程序会报错,因为当前userDao是个空对象,但是写完依赖注解的@Di后,即可以正常测试和使用。