手写springioc注解注入对象基本实现
2019-05-27 17:38
1581 查看
[code]还是要养成写注释的习惯,首先一个代码想让人家看懂的情况下,记住一点,重构加设计模式,其实设计模式也比较好, 达到别人可易读性,这是我要跟你讲的,而且你们不写注释是一个不好的习惯,你们一定要养成我善于重构代码,善于写注释, 把这样的思想学会的情况下,我相信你以后是一个非常好的程序员,所以不要说别人不写注释的程序员就很牛,这不一定的, 这个注解方式要怎么实现,在这个地方我们来看一段代码,手写注解版本的AOP容器,只要我加上一个注解,就会把这个类注入到 Spring容器里面去,你们想想这种方式是怎么实现的,为什么加一个注解就能够把这个类注入到Spring容器里面去呢,谁能告诉我 答案的,思路是什么,我只要思路不要代码,这个还真不是AOP,绝对不是AOP,注解AOP是经过方法的,我是在类上面加上注解之后, 把我这个类注入到Spring容器里面去,是不是这样的,其实我告诉你,怎么实现的呢,其实挺简单的,真的挺简单的,不是AOP,就是反射 机制,我们画图,我们先说个思路,如果让你们自己去写Spring框架,你们怎么去写,你最好答出来,因为我是真的写过的,我不是光看 源码那么简单的,所以这个都是比较重要的,这是我讲注解版本的实现,我们把依赖信息给copy过来,不用AOP https://cloud.tencent.com/developer/article/1424714 手写源码(二):自己实现SpringIOC https://www.jianshu.com/p/274275cf28ce
[code]<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 这个是扫包范围 这段代码表示扫包 注解的扫包范围,它是先读取配置文件,把包名读到之后,读到他包下有哪些子类,那些个类, 然后再获取这个类上有没有这个注解,有注解的情况下再利用反射机制,帮你去初始化了,是不是这样的, --> <context:component-scan base-package="com.learn"></context:component-scan> </beans>
[code]package com.learn.service; //user 服务层 public interface UserService { public void add(); public void del(); }
[code]package com.learn.service.impl; import org.springframework.transaction.annotation.Transactional; import com.learn.annotation.ExtService; import com.learn.service.UserService; //user 服务层 /** * @ExtService表示要注入到Spring容器里面去 * * @author Leon.Sun * */ @ExtService public class UserServiceImpl implements UserService { @Transactional public void add() { System.out.println("使用JAVA反射机制初始化对象...."); } // 方法执行完毕之后,才会提交事务 public void del() { } }
[code]package com.learn.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 自定义注解 注入到Spring容器 /** * 自定义注解 service 注入bean容器 * @author Leon.Sun * */ @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface ExtService { }
[code]package com.learn.utils; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.net.JarURLConnection; import java.net.URL; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * 这段代码千万不要去写 * 写了没意义 * 这个我大体说一下原理 * 原理是什么样的呢 * * * @author Leon.Sun * */ public class ClassUtil { /** * 取得某个接口下所有实现这个接口的类 */ public static List<Class> getAllClassByInterface(Class c) { List<Class> returnClassList = null; if (c.isInterface()) { // 获取当前的包名 String packageName = c.getPackage().getName(); // 获取当前包下以及子包下所以的类 List<Class<?>> allClass = getClasses(packageName); if (allClass != null) { returnClassList = new ArrayList<Class>(); for (Class classes : allClass) { // 判断是否是同一个接口 if (c.isAssignableFrom(classes)) { // 本身不加入进去 if (!c.equals(classes)) { returnClassList.add(classes); } } } } } return returnClassList; } /* * 取得某一类所在包的所有类名 不含迭代 */ public static String[] getPackageAllClassName(String classLocation, String packageName) { // 将packageName分解 String[] packagePathSplit = packageName.split("[.]"); String realClassLocation = classLocation; int packageLength = packagePathSplit.length; for (int i = 0; i < packageLength; i++) { realClassLocation = realClassLocation + File.separator + packagePathSplit[i]; } File packeageDir = new File(realClassLocation); if (packeageDir.isDirectory()) { String[] allClassName = packeageDir.list(); return allClassName; } return null; } /** * 从包package中获取所有的Class * * @param pack * @return */ public static List<Class<?>> getClasses(String packageName) { // 第一个class类的集合 List<Class<?>> classes = new ArrayList<Class<?>>(); // 是否循环迭代 boolean recursive = true; // 获取包的名字 并进行替换 String packageDirName = packageName.replace('.', '/'); // 定义一个枚举的集合 并进行循环来处理这个目录下的things Enumeration<URL> dirs; try { dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName); // 循环迭代下去 while (dirs.hasMoreElements()) { // 获取下一个元素 URL url = dirs.nextElement(); // 得到协议的名称 String protocol = url.getProtocol(); // 如果是以文件的形式保存在服务器上 if ("file".equals(protocol)) { // 获取包的物理路径 String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); // 以文件的方式扫描整个包下的文件 并添加到集合中 findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes); } else if ("jar".equals(protocol)) { // 如果是jar包文件 // 定义一个JarFile JarFile jar; try { // 获取jar jar = ((JarURLConnection) url.openConnection()).getJarFile(); // 从此jar包 得到一个枚举类 Enumeration<JarEntry> entries = jar.entries(); // 同样的进行循环迭代 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(packageDirName)) { 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) { e.printStackTrace(); } } } } } } catch (IOException e) { e.printStackTrace(); } } } } catch (IOException e) { e.printStackTrace(); } return classes; } /** * 以文件的形式来获取包下的所有Class * * @param packageName * @param packagePath * @param recursive * @param classes */ public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes) { // 获取此包的目录 建立一个File File dir = new File(packagePath); // 如果不存在或者 也不是目录就直接返回 if (!dir.exists() || !dir.isDirectory()) { return; } // 如果存在 就获取包下的所有文件 包括目录 File[] dirfiles = dir.listFiles(new FileFilter() { // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件) public boolean accept(File file) { return (recursive && file.isDirectory()) || (file.getName().endsWith(".class")); } }); // 循环所有文件 for (File file : dirfiles) { // 如果是目录 则继 7ff7 续扫描 if (file.isDirectory()) { findAndAddClassesInPackageByFile(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)); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } }
[code]package com.learn.spring; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang.StringUtils; import com.learn.annotation.ExtService; import com.learn.utils.ClassUtil; /** * 手写Spring IOC 注解版本 * 我就不写入到xml里面去 * 我就传入进来 * * * @author Leon.Sun * */ public class ExtClassPathXmlApplicationContext { // 包名 private String packageName; // bean容器 /** * 我们定义一个集合ConcurrentHashMap * 这个String表示bean的id * 你们觉得这里存Object好呢还是存对象好呢 * 是存Class地址好呢还是存Object好 * 你们想想 * 其实这个看你们自己 * 我觉得存地址相对灵活点 * 那么Object相对好一点 * 就是他不用在初始化了 * 是不是这样的 * 地址稍微好一点 * 扩展性高一点 * 万一他想要class地址 * 地址他可以随便new的 * Class地址是最好的 * Spring里面其实也是这样写的 * 静态其实最好的 * 没任何影响的 * 但是要记住一点 * 线程安全问题 * 但是ConcurrentHashMap线程安不安全 * 您们知不知道 * 肯定是安全的 * 源码里面好像是静态的 * 源码里面我也忘记了 * 你们也可以把它作为静态的 * 这个有很多实现方式 * 归根结底 * 你们也可以在这里初始化 * 但是告诉你们 * 但是你们最好不要在上面直接初始化 * 这个我要告诉你们的 * 怎么做的呢 * 先把他置为null * 有的人会问 * 那我什么时候去初始化呢 * 记住一点 * 我待会告诉你们 * 你们真正写的情况下 * 有个方法 * 加载的时候才初始化的 * 不要一上去就初始化 * 我们先不初始化 * 我后面讲什么时候初始化 * * Spring bean容器 * */ private ConcurrentHashMap<String, Object> beans = null; /** * 构造函数 * * @param packageName 包名 * @throws InstantiationException * @throws IllegalAccessException */ public ExtClassPathXmlApplicationContext(String packageName) throws InstantiationException, IllegalAccessException { /** * 你们最好判断这个地址是否为空的 */ this.packageName = packageName; beans = new ConcurrentHashMap<String, Object>(); initBeans(); } /** * 初始化对象 * 你们知道怎么去获取当前包下所有的类 * 怎么去获取 * 方法是什么 * 其实原理比较简单 * 读取有哪些class名称 * 比如读取com.learn.service * 知道他下面有哪些类 * 就是这样的 * 这个大家直接用API就行了 * 反射工具类 * 这也是我从网上找的 * * 初始化bean我们放在什么地方 * 一般都是放在构造函数初始化 * * * * @throws IllegalAccessException * @throws InstantiationException */ private void initBeans() throws IllegalAccessException, InstantiationException { // 遍历所有类 /** * 这里传入我们的包名packageName * 是不是返回所有的class地址 * 我们这一步拿到了 * * 首先读取这个包有哪些类 * */ List<Class<?>> classes = ClassUtil.getClasses(packageName); // 将所有标注ExtService注解的类加入到容器中 findClassExistAnnotation(classes); /** * 如果他等于null就报个错出来 * 你们可以报错也可以不报错 * 正常情况不会报错 * 看你们自己 * 我 为了演示效果就抛个异常出来 * * */ if (beans == null || beans.isEmpty()) { /** * 该包下没有任何类加上注解 */ throw new RuntimeException("没有类加上了注解"); } } /** * 过滤标注ExtService注解的类 * 判断类上面是否有注解呢 * * * @param classes * @throws InstantiationException * @throws IllegalAccessException */ private void findClassExistAnnotation(List<Class<?>> classes) throws InstantiationException, IllegalAccessException { /** * 这里是classInfo * */ for (Class<?> classInfo : classes) { /** * 调用这个方法判断是否有注解 * 我传入ExtService * 判断类上是否有注解 * * */ Annotation annotation = classInfo.getAnnotation(ExtService.class); /** * 如果现在上面一旦有的话 * 有的话怎么办 * 如果annotation他不等于null的话他会怎么做 * * 有没有注解 * 因为我后面加了注解 * * */ if (annotation != null) { //到这里表示有这个注解 /** * 有注解我就获取他的类名 * */ String className = classInfo.getName(); //默认Id是首字母小写 /** * 在这里就有一个问题 * 在这里要存放的是bean的id * 这个beanid怎么获取 * 注解里面的beanid是怎么实现的 * 想一想注解的beanid是怎么来的 * beanid是默认类名小写 * 是不是这样的过程 * 在这边我要说一下 * 我来问你们 * 判断有没有值 * 如果他有传入beanid名称之后 * 默认会用到beanid的名称 * 如果没有传就默认小写 * 这个看你自己 * 你们如果想写的非常完善的话 * 你可以自己去写 * classInfo.getSimpleName()这个是获取当前类名 * 获取完当前类名之后 * 我们就可以拿到className * 正常情况下他会不会为空 * 既然能被反射出来肯定是不会为空的 * 如果你想写的特别完善的话 * 你判断一下也可以 * 其实没必要判断 * 为什么呢 * 这不为空的 * 这样的 * 肯定是有值的 * 这个我就说一下 * 您们说在这里是continue好呢 * 还是break好呢 * 你们想一想 * 肯定是continue * 因为还需要继续扫包检查 * * 把类名变成小写的 * 存放到bean的容器里面去 * * * */ beans.put(toLowerCaseFirestOne(classInfo.getSimpleName()), newInstance(classInfo)); } } } /**类名的首字母小写*/ /** * 把你的类名首字母变成小写的 * 我就不去说了 * 这里我们可以拿到newClassName * 就是新的name名称 * 这里叫beanid也可以 * * @param className * @return */ private String toLowerCaseFirestOne(String className) { return new StringBuilder().append(Character.toLowerCase(className.charAt(0))).append(className.substring(1)).toString(); } /**获取Bean的方法*/ /** * 这里传入一个beanId * * * @param beanId * @return * @throws IllegalAccessException * @throws InstantiationException */ public Object getBean(String beanId) throws IllegalAccessException, InstantiationException { /** * 你们只需要判断beanid * * 是不是传入beanid进来 * * */ if (StringUtils.isEmpty(beanId)) { /** * 如果抛异常表示beanid不能为空 * 如果beanid参数不能为空 * * */ throw new RuntimeException("BeanID为空"); } /** * 通过beanid去查找一个对象 * 他就拿到对象了 * 从Spring容器获取bean * */ return beans.get(beanId); } /**利用反射机制创建Bean*/ private Object newInstance(Class<?> classInfo) throws IllegalAccessException, InstantiationException { /** * 如果他为空的情况下 * 就说明什么情况 * 是不是没有找到bean * * */ if (classInfo == null) { throw new RuntimeException("没有这个ID的bean"); } /** * 使用反射机制初始化对象 * * 利用反射去初始化对象的 * */ return classInfo.newInstance(); } /**依赖注入传入类的属性*/ private void attrAssign(Class<?> classInfo) { //获取这个类所有的属性 Field[] fields = classInfo.getFields(); //判断当前属性是否有注解 for (Field field : fields) { ExtService extService = field.getAnnotation(ExtService.class); if (extService != null) { //到这里说明这个属性里有这个注解 String fieldName = field.getName(); } } } }
[code]package com.learn; import com.learn.service.UserService; import com.learn.spring.ExtClassPathXmlApplicationContext; /** * 核心是三步 * 但是代码是比较麻烦的 * 我可能会用到一些工具类 * 那么我们这边同样道理 * 我们首先要定义注解 * * * @author Leon.Sun * */ public class Test001 { public static void main(String[] args) throws Exception { /** * 在使用注解版本事务的时候 * 你们第一步要做什么 * 怎么样保证我的注解一定要生效呢 * 我要怎么做 * 想一想 * 记住扫包 * 为什么要加扫包 * 注解定义完了不一定能用它 * 是不是要加扫包 * 你们之前为什么要在配置文件里面加这样的一段代码 * 干嘛用的 * 就是用来做扫包的操作 * 我们就以这个为切入点 * 怎么进行实现过来的 * 那么我们在这里给你详细讲一下这个步骤 * 跟着扫包去分析 * 大家一定要跟着我的思路去想想 * 首先要利用JAVA的反射机制扫包 * 获取当前包下所有的类 * 是不是这样的 * 这个是不是第一步 * 我肯定要获取当前包下所有的类 * * */ /** * 想想第二步干嘛 * 写白话文 * 判断类上是否存在注入bean的注解 * * */ /** * 如果在类上加上注解的话 * 我要怎么做 * 使用JAVA的反射机制进行初始化 * 简单来说就是这三步 * 但是我告诉你绝对不止这三步 * 比这个难多了 * 这只是最简单的三步 * 我们细讲一下底层到底是怎么实现的 * 所有有人的人说用AOP * AOP确实可以实现 * 但是AOP太麻烦了 * AOP我们一般用哪个方法我们用AOP * 但是在类上的话我们要用反射进行实现 * 是不是这样的 * 那么这个思路大家有没有一点头绪 * * */ /** * 我们这个要导包 * com.learn.service.impl它是我们需要加载的 * 我们在我们的Service上加上我们的注解@ExtService * 我们要传UserServiceImpl类名首字母小写的 * 如果我不加注解它是肯定找不到的 * UserServiceImpl类上面我们是没有加注解的 * * */ ExtClassPathXmlApplicationContext app = new ExtClassPathXmlApplicationContext("com.learn.service.impl"); UserService userService = (UserService) app.getBean("userServiceImpl"); /** * 打印一下这个效果 */ System.out.println(userService); } }
相关文章推荐
- Spring自动注入,利用注解实现spring基本配置详解,Spring注解快速入门
- 一起写框架-Ioc内核容器的实现-对象的调用-@Bean注解注入容器的对象(十二)
- 【Spring】【IOC】【Spring容器注入Bean对象的四种方式】【Spring中注入bean对象的注解】
- 仿spring的ioc实现之注解注入的小例子
- 编码实现Spring 利用@Resource注解实现bean的注入,xml实现基本数据类型的注入
- #spring(2)IOC/DI,构造器参数注入,按照名字,类自动注入,Spring基本值注入,利用Spring配置数据源, 集合注入,读取Properties文件,Spring 表达式,注解
- Spring IOC/DI 基本配置 及测试 /注入对象 火推
- Spring学习3—控制反转(IOC)基于Annotation(注解)的依赖注入实现
- 手写Spring基本体系IOC+AOP+MVC+事物管理等简单实现。
- spring 框架中的依赖注入(IOC--设值注入)--使用注解--的具体实例的简单实现
- Spring学习3—控制反转(IOC)基于Annotation(注解)的依赖注入实现
- ######【spring属性注入(Ioc的DI)总结】:注解方式属性注入,属性名任意.=for理解:Aop注入代理对象时,注入被增强类对象时,属性名为proxy(自定义)。
- 依赖注入(DI)和控制反转(IOC)的详细讲解 spring容器(spring注解实现,而不是xml配置文件)
- SpringBoot系列三:SpringBoot基本概念(统一父 pom 管理、SpringBoot 代码测试、启动注解分析、配置访问路径、使用内置对象、项目打包发布)
- Spring学习笔记——关于Spring注解扫描不能注入new对象问题
- 打造简易的依赖注入框架(下)(此篇与Spring.NET无关,为自己手写IoC框架)
- 基于配置文件的工厂设计模式实现,并且做到对象的单例,类似于spring的ioc
- 开涛spring3(12.2) - 零配置 之 12.2 注解实现Bean依赖注入
- 深入探索spring技术内幕(四): 剖析@Resource注解实现原理与注解注入
- Spring 用注解实现IOC控制反转