Spring笔记三(自定义IOC)
温馨提示:
本文最后更新于 2023年11月30日,已超过 565 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我。
IOC是基于java的反射机制实现的。
一. 反射核心
Animal类
public class Animal {
//动物名
private String animalName;
//动物年龄
private int animalAge;
//动物颜色
public String animalColor;
//无参构造
public Animal() {
}
//全参构造
private Animal(String animalName, int animalAge, String animalColor) {
this.animalName = animalName;
this.animalAge = animalAge;
this.animalColor = animalColor;
}
//私有方法
private void showAnimal(){
System.out.println("展示动物:"+this.animalName);
}
@Override
public String toString() {
return "Animal{" +
"animalName='" + animalName + '\'' +
", animalAge=" + animalAge +
", animalColor='" + animalColor + '\'' +
'}';
}
public String getAnimalName() {
return animalName;
}
public void setAnimalName(String animalName) {
this.animalName = animalName;
}
public int getAnimalAge() {
return animalAge;
}
public void setAnimalAge(int animalAge) {
this.animalAge = animalAge;
}
public String getAnimalColor() {
return animalColor;
}
public void setAnimalColor(String animalColor) {
this.animalColor = animalColor;
}
}
1.1.获取Class的多种方式
- 类.class
- 对象.getClass
- Class.forName("全路径")
@Test
public void testClass() throws Exception {
Class clazz1 = Animal.class;
Animal animal = new Animal();
Class clazz2 = animal.getClass();
Class clazz3 = Class.forName("com.mhld.manage.animal.Animal");
//利用class进行对象创建
Animal animalTemp = (Animal)clazz3.getDeclaredConstructor().newInstance();
System.out.println(animalTemp);
}
1.2.获取构造方法
- class.getConstructors 只能获取public的构造方法
- class.getDeclaredConstructors 可获取所有的构造方法
@Test
public void testConstructor() {
Class clazz = Animal.class;
//getConstructors,只能获取public的构造方法
Constructor[] constructors = clazz.getConstructors();
for (Constructor con : constructors) {
System.out.println("构造方法名:"+con.getName()+"--- 参数个数="+con.getParameterCount());
}
System.out.println("分割线===========================");
//getDeclaredConstructors 可获取所有的构造方法
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor con : declaredConstructors) {
System.out.println("构造方法名:"+con.getName()+"--- 参数个数="+con.getParameterCount());
}
}
1.3.获取属性值
- class.getFields() 只能获取到public 属性
- class.declaredFields() 可以获取到所有属性
@Test
public void testFiles() throws Exception{
Animal animal = new Animal();
Class clazz = animal.getClass();
//只能获取到public 属性
Field[] fields = clazz.getFields();
for (Field file : fields) {
if (file.getName().equals("animalColor")){
file.set(animal,"灰色");
}
System.out.println("属性名==="+file.getName());
}
System.out.println(animal);
System.out.println("分割线===========================");
//可以获取到所有属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field file : declaredFields) {
//在对私有属性值进行赋值时,需要设置字段可操作
if (file.getName().equals("animalName")){
file.setAccessible(true);
file.set(animal,"老虎");
}
System.out.println("属性名==="+file.getName());
}
System.out.println(animal);
}
1.4.获取方法
- clazz.getMethods();
- clazz.getDeclaredMethods();
@Test
public void testMethod() throws Exception{
Class clazz = Animal.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class,int.class,String.class);
declaredConstructor.setAccessible(true);
Animal animal = (Animal) declaredConstructor.newInstance("老虎",2,"黄色");
//只能获取到public 方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("方法名==="+method.getName());
}
System.out.println("分割线===========================");
//可以获取到所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.getName().equals("showAnimal")){
//私有方法需要设置可允许操作
method.setAccessible(true);
//使用invoke调用方法,入参为 调用方法的对象和对应参数(没有可以不填)
method.invoke(animal);
}
System.out.println("方法名==="+method.getName());
}
}
二. 手写IOC
模拟Controller注入service,调用service方法。service注入dao,调用dao方法
2.1.准备
2.1.1. 自定义MyBean注解和MyDi注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyBean {
}
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDi {
}
2.1.2.dao、service、controller
public interface UserDao {
void testUser();
}
@MyBean
public class UserDaoImpl implements UserDao{
@Override
public void testUser() {
System.out.println("Dao执行了");
}
}
public interface UserService {
void testUser();
}
@MyBean
public class UserServiceImpl implements UserService{
@MyDi
private UserDao userDao;
@Override
public void testUser() {
System.out.println("UserService执行了");
userDao.testUser();
}
}
@MyBean
public class UserController {
@MyDi
private UserService userService;
public void testCustom(){
System.out.println("UserController执行了。。。。。。。。。");
userService.testUser();
}
}
2.1.3.容器
容器接口,实现类,在实现类中实现IOC和DI的工作
public interface MyApplicationContext {
Object getObject(Class clazz);
}
public class MyApplicationContextImpl implements MyApplicationContext{
//存放Bean实例
private Map<Class,Object> beanFactoryMap = new HashMap<>();
private String absolutePath;
//通过class获取实例
@Override
public Object getObject(Class clazz) {
return beanFactoryMap.get(clazz);
}
}
2. 实现IOC和DI的工作
初始化构造器,传入需要扫描的包
处理包路径为file文件路径(用到File进行文件扫描处理)
扫描对象
遍历file文件,如果该文件是文件夹,则进入迭代
筛选出class文件
获取class文件的文件路径
将文件路径转为包路径
使用反射获取到Class
判断Class是否有自定义的MyBean注解,如果有,则利用反射生成单例,放到map中
此时的自定义注解是放在实现类上的,使用Di注入的时候,对象属性是接口,所以需要获取到该实现类的接口,将KEY设置为接口Class
处理依赖注入
- 遍历map
- 获取到map中每个对象的class
- 获取class的field列表
- 遍历field列表
- 判断field是否有MyDi注解
- 如果有注解,则获取到该field的Class,通过class从map中取出对象,通过set方法设置到当前遍历对象中
完整代码:
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
public class MyApplicationContextImpl implements MyApplicationContext{
//存放Bean实例
private Map<Class,Object> beanFactoryMap = new HashMap<>();
private String absolutePath;
//通过class获取实例
@Override
public Object getObject(Class clazz) {
return beanFactoryMap.get(clazz);
}
//初始化IOC容器,通过指定的包进行路径扫描及实例生成
public MyApplicationContextImpl(String basePackage) {
/*
* 1. basePackage包路径分割符号是. 需要替换为\
* 2. 获取相对路径,加上basePackage进行全路径扫描
* 3. 处理扫描到的文件
* 3.1 筛选出.class文件
* 3.2 获取到该class文件的全路径(利用反射获取到class)
* 3.3 判断class是否有自定义的注解(@MyBean),如果有,则生成实例,放到实例工厂中
*/
try {
basePackage = basePackage.replaceAll("\\.","\\\\");
//通过线程方法获取到当前的URLS
Enumeration<URL> urls =
Thread.currentThread().getContextClassLoader().getResources(basePackage);
//处理绝对路径
while (urls.hasMoreElements()){
URL url = urls.nextElement();
//使用utf8解码,不然会乱码
String path = URLDecoder.decode(url.getPath(), "utf-8");
absolutePath = path.substring(1,path.length()-basePackage.length());
}
//替换路径的/ 为\,后续file对象获取的路径都是xx\xx\ 便于替换,处理成包路径
absolutePath = absolutePath.replaceAll("/","\\\\");
//包路径下的class文件处理,对象创建
handleFile(new File(absolutePath));
//处理依赖注入
handelDi();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//处理包路径下的文件
public void handleFile(File file){
if (file.isFile()){
String name = file.getName();
//过滤class文件
if (name.contains(".class")){
//获取当前文件的绝对路径 xxxx\xxx\xxx
String absoluteFilePath = file.getAbsolutePath();
//替换父工程绝对路径
absoluteFilePath = absoluteFilePath.replace(absolutePath,"");
//替换到.class 将\替换为. 得到包路径
absoluteFilePath = absoluteFilePath.replace(".class","").replaceAll("\\\\","\\.");
try {
//获取到clazz,判断是否有自定义注解MyBean
Class clazz = Class.forName(absoluteFilePath);
if (clazz.isAnnotationPresent(MyBean.class)){
//如果有注解,生成单实例
Object o = clazz.getDeclaredConstructor().newInstance();
//获取实现的接口
Class[] interfaces = clazz.getInterfaces();
//如果有实现的接口,KEY就使用第一个接口class,如果没有,就放当前的class
//基础示例,后期可以根据需求进行变化
if (interfaces.length>0){
beanFactoryMap.put(interfaces[0],o);
}else {
beanFactoryMap.put(clazz,o);
}
}
}catch (Exception e){
e.printStackTrace();
System.out.println("获取类错误");
}
}
}else {
//如果是文件夹,就进行递归
File[] files = file.listFiles();
for (File fileTemp : files) {
handleFile(fileTemp);
}
}
}
//处理依赖注入
public void handelDi(){
//遍历实例map
for (Map.Entry<Class, Object> entry : beanFactoryMap.entrySet()){
//获取到对象的class
Class clazz = entry.getValue().getClass();
//获取属性列表
Field[] declaredFields = clazz.getDeclaredFields();
if (declaredFields.length>0){
for (Field filed : declaredFields) {
//判断是否有依赖注入属性
MyDi annotation = filed.getAnnotation(MyDi.class);
if (null!=annotation){
//如果有,就获取到当前字段的clazz
Class filedClazz = filed.getType();
//从map中获取到对象
Object beanObj = beanFactoryMap.get(filedClazz);
//设置字段为可操作
filed.setAccessible(true);
try {
//将值自动设置到当前遍历对象中
filed.set(entry.getValue(),beanObj);
} catch (IllegalAccessException e) {
System.out.println("依赖注入值设置错误");
throw new RuntimeException(e);
}
}
}
}
}
}
}
3.测试
@Test
public void testDi(){
MyApplicationContext context = new MyApplicationContextImpl("com.xx.xx");
UserController userController = (UserController) context.getObject(UserController.class);
userController.testCustom();
}
UserController执行了。。。。。。。。。
UserService执行了
Dao执行了
正文到此结束
- 本文标签: Java Spring
- 本文链接: https://www.tianyajuanke.top/article/76
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权