自定义注解@MyBean用于实现自动装配
@Target({ ElementType.TYPE })//类上面有效
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface MyBean {
}
自定义注解@MyAutowired用于实现自动注入
@Target({ ElementType.FIELD })//属性上面有效
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface MyAutowired {
}
定义bean容器接口
public interface ApplicationContext {
/**
* 接口中定义此方法,让所有实现类去实现
* @param clazz 根据class
* @return 容器中的对象
*/
Object getBean(Class<?> clazz);
}
自定义AnnotationApplicationContext作为ApplicationContext 的实现类
package cn.ttc.bean;
import cn.ttc.annotation.MyAutowired;
import cn.ttc.annotation.MyBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class AnnotationApplicationContext implements ApplicationContext {
private final static Logger logger = LoggerFactory.getLogger(AnnotationApplicationContext.class);
private static String rootPath;
private final Map<Class, Object> beanFactory = new ConcurrentHashMap<>();
@Override
public Object getBean(Class<?> clazz) {
return clazz != null ? beanFactory.get(clazz) : null;
}
/**
* 根据有参构造,传递包路径,设置包扫码规则
* 当前包及子包下只有存在@MyBean注解,通过反射实例化
*
* @param basePackage
*/
public AnnotationApplicationContext(String basePackage) {
if (!StringUtils.hasText(basePackage)) {
return;
}
//basePackage --> cn.ttc 找到包路径下的绝对路径获取到file文件夹
try {
//1. 把.替换成\
String packageDirName = basePackage.replaceAll("\\.", "\\\\");//处理转义
//2. 获取包编译后的绝对路径(类加载器可以知道其绝对路径)
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
while (urls.hasMoreElements()) {
// url ---> /D:/workspace/spring6/customization-ioc/target/test-classes/cn%5cttc
URL url = urls.nextElement();
//解决/被url编码的问题,需要转码。 得到完整的绝对路径
String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
//获取包前面路径的部分
rootPath = filePath.substring(0, filePath.length() - packageDirName.length());
//根据绝对路径获取文件夹后加载文件夹下所有存在@MyBean注解的类实例化到容器中
loadBean(new File(filePath));
}
} catch (IOException e) {
e.printStackTrace();
}
//加载自动注入功能
loadMyAutowired();
}
/**
* 使@MyAutowired拥有自动注入的功能
* 实例化对象在beanFactory的map集合里面
*/
private void loadMyAutowired() {
//1. 遍历beanFactory的map集合
//2. 得到每个对象的属性数组,得到每个属性
//3. 判断属性上是否村存在@MyAutowired注解
//4. 如果存在,把对象进行注入
if (!CollectionUtils.isEmpty(beanFactory)) {
Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
for (Map.Entry<Class, Object> entry : entries) {
Object obj = entry.getValue();
//获取对象Class
Class<?> clazz = obj.getClass();
//得到对象的全部属性数组
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//判断此属性上是否存在@MyAutowired注解
MyAutowired annotation = field.getAnnotation(MyAutowired.class);
if (annotation != null) {
//打破私有方法不能访问的限制
field.setAccessible(true);
try {
logger.info("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】");
Class<?> classType = field.getType();
field.set(obj,beanFactory.get(classType));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 根据绝对路径获取文件夹后加载文件夹下所有存在@MyBean注解的类实例化到容器中
*
* @param fileParent 文件夹
*/
private void loadBean(File fileParent) {
//判断不是null还是文件夹
if (fileParent != null && fileParent.isDirectory()) {
//得到所有的子文件或子文件夹
File[] childrenFiles = fileParent.listFiles();
if (childrenFiles != null && childrenFiles.length > 0) {
for (File child : childrenFiles) {
//如果还是文件夹
if (child.isDirectory()) {
//递归调用
loadBean(child);
} else {
//通过文件路径转变成全类名,第一步把绝对路径部分去掉
String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
//文件夹中可能存在其他文件,只处理.class后缀的文件
if (pathWithClass.endsWith(".class")) {
//去掉.class后缀,并且把 / 替换成 .
String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
try {
//根据全类限定名获取class
Class<?> clazz = Class.forName(fullName);
//把非接口的类实例化放在map中
if (!clazz.isInterface()) {
//判断里面是否存在注解@MyBean
MyBean annotation = clazz.getAnnotation(MyBean.class);
//根据反射创建实例化对象
if (annotation != null) {
Object instance = clazz.getDeclaredConstructor().newInstance();
//判断此对象是否存在接口
if (clazz.getInterfaces().length > 0) {
logger.info("正在加载【" + clazz.getInterfaces()[0] + "】,实例对象是:" + instance.getClass().getName());
//如果有接口把接口的class当成key,实例对象当成value
beanFactory.put(clazz.getInterfaces()[0], instance);
} else {
logger.info("正在加载【" + clazz + "】,实例对象是:" + instance.getClass().getName());
beanFactory.put(clazz, instance);
}
}
}
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
}
}
}
}
}
}
测试
在项目中使用@@MyBean和@MyAutowired
@org.junit.jupiter.api.Test
public void test001(){
AnnotationApplicationContext context = new AnnotationApplicationContext("cn.ttc");
StudentService studentService = (StudentService) context.getBean(StudentService.class);
String learning = studentService.learning();
System.out.println("learning ----------= " + learning);
}