使用Java反射模拟实现Spring的IoC容器的操作

 更新时间:2021年8月16日 12:00  点击:2219

实现的功能:

  • 默认情况下将扫描整个项目的文件
  • 可以使用@ComponentScan注解配置扫描路径
  • 只将被@Component注解修饰的类装载到容器中
  • 可以使用@AutoWired注解实现自动装配
  • 读取配置文件中的声明的类并注册到容器中

项目结构

下面是程序的项目结构图:

在这里插入图片描述

自定义注解

下面是自定义的三个注解: @AutoWired,@Component,@ComponentScan。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {
  String[] value();
}

容器实现

其中AnnotationConfigApplicationContext和ClassPathXMLApplicationContext为核心的类,其中

AnnotationConfigApplicationContext类实现扫描文件和解析注解等功能。

package learn.reflection.reflect;
import learn.reflection.Bootstrap;
import learn.reflection.annotation.AutoWired;
import learn.reflection.annotation.Component;
import learn.reflection.annotation.ComponentScan;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class AnnotationConfigApplicationContext<T>{
  //使用HaspMap存储Bean
  private HashMap<Class,Object> beanFactory=new HashMap<>();
  //获取Bean的方法
  public T getBean(Class clazz){
    return (T) beanFactory.get(clazz);
  }
  String path;//编译后的字节码存储路径
  /**
   * 初始化ApplicationContext,加载注解修饰的Bean到beanFactory
   */
  public void initContextByAnnotation(){
    //编译后的项目根目录:D:/idea_workplace/javaAppliTechnology/target/classes/
    path = AnnotationConfigApplicationContext.class.getClassLoader().getResource("").getFile();
    //查看启动类Bootstrap是否有定义扫描包
    ComponentScan annotation = Bootstrap.class.getAnnotation(ComponentScan.class);
    if (annotation!=null){
      //有定义就只扫描自定义的
      String[] definedPaths = annotation.value();
      if (definedPaths!=null&&definedPaths.length>0){
        loadClassInDefinedDir(path,definedPaths);
      }
    }else{
      //默认扫描整个项目的目录
      System.out.println(path);
      findClassFile(new File(path));
    }
    assembleObject();
  }
  /**
   * 给@AutoWired修饰的属性赋值
   */
  private void assembleObject(){
    Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
    //扫描所有容器中的Bean
    for (Map.Entry<Class, Object> entry : entries) {
      Object value = entry.getValue();
      //获取所有属性
      Field[] fields = value.getClass().getDeclaredFields();
      for (Field field : fields) {
        //如果被@AutoWired注解修饰则进行赋值
        AutoWired annotation = field.getAnnotation(AutoWired.class);
        if (annotation!=null){
          try {
            field.setAccessible(true);
            field.set(value,beanFactory.get(field.getType()));
          } catch (IllegalAccessException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }
  /**
   * 扫描用户自定义的包
   * @param path
   * @param definedPaths
   */
  private void loadClassInDefinedDir(String path, String[] definedPaths){
    for (String definedPath : definedPaths) {
      //转换成绝对路径
      String s = definedPath.replaceAll("\\.", "/");
      String fullName=path+s;
      System.out.println(s);
      findClassFile(new File(fullName));
    }
  }
  /**
   * 扫描项目中的每一个文件夹找到所有的class文件
   */
  private void findClassFile(File pathParent) {
    //路径是否是目录,子目录是否为空
    if (pathParent.isDirectory()) {
      File[] childrenFiles = pathParent.listFiles();
      if (childrenFiles == null || childrenFiles.length == 0) {
        return;
      }
      for (File childrenFile : childrenFiles) {
        if (childrenFile.isDirectory()) {
          //递归调用直到找到所有的文件
          findClassFile(childrenFile);
        } else {
          //找到文件
          loadClassWithAnnotation(childrenFile);
        }
      }
    }
  }
  /**
   *   装配找到的所有带有@Component注解的类到容器
   */
  private void loadClassWithAnnotation(File file) {
    //1.去掉前面的项目绝对路径
    String pathWithClass=file.getAbsolutePath().substring(path.length()-1);
    //2.将路径的“/”转化为“.”和去掉后面的.class
    if (pathWithClass.contains(".class")){
      String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
      /**
       *  根据获取到的类的全限定名使用反射将实例添加到beanFactory中
       */
      try {
        Class<?> clazz = Class.forName(fullName);
        //3.判断是不是接口,不是接口才创建实例
        if (!clazz.isInterface()){
          //4.是否具有@Bean注解
          Component annotation = clazz.getAnnotation(Component.class);
          if (annotation!=null){
            //5.创建实例对象
            Object instance = clazz.newInstance();
            //6.判断是否有实现的接口
            Class<?>[] interfaces = clazz.getInterfaces();
            if (interfaces!=null&&interfaces.length>0){
              //如果是有接口就将其接口的class作为key,实例对象作为value
              System.out.println("正在加载【"+interfaces[0].getName()+"】 实例对象:"+instance.getClass().getName());
              beanFactory.put(interfaces[0],instance);
            }else{
              System.out.println("正在加载【"+clazz.getName()+"】 实例对象:"+instance.getClass().getName());
              beanFactory.put(clazz,instance);
            }
            //如果没有接口就将自己的class作为key,实例对象作为value
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

ClassPathXMLApplicationContext类实现解析xml配置文件,并装载组件到容器中。

package learn.reflection.reflect;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.jdom2.Document;
import org.jdom2.JDOMException;
import org.jdom2.Element;
import org.jdom2.xpath.XPath;
import org.jdom2.input.SAXBuilder;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
/**
 * @author Hai
 * @date 2020/5/17 - 18:47
 */
public class ClassPathXMLApplicationContext{
  private File file;
  private Map<String,Object> map = new HashMap();
  public ClassPathXMLApplicationContext(String config_file) {
    URL url = this.getClass().getClassLoader().getResource(config_file);
    try {
      file = new File(url.toURI());
      XMLParsing();
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  private void XMLParsing() throws Exception {
    SAXBuilder builder = new SAXBuilder();
    Document document = builder.build(file);
    Element root = document.getRootElement();
    List elementList = root.getChildren("bean");
    Iterator i = elementList.iterator();
    //读取bean节点的所有信息
    while (i.hasNext()) {
      Element bean = (Element) i.next();
      String id = bean.getAttributeValue("id");
      //根据class创建实例
      String cls = bean.getAttributeValue("class");
      Object obj = Class.forName(cls).newInstance();
      Method[] method = obj.getClass().getDeclaredMethods();
      List<Element> list = bean.getChildren("property");
      for (Element el : list) {
        for (int n = 0; n < method.length; n++) {
          String name = method[n].getName();
          String temp = null;
          //找到属性对应的setter方法进行赋值
          if (name.startsWith("set")) {
            temp = name.substring(3, name.length()).toLowerCase();
            if (el.getAttribute("name") != null) {
              if (temp.equals(el.getAttribute("name").getValue())) {
                method[n].invoke(obj, el.getAttribute("value").getValue());
              }
            }
          }
        }
      }
      map.put(id, obj);
    }
  }
  public Object getBean(String name) {
    return map.get(name);
  }
}

测试

实体类User的定义:

@Component
public class User {
  private String username;
  private String password;
  
  public User(String username, String password) {
    this.username = username;
    this.password = password;
  }
  public User() {
  }
  //省略getter,setter方法
  }

在UserServiceImpl类中添加@Component注解,并使用@AutoWired注解注入容器中的IUerDao接口的实现类UserDaoImpl。

@Component
public class UserServiceImpl implements IUserService {
  @AutoWired
  private IUserDao userDao;
  @Override
  public void login(User user) {
    System.out.println("调用UserDaoImpl的login方法");
    userDao.loginByUsername(user);
  }
}

UserDaoImpl类同样添加@Component注解

@Component
public class UserDaoImpl implements IUserDao {
  @Override
  public void loginByUsername(User user) {
    System.out.println("验证用户【"+user.getUsername()+"】登录");
  }
}

在beans.xml中配置注册User类,文件beans.xml的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="user" class="learn.reflection.entity.User">
        <property name="username" value="张三" />
        <property name="password" value="123" />
    </bean>
</beans>

下面同时使用 AnnotationConfigApplicationContext类和 ClassPathXMLApplicationContext类。

Bootstrap类作为启动类添加注解@ComponentScan,指定扫描learn.reflection.dao和learn.reflection.service这两个包。

@ComponentScan(value = {"learn.reflection.dao","learn.reflection.service"})
public class Bootstrap {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
    applicationContext.initContextByAnnotation();
    UserServiceImpl userService = (UserServiceImpl) applicationContext.getBean(IUserService.class);
    ClassPathXMLApplicationContext xmlApplicationContext = new ClassPathXMLApplicationContext("beans.xml");
    User user = (User) xmlApplicationContext.getBean("user");
    System.out.println(user);
    userService.login(user);
  }
}

运行Bootstrap类,程序运行结果如下:

learn/reflection/dao
正在加载【learn.reflection.dao.IUserDao】 实例对象:learn.reflection.dao.impl.UserDaoImpl
learn/reflection/service
正在加载【learn.reflection.service.IUserService】 实例对象:learn.reflection.service.impl.UserServiceImpl
User{username='张三', password='123'}
调用UserDaoImpl的login方法
验证用户【张三】登录

以上为个人经验,希望能给大家一个参考,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • Java实现经典游戏复杂迷宫

    这篇文章主要介绍了如何利用java语言实现经典《复杂迷宫》游戏,文中采用了swing技术进行了界面化处理,感兴趣的小伙伴可以动手试一试...2022-02-01
  • java 运行报错has been compiled by a more recent version of the Java Runtime

    java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)...2021-04-01
  • 在java中获取List集合中最大的日期时间操作

    这篇文章主要介绍了在java中获取List集合中最大的日期时间操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • Spring AOP 对象内部方法间的嵌套调用方式

    这篇文章主要介绍了Spring AOP 对象内部方法间的嵌套调用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-08-29
  • Spring Cloud 中@FeignClient注解中的contextId属性详解

    这篇文章主要介绍了Spring Cloud 中@FeignClient注解中的contextId属性详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-09-25
  • Springboot如何实现Web系统License授权认证

    这篇文章主要介绍了Springboot如何实现Web系统License授权认证,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-28
  • 教你怎么用Java获取国家法定节假日

    这篇文章主要介绍了教你怎么用Java获取国家法定节假日,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-23
  • Java如何发起http请求的实现(GET/POST)

    这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
  • 浅谈Java与C#的一些细微差别

    说起C#和Java这两门语言(语法,数据类型 等),个人以为,大概有90%以上的相似,甚至可以认为几乎一样。但是在工作中,我也发现了一些细微的差别...2020-06-25
  • 解决Java处理HTTP请求超时的问题

    这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
  • Android模拟器上模拟来电和短信配置

    如果我们的项目需要做来电及短信的功能,那么我们就得在Android模拟器开发这些功能,本来就来告诉我们如何在Android模拟器上模拟来电及来短信的功能。 在Android模拟...2016-09-20
  • 夜神android模拟器设置代理的方法

    夜神android模拟器如何设置代理呢?对于这个问题其实操作起来是非常的简单,下面小编来为各位详细介绍夜神android模拟器设置代理的方法,希望例子能够帮助到各位。 app...2016-09-20
  • 如何在Spring WebFlux的任何地方获取Request对象

    这篇文章主要介绍了如何在Spring WebFlux的任何地方获取Request对象,帮助大家更好的理解和使用springboot框架,感兴趣的朋友可以了解下...2021-01-26
  • java 判断两个时间段是否重叠的案例

    这篇文章主要介绍了java 判断两个时间段是否重叠的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-15
  • 详解SpringCloudGateway内存泄漏问题

    这篇文章主要介绍了详解SpringCloudGateway内存泄漏问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-16
  • PHP函数分享之curl方式取得数据、模拟登陆、POST数据

    废话不多说直接上代码复制代码 代码如下:/********************** curl 系列 ***********************///直接通过curl方式取得数据(包含POST、HEADER等)/* * $url: 如果非数组,则为http;如是数组,则为https * $header:...2014-06-07
  • C#模拟http 发送post或get请求的简单实例

    下面小编就为大家带来一篇C#模拟http 发送post或get请求的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 超简洁java实现双色球若干注随机号码生成(实例代码)

    这篇文章主要介绍了超简洁java实现双色球若干注随机号码生成(实例代码),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-04-02
  • java 画pdf用itext调整表格宽度、自定义各个列宽的方法

    这篇文章主要介绍了java 画pdf用itext调整表格宽度、自定义各个列宽的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-01-31
  • Java生成随机姓名、性别和年龄的实现示例

    这篇文章主要介绍了Java生成随机姓名、性别和年龄的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-01