SpringIOC基础理论
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。"别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
而依赖注入和依赖查询是实现IOC思想的其中的几种方式
IOC的职责:松耦合、"别找我们,我们找你”
- 依赖查询,一句话总结就是,主动去寻找相关的依赖类,用户是拉取
- 依赖注入,由容器主动推送
- 依赖查找是主动或手动的依赖查找方式,通常需要依赖容器或标准API 实现。而依赖注入则是手动或自动依赖绑定的方式,无需依赖特定的容器和 API
- https://www.cnblogs.com/joemsu/p/7688307.html 构造器和setter注入的比较
自研实现SpringIOC
前期准备
因为本次自研框架仅实现注解功能,所以就需要定义一些注解
@Target(ElementType.TYPE) //在类上
@Retention(RetentionPolicy.RUNTIME) // 要用到反射
public @interface Component {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
}
/**
* Autowired目前仅支持成员变量注入
*
*/
@Target(ElementType.FIELD) // 仅作用在成员变量上
@Retention(RetentionPolicy.RUNTIME) // 需要保留到程序运行时
public @interface Autowired {
String value () default "";
}
IOC-资源定位模块
@Slf4j
public class ClassUtil {
public static final String FILE_PROTOCOL = "file";
/**
* 获取包下类集合
*
* @parampackageName包名
* @return 类集合
*/
public static Set<Class<?>> extractPackageClass(String packageName){
//1.获取到类的加载器。
ClassLoader classLoader = getClassLoader();
//2.通过类加载器获取到加载的资源
// 因为获取出来的文件夹都是/,所以要将/转成包的形式
URL url = classLoader.getResource(packageName.replace(".", "/"));
if (url == null){
log.warn("unable to retrieve anything from package: " + packageName);
return null;
}
//3.依据不同的资源类型,采用不同的方式获取资源的集合
Set<Class<?>> classSet = null;
//过滤出文件类型的资源
if (url.getProtocol().equalsIgnoreCase(FILE_PROTOCOL)){
// 如果他是file协议
classSet = new HashSet<Class<?>>();
// 拿到包的绝对路径(url.getPath()获取绝对路径)
File packageDirectory = new File(url.getPath());
extractClassFile(classSet, packageDirectory, packageName);
}
//TODO 此处可以加入针对其他类型资源的处理
return classSet;
}
/**
* 递归获取目标package里面的所有class文件(包括子package里的class文件)
*
* @param emptyClassSet 装载目标类的集合
* @param fileSource 文件或者目录
* @param packageName 包名
* @return 类集合
*/
/**
* 递归获取目标package里面的所有class文件(包括子package里的class文件)
*
* @param emptyClassSet 装载目标类的集合
* @param fileSource 文件或者目录
* @param packageName 包名
* @return 类集合
*/
private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
if(!fileSource.isDirectory()){
// 如果不是目录
return;
}
//如果是一个文件夹,则调用其listFiles方法获取文件夹下的文件或文件夹
File[] files = fileSource.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if(file.isDirectory()){
return true;
} else{
//获取文件的绝对值路径
String absoluteFilePath = file.getAbsolutePath();
if(absoluteFilePath.endsWith(".class")){
//若是class文件,则直接加载
addToClassSet(absoluteFilePath);
}
}
return false;
}
//根据class文件的绝对值路径,获取并生成class对象,并放入classSet中
private void addToClassSet(String absoluteFilePath) {
//1.从class文件的绝对值路径里提取出包含了package的类名
//如/Users/baidu/imooc/springframework/sampleframework/target/classes/com/imooc/entity/dto/MainPageInfoDTO.class
//需要弄成cn.mjz.entity.dto.MainPageInfoDTO
absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
String className = absoluteFilePath.substring(absoluteFilePath.indexOf(packageName));
className = className.substring(0, className.lastIndexOf("."));
//2.通过反射机制获取对应的Class对象并加入到classSet里
Class targetClass = loadClass(className);
emptyClassSet.add(targetClass);
}
});
if(files != null){
for(File f : files){
//递归调用,提取类里面的文件
extractClassFile(emptyClassSet, f, packageName);
}
}
}
/**
* 获取Class对象
*
* @param className class全名=package + 类名
* @return Class
*/
public static Class<?> loadClass(String className){
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
log.error("load class error:", e);
throw new RuntimeException(e);
}
}
/**
* 实例化class
*
* @param clazz Class
* @param <T> class的类型
* @param accessible 是否支持创建出私有class对象的实例
* @return 类的实例化
*/
public static <T> T newInstance(Class<?> clazz, boolean accessible){
try {
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(accessible);
return (T)constructor.newInstance();
} catch (Exception e) {
log.error("newInstance error", e);
throw new RuntimeException(e);
}
}
/**
* 获取classLoader
*
* @return 当前ClassLoader
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/**
* 设置类的属性值
*
* @param field 成员变量
* @param target 类实例
* @param value 成员变量的值
* @param accessible 是否允许设置私有属性,这个是为了可以访问私有成员变量,所以我们的依赖注入对象是私有成员变量也不怕拿不到
*/
public static void setField(Field field, Object target, Object value, boolean accessible){
field.setAccessible(accessible);
try {
// 将该类的实现类下的子实现类注入到该实现类中,这个就是依赖注入
field.set(target, value);
} catch (IllegalAccessException e) {
log.error("setField error", e);
throw new RuntimeException(e);
}
}
// 测试
public static void main(String[] args) {
extractPackageClass("cn.mjz.entity");
File[] files = null;
// 对于foreache方法一定要做空指针判断,不然就会有空指针异常
for(File f : files){
System.out.printf("haha");
}
files.getClass().getClassLoader();
}
}
IOC-装载、注册模块
-
定义基本的成员变量
/** * 存放所有被配置标记的目标对象的Map */ private final Map<Class<?>, Object> beanMap = new ConcurrentHashMap(); /** * 加载bean的注解列表 */ private static final List<Class<? extends Annotation>> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class, Aspect.class);
-
单例加载Bean容器的实现
/** * 获取Bean容器实例 * * @return BeanContainer */ public static BeanContainer getInstance() { return ContainerHolder.HOLDER.instance; } private enum ContainerHolder { HOLDER; private BeanContainer instance; ContainerHolder() { instance = new BeanContainer (); } }
-
扫描加载所有Bean
/** * 扫描加载所有Bean * * @param packageName 包名 */ public synchronized void loadBeans(String packageName) { //判断bean容器是否被加载过 if (isLoaded()) { log.warn("BeanContainer has been loaded."); return; } // 提取所有的该包下的class文件信息 Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName); if (ValidationUtil.isEmpty(classSet)) { log.warn("extract nothing from packageName" + packageName); return; } for (Class<?> clazz : classSet) { for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) { //如果类上面标记了定义的注解 if (clazz.isAnnotationPresent(annotation)) { //将目标类本身作为键,目标类的实例作为值,放入到beanMap中 beanMap.put(clazz, ClassUtil.newInstance(clazz, true)); } } } loaded = true; }
IOC-加载 Bean模块
/**
* 提供依赖注入的服务
*/
@Slf4j
public class DependencyInjector {
/**
* Bean容器
*/
private BeanContainer beanContainer;
public DependencyInjector(){
beanContainer = BeanContainer.getInstance();
}
/**
* 执行Ioc
*/
public void doIoc(){
if(ValidationUtil.isEmpty(beanContainer.getClasses())){
log.warn("empty classset in BeanContainer");
return;
}
//1.遍历Bean容器中所有的Class对象
for(Class<?> clazz : beanContainer.getClasses()){
//2.遍历Class对象的所有成员变量
Field[] fields = clazz.getDeclaredFields();
if (ValidationUtil.isEmpty(fields)){
continue;
}
for(Field field : fields){
//3.找出被Autowired标记的成员变量
if(field.isAnnotationPresent(Autowired.class)){
Autowired autowired = field.getAnnotation(Autowired.class);
String autowiredValue = autowired.value();
//4.获取这些成员变量的类型
Class<?> fieldClass = field.getType();
//5.获取这些成员变量的类型在容器里对应的实例
Object fieldValue = getFieldInstance(fieldClass, autowiredValue);
if(fieldValue == null){
throw new RuntimeException("unable to inject relevant type,target fieldClass is:" + fieldClass.getName() + " autowiredValue is : " + autowiredValue);
} else {
//6.通过反射将对应的成员变量实例注入到成员变量所在类的实例里
Object targetBean = beanContainer.getBean(clazz);
ClassUtil.setField(field, targetBean, fieldValue, true);
}
}
}
}
}
/**
* 根据Class在beanContainer里获取其实例或者实现类
*/
private Object getFieldInstance(Class<?> fieldClass, String autowiredValue) {
Object fieldValue = beanContainer.getBean(fieldClass);
if (fieldValue != null){
return fieldValue;
} else {
Class<?> implementedClass = getImplementedClass(fieldClass, autowiredValue);
if(implementedClass != null){
return beanContainer.getBean(implementedClass);
} else {
return null;
}
}
}
/**
* 获取接口的实现类
*/
private Class<?> getImplementedClass(Class<?> fieldClass, String autowiredValue) {
// 通过接口或者父类获取实现类或者子类的Class集合,不包括其本身
Set<Class<?>> classSet = beanContainer.getClassesBySuper(fieldClass);
if(!ValidationUtil.isEmpty(classSet)){
if(ValidationUtil.isEmpty(autowiredValue)){
if(classSet.size() == 1){
return classSet.iterator().next();
} else {
//如果多于两个实现类且用户未指定其中一个实现类,则抛出异常
throw new RuntimeException("multiple implemented classes for " + fieldClass.getName() + " please set @Autowired's value to pick one");
}
} else {
for(Class<?> clazz : classSet){
if(autowiredValue.equals(clazz.getSimpleName())){
return clazz;
}
}
}
}
return null;
}
}
总结
至此,springIOC的核心已经简单实现完成,其实springIOC的核心就是我上面提到的那几个模块,为了让大家深入了解spring的实现,本人也做了一个脑图,是对springIOC的核心的总结。
链接:
附上以上代码的gitee地址:https://gitee.com/oreo_team/self_developed_spring
http://naotu.baidu.com/file/752e733a3704d8793c23f1ebefe73745?token=ef8cc354de57e935
http://naotu.baidu.com/file/59f1cea147ffad7dd064bf51b775f656?token=9369c18878d626fe
个人唠叨
好了,这么快又到了个人唠叨环节了,虽然本期文章很短,但是是以一种不同的方式展示spring源码,也算是对上期个人唠叨的实现,大家可以参考参考学习学习。
本周的吐槽
本周,是没有像上周这么潇洒了,还能学习新知识,本来想继续肝完spring-AOP和netty源码的,结果被机器学习和软件工程给大作业给硬生生要了,所以,本周并没有如期完成上周的计划。
一些经验总结
再来说说机器学习吧,连续搞了3天的机器学习,没有认真听过一节课的我(哈哈哈,不要被老师看到)从一个小白变成一个会基本特征工程编码的"小白",其实,这里重点不是机器学习,重点在于,如何快速上手一门新技术(仅限小白,如果你是大佬,当我没说)
- 首先你要对这门新技术有一个简单的认识,在这个阶段,我是不建议大家做实战的,首先要了解清楚概念,相信很多人都会推荐你上官网看相关的资源,其实,对于一个小白来说,一来就看官网,毫无疑问是一种劝退。那其实我并没有上官网,而是搜索一些别人总结的资料来看,了解一下,让我知道一个大概。
- 然后,你就可以上手实战了,那要怎么实战了,这里建议你找一个视频来跟着视频一起敲代码,敲完之后再去了解每一行代码的含义,这里视频的难度不建议过高,好像我一开始,并没有直接看班上老师的实战,而是直接看了网上一些评价比较高的实战,那简直就是劝退啊!看得我迷迷糊糊!!!!然后我才去翻班上老师的实战,跟着一起敲代码,最后理解每一行代码的含义
- 到这里,基本也已经是可以着手准备你要做的东西,就是一开始你是为了什么而学习这个新技术。比如我:是为了期末大作业而去做这个项目,我这时候就要开始了,然而,这个过程是最痛苦的,你会经历各种怀疑人生,因为虽然你理解一些基础概念,但是这每个概念你串联起来就不会了(哈哈哈哈,是不是贼真实),所以这个时候,弄懂项目的开发流程是很重要的,比如我数据挖掘的流程是特征预处理,然后再合并特征表,然后再进行模型训练,我做的那块是模型训练,然后再深入了解模型训练的流程,所以在上手项目或者实战新技术的时候,最终的是先了解主线,再去搞细节(好像我上面的spring解析一样也是采用先了解主线再深入细节的方法),所以我这里的主线就是模型训练,然后模型训练的主线就是,把合并的特征标表分为测试集和验证集,放到模型中进行训练和计算出auc,最对测试集进行预测。那这个流程其实是涉及到很多细节的,比如,表的合并的空指怎么处理,怎么把预测值放回到表中,怎么进行预测等等,那这些细节就要带有目的性的到网上找答案。
- 好了,做到这里,你基本上就已经算是入门了。
下周计划
我这时候就要开始了,然而,这个过程是最痛苦的,你会经历各种怀疑人生,因为虽然你理解一些基础概念,但是这每个概念你串联起来就不会了(哈哈哈哈,是不是贼真实),所以这个时候,弄懂项目的开发流程是很重要的,比如我数据挖掘的流程是特征预处理,然后再合并特征表,然后再进行模型训练,我做的那块是模型训练,然后再深入了解模型训练的流程,所以在上手项目或者实战新技术的时候,最终的是先了解主线,再去搞细节(好像我上面的spring解析一样也是采用先了解主线再深入细节的方法),所以我这里的主线就是模型训练,然后模型训练的主线就是,把合并的特征标表分为测试集和验证集,放到模型中进行训练和计算出auc,最对测试集进行预测。那这个流程其实是涉及到很多细节的,比如,表的合并的空指怎么处理,怎么把预测值放回到表中,怎么进行预测等等,那这些细节就要带有目的性的到网上找答案。
4. 好了,做到这里,你基本上就已经算是入门了。
下周计划
下周比较特殊,是答辩周,很多课都要结课了,不过主要任务还是有的:搞完spring-AOP和搞完Netty源码,有时间的话,就要开始刷算法题了。