阅读导航
IOC容器的启动流程(基于注解)
IOC的基本概念:
IOC
:IoC 控制反转 Inverse of Control 创建对象的权限,Java 程序中需要用到的对象不再由程序员自己创建,而是交给 IoC 容器来创建。
IOC的启动流程
1.扫描包的组件:AnnotationConfigApplicationContext,负责扫描指定包下的所有类,将为被@Component注解的类动态创建对象,并完成自动注入;
2.工厂创建对象需要的条件:ClassName(反射创建对象),BeanName(创建对象再工厂中的唯一标识):
Spring中采用BeanDefinition来封装上述材料;
3.根据上面定义的BeanDefinition来动态创建对象;
4.将创建的对象装载到IOC容器中进行管理;
自己实现IOC的主要流程
1.自定义一个MyAnnotationConfigApplicationContext,构造器中传入要扫描的包名;
2.获取这个包下的所有类;
3.遍历这些类,找到@Component注解的类,获取它的Claa和对应的BeanName,封装为BeanDefinition,存入Set;
4.遍历Set集合,利用反射机制创建对象,同时对Value注解下的属性进行赋值,对Autowired的注解完成自动注入完成赋值,将这些动态生成的对象以key-value的形式存入缓存区;
5.提供getBean方法,通过BeanName方法拿到对应的Bean;
项目的流程图
自定义注解
@MyComponent
该注解类似Component,包扫描时会将该注解下的类获取
@Target(ElementType.TYPE)//表示该注解作用再类上
@Retention(RetentionPolicy.RUNTIME)//注解在运行时生效
public @interface MyComponent {
String value() default "";// 指定容器BeanName
}
@MyValue
完成基本类型的属性赋值
@Target(ElementType.FIELD)//表示该注解作用在成员变量上
@Retention(RetentionPolicy.RUNTIME)//运行时注解生效
public @interface MyValue {
String value();//Value必须手动赋值
}
@MyAutowired
完成引用类型的自动注入:ByType
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
@MyQualifier
完成引用类型的自动注入:ByName
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyQualifier {
String value();
}
自定义实体类
Account
@Data
@MyComponent("account")
public class Account {
@MyValue("1")
private Integer Id;
@MyValue("Taoger")
private String name;
@MyValue("20")
private Integer age;
}
Persion
@Data
@MyComponent()
public class Persion {
@MyAutowired
//@MyQualifier("account")
private Account account;
}
自定义BeanDefinition
BeanDefinition用来封装扫描得到的Class和BeanName,这些就是创建对象的基本材料;
@Data
@AllArgsConstructor
public class BeanDefinition {
private String beanName;
private Class beanClass;
}
包扫描工具类的编写
这个是套的模板,现在还不是特别熟悉Java的IO流方面的知识,暂且直接调用,感觉个才是这里最难写的,这个工具类的主要作用就是将对应包名下的所有类全部扫描到并封装为Set返回:
public class PackageUtils {
public static Set<Class<?>> getClasses(String packageName){
//第一个class类的集合
Set<Class<?>> classes=new LinkedHashSet<Class<?>>();
//是否循环迭代
boolean recursive=true;
//对传进来的参数进行分割
String packName=packageName;
String packDirName=packName.replace('.','/');
//定义一个枚举的集合并进行循环来处理当前目录
Enumeration<URL> dirs;
try {
dirs=Thread.currentThread().getContextClassLoader().getResources(packDirName);
//迭代循环
while(dirs.hasMoreElements()){
//获取下一个元素
URL url=dirs.nextElement();
//得到协议的名称
String protocol=url.getProtocol();
//如果是以文件的形式保存在服务器上
if("file".equals(protocol)){
//获取包的物理路径
String filePath= URLDecoder.decode(url.getFile(),"UTF-8");
// 以文件的方式扫描整个包下的文件 并添加到集合中
findClassesInPackageByFile(packageName, filePath, recursive, classes);
}
else if ("jar".equals(protocol)){
// 如果是jar包文件
// 定义一个JarFile
System.out.println("jar类型的扫描");
JarFile jar;
try {
// 获取jar
jar = ((JarURLConnection) url.openConnection()).getJarFile();
// 从此jar包 得到一个枚举类
Enumeration<JarEntry> entries = jar.entries();
findClassesInPackageByJar(packageName, entries, packDirName, recursive, classes);
} catch (IOException e) {
// log.error("在扫描用户定义视图时从jar包获取文件出错");
e.printStackTrace();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return classes;
}
private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packDirName, boolean recursive, Set<Class<?>> classes) {
// 同样的进行循环迭代
while (entries.hasMoreElements()) {
// 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
JarEntry entry = entries.nextElement();
String name = entry.getName();
// 如果是以/开头的
if (name.charAt(0) == '/') {
// 获取后面的字符串
name = name.substring(1);
}
// 如果前半部分和定义的包名相同
if (name.startsWith(packDirName)) {
int idx = name.lastIndexOf('/');
// 如果以"/"结尾 是一个包
if (idx != -1) {
// 获取包名 把"/"替换成"."
packageName = name.substring(0, idx).replace('/', '.');
}
// 如果可以迭代下去 并且是一个包
if ((idx != -1) || recursive) {
// 如果是一个.class文件 而且不是目录
if (name.endsWith(".class") && !entry.isDirectory()) {
// 去掉后面的".class" 获取真正的类名
String className = name.substring(packageName.length() + 1, name.length() - 6);
try {
// 添加到classes
classes.add(Class.forName(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// .error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
}
private static void findClassesInPackageByFile(String packageName, String filePath, boolean recursive, Set<Class<?>> classes) {
// 获取此包的目录 建立一个File
File dir = new File(filePath);
// 如果不存在或者 也不是目录就直接返回
if (!dir.exists() || !dir.isDirectory()) {
// log.warn("用户定义包名 " + packageName + " 下没有任何文件");
return;
}
// 如果存在 就获取包下的所有文件 包括目录
File[] dirfiles = dir.listFiles(new FileFilter() {
// 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
@Override
public boolean accept(File file) {
return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
}
});
// 循环所有文件
for (File file : dirfiles) {
// 如果是目录 则继续扫描
if (file.isDirectory()) {
findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
} else {
// 如果是java类文件 去掉后面的.class 只留下类名
String className = file.getName().substring(0, file.getName().length() - 6);
try {
// 添加到集合中去
// classes.add(Class.forName(packageName + '.' +
// className));
// 经过回复同学的提醒,这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
} catch (ClassNotFoundException e) {
// log.error("添加用户自定义视图类错误 找不到此类的.class文件");
e.printStackTrace();
}
}
}
}
}
MyAnnotationConfigApplicationContext(基于注解的扫描组件)
这个就是IOC的核心API,也就是IOC生产对象的工厂,主要功能是根据包名动态生成对象并完成属性注入;
构造器
public class MyAnnotationConfigApplicationContext {
// 模拟IOC容器
private Map<String,Object> MyIoc=new HashMap<>();
//管理BeanNames
private List<String> beanNames = new ArrayList<>();
//定义构造器
public MyAnnotationConfigApplicationContext(String packageName) {
//遍历包名,拿到对应的BeanDefinition
Set<BeanDefinition> beanDefinitions=getBeanDefinitions(packageName);
//根据拿到的BeanDefinition,动态生成对象
createTargetObject(beanDefinitions);
//根据拿到的BeanDefinition,完成自动装配
autowireObject(beanDefinitions);
}
}
getBeanDefinitions
注意
:这里类比Spring,对于未指定BeanName的类名默认为类名的首字母小写;
/*
将添加了注解的类的class与BeanName封装为BeanDefinition存入Set
*/
public Set<BeanDefinition> getBeanDefinitions(String packageName){
Set<BeanDefinition> beanDefinitions=new HashSet<>();
//获取包下所有的类
Set<Class<?>> classes=PackageUtils.getClasses(packageName);
//遍历Set集合,拿到添加了注解的类
Iterator<Class<?>> iterator=classes.iterator();
while(iterator.hasNext()){
Class<?> clazz=iterator.next();
//判断是否加上注解
MyComponent myComponent = clazz.getAnnotation(MyComponent.class);
//通过myComponent来判断是否加上注解
if(myComponent!=null){
//获取注解标记对象在容器中的名字BeanName
String beanName=myComponent.value();
//如果用户没有写,默认为类名的首字母小写
if("".equals(beanName)){
//clazz.getName(); 拿到类的全限定名称
// clazz.getPackage().getName() 拿到类的包名
// String defaultName=clazz.getName().replaceAll(clazz.getPackage().getName()+".","");
String defaultName=clazz.getSimpleName();
defaultName= defaultName.substring(0,1).toLowerCase()+defaultName.substring(1);
beanName=defaultName;
}
BeanDefinition beanDefinition=new BeanDefinition(beanName,clazz);
beanDefinitions.add(beanDefinition);
}
}
return beanDefinitions;
}
createTargetObject
坑点
:@MyValue注解时赋值的String,对应的属性可能是其他类型,一定要注意类型转换,不然会发生类型转换错误;
/*
根据拿到的BeanDefinitions根据反射来创建目标对象
*/
public void createTargetObject(Set<BeanDefinition> beanDefinitions){
Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
while(iterator.hasNext()){
BeanDefinition beanDefinition=iterator.next();
//拿到Class
Class beanClass=beanDefinition.getBeanClass();
//拿到BeanName
String beanName=beanDefinition.getBeanName();
try {
//根据对象的模板来生产对象,利用反射机制,底层采用无参构造机制
Object object = beanClass.getConstructor().newInstance();
//根据注解MyValue来自动装配,完成属性的赋值
Field[] declaredFields=beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
//判断成员变量是否被MyValue注解标识
MyValue myValue = declaredField.getAnnotation(MyValue.class);
if(myValue!=null){
//拿到注解中的赋值,这里用String来接受可能会产生类型不匹配
String value=myValue.value();
//拿到方法名:
String fieldName = declaredField.getName();
String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
// 通过Set方法来赋值
Method method = beanClass.getMethod(methodName, declaredField.getType());
//完成类型转换,这里只是做了简单的类型处理,真正的IOC需要更完善的类型转换
Object val=null;
switch (declaredField.getType().getName()){
case "java.lang.Integer":
val= Integer.parseInt(value);
break;
case "java.lang.String":
val= value;
break;
case "java.lang.Float":
val= Float.parseFloat(value);
break;
}
method.invoke(object,val);
}
}
//将创建的对象存入IOC容器中
MyIoc.put(beanName,object);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
autowireObject
注意
:这里提供ByName和ByType两种方法
/*
根据拿到的原材料进行自动装载
*/
public void autowireObject(Set<BeanDefinition> beanDefinitions){
Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
while(iterator.hasNext()){
BeanDefinition beanDefinition=iterator.next();
//拿到Class,遍历当前Class每一个属性,判断是否需要自动装载
Class beanClass=beanDefinition.getBeanClass();
Field[] declaredFields=beanClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
MyAutowired myAutowired = declaredField.getAnnotation(MyAutowired.class);
//该属性需要自动装配
if(myAutowired!=null){
//判断是否通过名字注入
MyQualifier myQualifier = declaredField.getAnnotation(MyQualifier.class);
//通过名称注入,byName
if(myQualifier!=null){
// System.out.println("需要自动装配的属性:"+declaredField);
try {
String beanName=myQualifier.value();
Object autowiredValue = getBean(beanName);
//拿到方法名:
String fieldName = declaredField.getName();
String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
Method method=beanClass.getMethod(methodName,declaredField.getType());
//拿到需要赋值的对象
Object object = getBean(beanDefinition.getBeanName());
System.out.println("通过ByNameXSx");
method.invoke(object,autowiredValue);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
// 通过类型注入,ByType
else{
//遍历容器中的所有对象
for (String beanName : MyIoc.keySet()) {
//如果类型与filed的属性一样
if(getBean(beanName).getClass()==declaredField.getType()){
//拿到方法名:
try {
String fieldName = declaredField.getName();
String methodName = "set"+fieldName.substring(0, 1).toUpperCase()+fieldName.substring(1);
Method method=beanClass.getMethod(methodName,declaredField.getType());
//拿到需要赋值的对象
Object object = getBean(beanDefinition.getBeanName());
System.out.println("通过ByType");
method.invoke(object,getBean(beanName));
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
}
}
}
}
对外提供的方法
根据key,拿value
/*
提供getBean方法,根据在容器中的标记值拿到对应的对象
*/
public Object getBean(String beanName){
return MyIoc.get(beanName);
}
/*
提供getBeanDefinitionNames,返回容器中管理对象的BeanName
*/
public String[] getBeanDefinitionNames(){
return beanNames.toArray(new String[0]);
}
/*
提供getBeanDefinitionCount,返回容器中管理对象的数量
*/
public Integer getBeanDefinitionCount(){
return beanNames.size();
}
Test
public class Test {
public static void main(String[] args) {
MyAnnotationConfigApplicationContext applicationContext=new
MyAnnotationConfigApplicationContext("com.xu.myspring.entity");
System.out.println(applicationContext.getBean("persion"));
//Account account= (Account) applicationContext.getBean("Account");
}
}
- 测试效果
视频详解