使用Java反射模拟实现Spring的IoC容器的操作
作者:Code0cean 发布时间:2023-06-03 17:37:42
实现的功能:
默认情况下将扫描整个项目的文件
可以使用@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方法
验证用户【张三】登录
来源:https://blog.csdn.net/huangjhai/article/details/106177527


猜你喜欢
- Java Boolean 初始化方式1、Boolean(String boolString);以字符串的方式初始化,只有当字符串是“true
- 目录前言Lottie案例尝试1. 集成依赖2. 添加 LottieAnimationView 加载网络资源3. 加载本地资源4. 循环播放
- Android MotionEvent中getX()和getRawX()的区别实例详解实例代码:public class Res exten
- 相同点:二者都是Java的虚拟机,用来执行Java程序区别:javaw.exe运行程序时不会输出控制台信息,如果是双击打开jar文件的话(假
- 前言异步调用几乎是处理高并发,解决性能问题常用的手段,如何开启异步调用?SpringBoot中提供了非常简单的方式,就是一个注解@Async
- 本文实例讲述了Java通过在主循环中判断Boolean来停止线程的方法。分享给大家供大家参考,具体如下:package Threads;/*
- 获取Android的ROOT权限其实很简单,只要在Runtime下执行命令"su"就可以了。// 获取ROOT权限pub
- 本文实例讲述了Java数组传递及可变参数操作。分享给大家供大家参考,具体如下:方法可以操作传递和返回基本数据类型,但是方法中也可用来传递和返
- 一、问题分析入门案例的内容已经做完了,在入门案例中我们创建过一个SpringMvcConfig的配置类,再回想前面咱们学习Spring的时候
- 简介因为目前做的项目查询提供的接口都使用GraphQL替代典型的REST API,所以有必要去对它进行了解和源码的阅读。本篇主要大致了解下G
- 委托定义类型,类型指定特定方法签名。可将满足此签名的方法(静态或实例)分配给该类型的变量,然后(使用适当参数)直接调用该方法,或将其作为参数
- 这篇文章主要介绍了Spring案例打印机的实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 1.通过用FTP进行上传文件,首先要实现建立FTP连接,一般建立FTP连接,需要知道FTP配置有关的信息。一般要在Bean中建立一个Serv
- LinkedList实现班级信息管理系统,供大家参考,具体内容如下代码如下:import java.util.*;public class
- RecyclerView 已经出来很久了,但是在项目中之前都使用的是ListView,最近新的项目上了都大量的使用了RecycleView.
- 背景接上文《失踪人口回归,mybatis-plus 3.3.2 发布》[1] ,提供了一个非常实用的功能 「数据安全保护」 功能,不仅支持数
- 最近在用SpringMvc做Http接口时,对方在调用我接口时发现Date格式的默认转化为long,因此在前端页面看到的是一串数字。我们可以
- 本文实例讲述了Android开发之绘制平面上的多边形功能。分享给大家供大家参考,具体如下:计算机里的3D图形其实是由很多个平面组合而成的。所
- 本文将引导您完成 2 个示例,演示如何在 Flutter 中获取设备标识符使用 platform_device_id如果您只需要运行应用程序
- 归并排序简单解释:该算法是采用分治法,把数组不断分割,直至成为单个元素,然后比较再合并(合并的过程就是两部分分别从头开始比较,取出最小或最大