大家在spring框架中都知道加注解,还有ioc容器,还有一开始就会加载配置类,然后自动创建对象放入ioc中,这些是怎么完成的呢?
下面我们来自己定义一下spring:
先来创建主启动类:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.liyunmiaocontext;
public class Test {
public static void main(String[] args) {
liyunmiaocontext context = new liyunmiaocontext(configclass.class);
Userservice userservice=(Userservice) context.getBean("Userservice");
}
}
liyunmiaocontext就是容器类:
java
package com.code.liyunmiao.spring;
import com.code.liyunmiao.service.Userservice;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class liyunmiaocontext {
private Class configclass;
public liyunmiaocontext(Class configclass)
{
this.configclass=configclass;
if(configclass.isAnnotationPresent(ComponentScan.class))
{
ComponentScan componentScan=(ComponentScan)configclass.getAnnotation(ComponentScan.class);
String value=componentScan.value();
}
}
public Object getBean(String beanname) {
return null;
}
}
当在主启动类里面run的时候也就是 liyunmiaocontext context = new liyunmiaocontext(configclass.class); 这一步,就是相当于new这个容器,然后也会正常的去执行他的构造函数也就是对应类的构造函数就会执行(以及父类构造函数链)。
再看配置类:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.ComponentScan;
@ComponentScan("com.code.liyunmiao.service")
public class configclass {
}
配置类上面componentscan决定了要扫描哪些目录文件下。
自定义component注解:
java
package com.code.liyunmiao.spring;
public @interface Component {
String value() default "";
}
自定义componentscan注解:
java
package com.code.liyunmiao.spring;
public @interface ComponentScan {
String value() default "";
}
userService类:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.Component;
@Component("Userservice")
public class Userservice {
}
下面直接来看完整版:
这是容器:
java
package com.code.liyunmiao.spring;
import com.code.liyunmiao.service.Definationbean;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.security.KeyStore;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 手写版「应用上下文」:从配置类读取 {@link ComponentScan},在 classpath 上找到对应包目录,
* 遍历其中的 .class,再用 {@link Component} 判断是否注册为 Bean,并把「Bean 定义」放进 map(思路接近 Spring 的组件扫描)。
*/
public class liyunmiaocontext {
/** 传入的配置类(如 configclass.class),作为读取 @ComponentScan 的入口。 */
private Class configclass;
/**
* Bean 定义表:key 为 {@link Component#value()}(Bean 名称),value 为封装了类型、作用域的 {@link Definationbean}。
* 扫描阶段只登记定义,真正创建实例在 {@link #getBean(String)} 中按作用域处理。
*/
private final ConcurrentHashMap<String, Definationbean> map = new ConcurrentHashMap<>();
/**
* 单例 Bean 的已创建实例缓存(仅当 {@link Definationbean#getScope()} 为 singleton 时使用)。
*/
private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();
public liyunmiaocontext(Class configclass) {
this.configclass = configclass;
// 仅在配置类上声明了 @ComponentScan 时才做包扫描;注解需带 @Retention(RUNTIME) 才能在运行时被反射读到。
if (configclass.isAnnotationPresent(ComponentScan.class)) {
// 取出 @ComponentScan("com.xxx.yyy") 的 value:要扫描的根包(点分包名)。
ComponentScan componentScan = (ComponentScan) configclass.getAnnotation(ComponentScan.class);
String path = componentScan.value();
// ClassLoader#getResource 使用「目录式」路径:包名中的 . 换成 /,对应磁盘上 com/xxx/yyy 层级。
path = path.replace('.', '/');
// 用加载本类的 ClassLoader,在 classpath 上解析该包对应的 URL,再转成 File 以便列目录。
ClassLoader classLoader = liyunmiaocontext.class.getClassLoader();
// 部分环境下目录资源需以 / 结尾才能解析到 URL,故先尝试 path,再尝试 path/。
URL resource = classLoader.getResource(path);
if (resource == null && !path.endsWith("/")) {
resource = classLoader.getResource(path + "/");
}
if (resource == null) {
throw new IllegalStateException("classpath 上未找到包目录,请检查 @ComponentScan 的 value 是否与编译输出目录一致: " + path);
}
File file = new File(resource.getFile());
// 若是真实目录(开发时常见:target/classes 下),可列出该包内所有条目;若在 jar 内则需另行处理(JarURLConnection 等)。
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files == null) {
return;
}
// 只遍历当前包目录下「一层」条目;子包(子目录)需递归或其它扫描方式才能覆盖。
for (File f : files) {
String filename = f.getAbsolutePath();
if (filename.endsWith(".class")) {
// filename 是绝对路径(如 ...\classes\com\code\...\Foo.class)。
// ClassLoader#loadClass 需要的是 JVM「二进制类名」(如 com.code.xxx.Foo),不是磁盘路径;
// 此处从路径中截取从 "com" 开始到 .class 前的片段,再替换为点分全限定名。
// 注意:依赖路径中首次出现的 "com" 即包名起点;包名不以 com 开头或路径含多个 com 时可能截错。
String classname = filename.substring(filename.indexOf("com"), filename.lastIndexOf(".class"));
classname = classname.replace('\\', '.');
try {
// 变量名仅作占位;此处得到的是「类元数据」,后续用其读 @Component、@Scope。
Class<?> Class = classLoader.loadClass(classname);
// 仅当类上标了 @Component 时,才登记为 Bean 定义。
if (Class.isAnnotationPresent(Component.class)) {
Component component = Class.getAnnotation(Component.class);
String beanname = component.value();
Definationbean definationbean = new Definationbean();
definationbean.setType(Class);
// 若有 @Scope 则取注解值,否则与 Spring 类似默认为 singleton。
if (Class.isAnnotationPresent(Scope.class)) {
Scope scope = Class.getAnnotation(Scope.class);
definationbean.setScope(scope.value());
} else {
definationbean.setScope("singleton");
}
map.put(beanname, definationbean);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
// 扫描结束后:对作用域为 singleton 的 Bean 可在此「预创建」并放入 singletonObjects(与 Spring 预实例化单例思路类似)。
for (Map.Entry<String, Definationbean> entry : map.entrySet()) {
String beanname = entry.getKey();
Definationbean definationbean = entry.getValue();
if (entry.getValue().getScope().equals("singleton")) {
Object bean = creatbean(beanname, definationbean);
singletonObjects.put(beanname, bean);
}
}
}
}
}
/**
* 根据 Bean 名称与定义创建实例;具体创建方式由你自行实现(反射无参构造、工厂等)。
*/
private Object creatbean(String beanname, Definationbean definationbean) {
//通过反射也就是拿到calss类元信息然后进行创建对象这一过程称之为反射(对象的创建)
Class clazz = definationbean.getType();
try {
Object bean = clazz.getConstructor().newInstance();
return bean;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* 按名称从容器中获取 Bean 实例:先查 {@link #map} 得类型与作用域;
* singleton 则复用 {@link #singletonObjects} 中已创建实例,否则每次反射新建(prototype 等)。
*/
public Object getBean(String beanname) {
// 未登记过该名称则无定义,直接返回 null。
Definationbean definationbean = map.get(beanname);
if (definationbean == null) {
return null;
}
// singleton:优先用缓存;缓存没有则创建后放入 map 并返回新建实例(注意应 return 新建对象,勿 return 旧的 null 引用)。
if (definationbean.getScope().equals("singleton")) {
Object bean = singletonObjects.get(beanname);
if (bean == null) {
Object bean2 = creatbean(beanname, definationbean);
singletonObjects.put(beanname, bean2);
return bean2;
}
return bean;
}
// 非 singleton(如 prototype):每次 getBean 都新建,不放入 singletonObjects。
return creatbean(beanname, definationbean);
}
}
自定义scope注解:
java
package com.code.liyunmiao.spring;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* 作用域(手写简化版):标在类上;未标注时容器内默认按 singleton 处理。
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
String value() default "";
}
Definationbean实体类:
java
package com.code.liyunmiao.service;
/**
* Bean 定义:扫描阶段为每个带 {@code @Component} 的类生成一份,保存「类型 + 作用域」,
* 供 {@link com.code.liyunmiao.spring.liyunmiaocontext} 在 {@code getBean} 时按作用域创建或复用实例。
*/
public class Definationbean {
/** 作用域字符串,如 singleton;与 {@link com.code.liyunmiao.spring.Scope} 或默认值对应。 */
private String scope;
/** Bean 对应的类元数据,用于反射创建实例。 */
private Class type;
public void setScope(String scope) {
this.scope = scope;
}
public String getScope() {
return scope;
}
public void setType(Class type) {
this.type = type;
}
public Class getType() {
return type;
}
}
当给userService加:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.Component;
import com.code.liyunmiao.spring.Scope;
/**
* 示例组件:{@code @Component} 的 value 为容器中注册的 Bean 名称,{@code getBean("Userservice")} 与之对应。
*/
@Component("Userservice")
@Scope("singleton")
public class Userservice {
}
下面来看测试类:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.liyunmiaocontext;
/**
* 入口:显式传入配置类 {@link configclass},构造手写容器后按 Bean 名称取出 {@link Userservice}。
*/
public class Test {
public static void main(String[] args) {
liyunmiaocontext context = null;
// 构造过程中可能抛异常(如 classpath 上找不到扫描包目录等),此处统一捕获并包装。
try {
context = new liyunmiaocontext(configclass.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
// Bean 名需与 @Component("Userservice") 中 value 一致。
Userservice userservice=(Userservice) context.getBean("Userservice");
System.out.println(context.getBean("Userservice"));
System.out.println(context.getBean("Userservice"));
System.out.println(context.getBean("Userservice"));
System.out.println(context.getBean("Userservice"));
}
}

这是输出:输出都是同一个对象也就是对应的单例bean。
下面看另一种:
java
package com.code.liyunmiao.service;
import com.code.liyunmiao.spring.Component;
import com.code.liyunmiao.spring.Scope;
/**
* 示例组件:{@code @Component} 的 value 为容器中注册的 Bean 名称,{@code getBean("Userservice")} 与之对应。
*/
@Component("Userservice")
@Scope("proporty")
public class Userservice {
}
当scope里面加这个的时候每次get的就是新的实例,也就是说是多例模式每次获取都是new一个新的而不是用那个旧的了,或者说而不是一直用那一个了。

这是输出。