IOC介绍
IOC全称是控制反转(Inversion of Control),又名依赖注入,其实这两个概念是一样的,控制反转是学术名词,晦涩难懂,为了便于理解,后来引入依赖注入方便理解。
为什么要使用ioc
在没有使用ioc时,项目的耦合性很大,往往在更新程序时,是不能直接删除原功能,要保留原来的部分,那么就要用一个新的类来替换旧的类,但这样一来,所有有关的类都需要修改,这样就很麻烦,而且忘记改一个,就会报错,程序运行不了,而ioc可以实现类与类解耦,层与层解耦,达到只需要更改一处,就可以更新整个项目,方便维护拓展。
如何实现ioc
要达到解耦的目的,就需要用到反射和接口来注入对象
1.创建两个类和一个测试类
//User类
public class User {
private UserDAO userDAO;
public UserDAO getUserDAO() {
return userDAO;
}
}
//UserDAO类
public class UserDAO {
}
//测试类
public class Demo {
public static void main(String[] args) {
User user = new User();
user.getUserDAO();
}
}
现在我想新建一个UserDAO1来代替UserDAO,那么按照以前的就必须修改User和Demo,如果关系还有别的类需要这两个类,那么改的更多,这就要用到反射来注入,首先让UserDAO和UserDAO1都实现IUserDAO,并且让User类的属性变为IUserDAO
public class User {
private IUserDAO userDAO;
public IUserDAO getUserDAO() {
return userDAO;
}
在demo类中用反射来注入userDAO1
public static void main(String[] args) throws Exception {
User user = new User();
//如果要更换UserDAO类,只需要添加一个新的类让他实现IuserDAO,这里修改一下就可以了
IUserDAO userDAO = new UserDAO1();
//通过反射取得UserDAO的属性并把要更改的类的实例注入
Class<? extends User> aClass = user.getClass();
Field field = aClass.getDeclaredField("userDAO");
field.setAccessible(false);
field.set(user,userDAO);
}
到这了,肯定会纳闷,这么麻烦我还不如一个个去new对象,一行代码就搞定了,但要是项目很大,类很多呢,一个个去找吗?万一找漏一个,就是bug。而且这个只需要写一次,以后再多的类,再多的项目都可以用这一个就可以了。
2.有很多属性甚至方法要注入呢?
那就要用到注解了,先自定义一个注解MyField,给两个属性,都给一个默认值
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MyField {
Class name() default Class.class;
String value() default "";
}
然后将要注入的属性上加上注解,并且加上需要注入的值
public class User {
@MyField(name = UserDAO1.class)
public IUserDAO userDAO;
@MyField(value = "张三")
public String name;
public IUserDAO getUserDAO() {
return userDAO;
}
}
在demo中对user中的元素进行注入
public static void main(String[] args) throws Exception {
User user = new User();
//遍历获取user中的所有元素
Field[] declaredFields = user.getClass().getDeclaredFields();
for (Field field:declaredFields){
MyField annotation = field.getAnnotation(MyField.class);
//判断元素上面是否有MyFiled注解
if (annotation != null){
//如果有就获取name和value的值
Class userDAO1 = annotation.name();
String name = annotation.value();
//如果name的值不为空,且不等于默认值,就说明需要注入,直接注入
if (userDAO1!=null && !userDAO1.equals(Class.class)){
field.set(user,userDAO1.newInstance());
}
//如果value值不为空且不等于默认值,就说明需要注入,直接注入
if(name!=null && !name.equals("")){
field.set(user,name);
}
}
}
System.out.println(user);
}
输出结果为User{userDAO=com.qf.demo.UserDAO1@511d50c0, name=‘张三’}
说明注入成功,现在我们发现如果要换UserDAO要修改的就只有注解上的值,而且注解严格来说不算java代码,这样等于说不需要修改java代码,达到解耦目的。
3.要注入的不只有User类,有很多类呢?
其实原理也一样,我在所有需要注入的类上都加一个自定义的注解SpringComponent,然后遍历整个文件夹,获取到所有的java文件,在将有SpringComponent注解的类直接传给上面写好的inject方法中
public static void main(String[] args) throws Exception {
Injection injection = new Injection();
injection.init("com.qf.demo");
}
public void init(String packageName) throws Exception {
String basePath="D:\\program\\idea\\day52_demo\\src\\main\\java\\";
//将传入的"com.qf.demo"转换成"com\qf\demo"
String replace = packageName.replace(".", "/");
File file = new File(basePath + "\\"+replace);
//遍历该文件夹下所有文件
File[] files = file.listFiles();
for (File f:files){
String name = f.getName();
if(f.isFile()){
//只有java文件才是哦们需要的,如果是文件就判断是不是以".java"结尾的
if (name.endsWith(".java")){
//如果是就以“.”将文件名分割成"demo"和"java"两部分
String[] split = name.split("\\.");
String className = split[0];
Class<?> aClass = Class.forName(packageName +"." +className);
//判断类上是否有SpringComponent注解,如果有就调用inject方法,将类对象传入
SpringComponent annotation = aClass.getAnnotation(SpringComponent.class);
if (annotation != null){
inject(aClass);
}
}
}else{
//如果不是文件就是文件夹,就使用递归循环该方法
String childPath=packageName+"."+name;
init(childPath);
}
}
}
public <T>void inject(Class<T> clazz) throws Exception {
//创建传入类对象的实例对象
T t = clazz.newInstance();
Field[] declaredFields = clazz.getClass().getDeclaredFields();
for (Field field:declaredFields){
MyField annotation = field.getAnnotation(MyField.class);
//判断元素上面是否有MyFiled注解
if (annotation != null){
//如果有就获取name和value的值
Class aClass = annotation.name();
String name = annotation.value();
//如果name的值不为空,且不等于默认值,就说明需要注入,直接注入
if (aClass!=null && !aClass.equals(Class.class)){
field.set(t,aClass.newInstance());
}
//如果value值不为空且不等于默认值,就说明需要注入,直接注入
if(name!=null && !name.equals("")){
field.set(t,name);
}
}
}
}
结尾
现在虽然已经实现了ioc的最基础的功能,但还有很多缺点,比如:
1.每次创建新项目就必须改地址basePath
2.我要取对象内的元素的话还是要取new一个对象,和以前没啥区别
3.如果A是B的元素,而B又先执行,那么B中的A就为空
…