inversion of control【控制反转】,将对象的创建权反转给spring
注意:IOC只是用来降低程序的耦合性,让代码更为合理,不能实现具体的业务功能。
好处:
• 解耦,降低程序将的耦合性
底层原理:
• 工厂+配置文件+反射
我们现在编写的代码都是遵循的 JavaEE 三层架构,分为 web 层,service 层,dao层。但是之前写的代码存在一些潜在的问题:
以 Servlet 中的代码为例,之前的代码在得到某个 Service 的实现类的时候都是直接 new 实现类。一旦实现类产生了改变,new 实现类的代码就要跟着修改。如在 UserServlet 中需要使用 UserService 的实现:
//声明UserService业务对象 private UserService service = new UserServiceImpl();
如果现在实现方案产生了改变,我们在业务层写了一个新的实现类UserServiceImpl2
public class UserServiceImpl2 implements UserService {
xxx(){
}
...
}
虽然现在是 service 层的实现产生了变化,但是 web 层的代码需要作出修改,我们需要将 UserServlet 的代码改为:
//声明UserService业务对象 private UserService service = new UserServiceImpl2();
其中某一层的实现产生改变,相关的其它层的代码也必须要跟着产生变化,这就叫代码的耦合
要解决这个问题,需要两个步骤:
【步骤1】引入一个名为 applicationContext.xml 的配置文件,格式如下(maven 工程放入 resources 目录):
...
在这个配置文件中定义了很多的 bean 标签,每个 bean 标签都有一个唯一的 id 属性和一个对应的 class 属性,class 属性的值就是某个 service 或者 其它 实现类的全限定名。
【步骤2】创建一个 BeanFactory 工厂类,在这个类中提供一个名为 getBean 的工具方法,在方法中根据用户传入的 id,解析 applicationContext.xml 文件,得到 id 对应的 class 值,反射生成实例并返回。
public class BeanFactory {
public static Object getBean(String id){
try {
// 获取xml文件的真实路径
String path = BeanFactory.class.getClassLoader().getResource("applicationContext.xml").getPath();
File file = new File(path);
// 解析xml文件得到文档对象
document document = Jsoup.parse(file, "UTF-8");
// 从文档中根据id值获取bean标签对应的元素
Element beanEle = document.getElementById(id);
// 根据class属性名获取属性值
String className = beanEle.attr("class");
// 将class属性值对应的类加载进内存
Class> clazz = Class.forName(className);
// 创建实例对象
Object obj = clazz.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
注意:代码中使用了 jsoup 的 api,所以要在工程的 pom.xml 文件中引入 jsoup 对应的依赖。
org.jsoup jsoup 1.10.3
• 经过以上修改,当我们需要使用某一个功能的实现类的时候,不再是直接 new 实现类,而是通过 BeanFactory 中的 getBean 方法获取,如:
userService = (UserService) BeanFactory.getBean("userService");
• 而且,当我们需要切换某一个功能的实现类的时候,只需要修改配置文件中该实现类对应的 class 属性的值,其它代码不需要经过任何修改。例如,现在userService的实现方案产生了改变,有一个新的 UserServiceImpl2出现,我们只需要修改配置文件中 userService对应的 class 值即可
这样就达到了在切换底层实现时没有修改 java 源代码的需求,也就是解除了层与层之间的耦合。这个通过 工厂+配置文件+反射 实现解耦的方案,就是 Spring 中的 IOC(控制反转) 思想。
AOP 概念Aspect Oriented Programming【面写切面编程】
是一种在运行期间通过动态代理,实现在不修改源代码的情况下给程序统一添加功能的编程思想
需求:需要统计 service 中所有 findXXX 相关的查询方法所消耗的时长
分析:
• 不可能在所有 service 中的所有查询方法中加入统计时长的代码,最好这个代码只写一次
• 之前 dao 中的查询方法没有统计时长的功能,现在我希望它有这个功能,其实就是要增强方法的功能,可以想到动态代理。
• 动态代理是对某个实现了接口的对象进行增强,我们的 dao 都实现了接口,这些 dao 的对象再哪创建的?在 BeanFactory 中反射生成的。所以这个代码应该写在 BeanFactory 中。代码如下:
public class BeanFactory {
public static Object getBean(String id){
try {
// 获取xml文件的真实路径
String path = BeanFactory.class.getClassLoader().getResource("applicationContext.xml").getPath();
File file = new File(path);
// 解析xml文件得到文档对象
document document = Jsoup.parse(file, "UTF-8");
// 从文档中根据id值获取bean标签对应的元素
Element beanEle = document.getElementById(id);
// 根据class属性名获取属性值
String className = beanEle.attr("class");
// 将class属性值对应的类加载进内存
Class> clazz = Class.forName(className);
// 创建实例对象
Object obj = clazz.newInstance(); // 被代理对象
// 判断当前获取的是不是service实现类
if(id.endsWith("Service")){
Object objProxy = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new InvocationHandler() {
// 调用代理对象的任何方法,会执行invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名
String methodName = method.getName();
// 判断是不是查询方法
if (methodName.startsWith("find")){
// 获取开始时间
long startTime = System.currentTimeMillis();
// 执行查询方法
Object rtValue = method.invoke(obj,args);
// 获取结束时间
long endTime = System.currentTimeMillis();
// 获取类名
String simpleName = obj.getClass().getSimpleName();
System.out.println(simpleName+"类的"+methodName+"方法执行消耗了"+(endTime-startTime)+"时长");
// 返回结果
return rtValue;
}else {
// 其它方法,原封不动的调用
return method.invoke(obj, args);
}
}
});
// 返回增强之后的代理对象
return objProxy;
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
这部分代码,就是 Spring 中的另一个核心思想 AOP(面向切面编程)
概念:AOP,面向切面编程
作用:在不修改源代码的情况下统一增强程序的功能
原理:动态代理



