相信许多同学对spring的IOC已经不陌生,这是我们从入门学习spring框架就经常能够听到名词,但是从使用spring至今都没对IOC进行一个深入的理解与剖析,今天就趁着学了java的反射技术以出发点去深入的去了解下IOC的原理,所谓IOC就是“控制反转”,它更偏向于一种设计思想。在Java开发中,IOC就是把原先我们代码里面需要实现的对象创建代码,反转给容器来帮忙实现。那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件。即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
用一个例子来说明,天气热了,我要需要一杯柠檬水,我需要买一个柠檬,需要买一个杯子,一个吸管,需要加工。而现实生活中我们却往往却是去饮品店告诉老板,我要一杯柠檬水,而刚好老板已经做好了柠檬水在等你来买,然后你就得到了柠檬水。这样就为我们省去大量去创建柠檬水的过程。而IOC就是饮品店老板,它提前创建好了各式各样的饮品,放在店里统一管理,当你需要什么饮品,IOC就会直接将它给你。
IOC的优点:
第一,资源集中管理,实现资源的可配置和易管理。
第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
第三,创建的是公用对象,是单例对象,一次创建,到处使用。减少了创建对象产生的内存损耗
IOC的缺点:
1、创建对象的步骤变复杂了。
2、因为使用反射来创建对象,所以在效率上会有些损耗。但相对于程序的灵活性和可维护性来说,这点损耗是微不足道的。
使用注解+反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
1.@Retention: 定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) // 注解仅存在于源码中,在 class 字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在 class 字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在 class 字节码文件中存在,在运行时可以通过反射获取到
首 先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解。
2.@Target:定义注解的作用目标
源码为:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATIONTYPE)
public @interface Target {
ElementType[] value();
}
@Target(ElementType.TYPE) // 接口、类、枚举、注解
@Target(ElementType.FIELD) // 字段、枚举的常量
@Target(ElementType.METHOD) // 方法
@Target(ElementType.PARAMETER) // 方法参数
@Target(ElementType.CONSTRUCTOR) // 构造函数
@Target(ElementType.LOCALVARIABLE)// 局部变量
@Target(ElementType.ANNOTATION_TYPE)// 注解
@Target(ElementType.PACKAGE) /// 包
3.@Document:说明该注解将被包含在 javadoc 中
4.@Inherited:说明子类可以继承父类中的该注解
通过反射和元注解的运用我们可以完成很多框架的实现,在理解了这些基本概念后,我们通过一个示意图来描述一下一个简单的IOC例子流程。
要让jvm在运行时识别那些类我们需要对他进行操作,我们需要设置注解。
首先我们自定义了一个用于标注着那些类需要我们把它放入容器里管理:
//设置注解用在类上
@Target({ElementType.TYPE})
//设置运行时可见
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
//spring使用byType和byName两种方式来关联管理对象
//我们这里用了自定义的name属性来关联
String name() default "";
}
定义另一个注解用于标志着那些对象需要注入
//注解用在属性上
@Target({ElementType.FIELD})
//运行时可见
@Retention(RetentionPolicy.RUNTIME)
public @interface GetModelClass {
//用于关联对象属
String name() default "";
}
Teacher类,注入student类,test方法调用student类中callStudent方法
public class Teacher {
@GetModelClass(name = "student")
private Student student;
//测试方法 实例是否被注入
public void test() {
student.callStudent();
}
}
Student 类 用Bean注解修饰标志着会被容器管理
@Bean(name="student")
public class Student {
public void callStudent(){
System.out.println("成功调用方法![在这里插入图片描述](https://img-blog.csdnimg.cn/20200531125432505.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2NjA5NjE1,size_16,color_FFFFFF,t_70)");
}
}
//创建了一个hashMap作为管理类的容器
private static Map<String,Object> container=new HashMap<>();
@Test
public void test(){
//定义扫描包路径
String scanBasePackage = "com.example.demo.test";
Path rootPath = null;
try {
//取到编译后路径
rootPath = Paths.get(DemoApplicationTests.class.getResource("/").toURI());
} catch (URISyntaxException e) {
e.printStackTrace();
}
Path sourcePath = null;
try {
sourcePath = Paths.get(DemoApplicationTests.class
.getResource("/" + scanBasePackage.replaceAll("\\.", "/") + "/").toURI());
} catch (URISyntaxException e) {
e.printStackTrace();
}
try {
//取到编译完整文件名
Path finalRootPath = rootPath;
Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.toString().endsWith(".class")) {
String fullClassName = file.toString()
.replace(finalRootPath.toString() + "\\", "")
.replaceAll("\\\\", ".")
.replace(".class", "");
try {
//根据文件名反射取得类信息
Class<?> aClass = Class.forName(fullClassName);
//把含有bean注解的类实例化并装载在map容器中
Bean[] annotationsByType = aClass.getAnnotationsByType(Bean.class);
if(annotationsByType.length>0) {
Object o= aClass.newInstance();
//以注解上的name作为标识来关联对象
container.put(annotationsByType[0].name(),o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return FileVisitResult.CONTINUE;
}
});
container.forEach((k,v)->
System.out.println("容器中实例有叫"+k+"的对象"+v);
});
//当Teacher对象需要装载Student对象,把容器中已经实例化的对象返回
Teacher teacher=new Teacher();
try{
//没有注入student对象会报异常
teacher.test();
}catch (Exception e){
e.printStackTrace();
}
//通过反射给类student赋值
Class<?> aClass =Teacher.class;
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
//当属性上有getModelClass注解时,通过name传入值从容器取对应实例
if(field.isAnnotationPresent(GetModelClass.class)){
field.set(teacher,container.get(field.getAnnotationsByType(GetModelClass.class)[0].name()));
}
}
teacher.test();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
最后我们可以看到结果:com.example.demo.test路径下的有Bean注解标注的类Student、Teacher被加载到了容器之中管理。
当Teacher想调用student类中方法时,没有创建新的对象,没有注入实例化的类,就会有空指针的异常,当我们使用反射将用注解上name属性关联的容器中实例化对象的值赋入到对象之中,就可以成功完成方法调用。
至此就简单的完成了IOC的思想,我们没有新创建一个类去调用需要使用的方法,而是IOC帮你先创建好了实例对象,当你需要使用的时候它将对应的对象从容器中传递给你。