您的位置:首页 > 编程语言 > Java开发

手写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);

}

}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐