Spring IOC 和 AOP 原理深入理解
Spring的IOC和AOP原理
本文讲的是面试之Spring框架IOC和AOP的实现原理,
IoC(Inversion of Control,控制反转)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权发生了转移,交给了使用者外部的第三方容器IoC来创建类对象,并注入到使用者中。
如上图,IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦
IoC(Inversion of Control,控制反转)
(1) IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控。控制权由应用代码中转到了外部容器,控制权的转移是所谓反转。
对于Spring而言,就是由Spring来控制对象的生命周期和对象之间的关系;
IoC还有另外一个名字——“依赖注入(Dependency Injection)”。从名字上理解,所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,即由容器动态地将某种依赖关系注入到组件之中。
(2) 在Spring的工作方式中,所有的类都会在Spring容器中登记,告诉Spring这是个什么东西,你需要什么东西,然后Spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 Spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是Spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被Spring控制,所以这叫控制反转。
(3) 在系统运行中,动态的向某个对象提供它所需要的其他对象。
(4) 依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在Spring中创建被调用者的工作由Spring来完成,然后注入调用者,即所谓的依赖注入or控制反转。
注入方式有两种:依赖注入和设置注入;
IoC的优点:
1)降低了组件之间的耦合
2)降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。
AOP(Aspect Oriented Programming,面向切片编程)
1、AOP面向方面编程基于IoC,是对OOP的有益补充;
2、AOP利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,比如日志记录、事务管理、缓存、异常、持久化等,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
3、 AOP代表的是一个横向的关系,将“对象”比作一个空心的圆柱体,其中封装的是对象的属性和行为;则面向方面编程的方法,就是将这个圆柱体以切面形式剖开,选择性的提供业务逻辑。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹,但完成了效果。
4、 实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
网上有一张非常形象的图,描述了各个概念所处的场景和作用,贴在这里供大家理解:
5、 Spring实现AOP:JDK动态代理和CGLIB代理
JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理;其核心的两个类是InvocationHandler和Proxy。
CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强;需要引入包asm.jar和cglib.jar。
使用AspectJ注入式切面和@AspectJ注解驱动的切面,实际上底层也是通过动态代理实现的。
(6). AOP使用场景:
Authentication 权限检查
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 延迟加载
Debugging 调试
logging, tracing, profiling and monitoring 日志记录,跟踪,优化,校准
Performance optimization 性能优化,效率检查
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务管理
另外Filter的实现和struts2的拦截器的实现都是AOP思想的体现。
Spring 的优点
1.组件之间降低了耦合性 ,实现了软件各层之间的解耦
2.可以使用容易提供的众多服务,如事务管理,消息服务等
3.容器提供单例模式支持
4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能
5.容器提供了众多的辅助类,能加快应用的开发
6.spring对于主流的应用框架提供了集成支持,如hibernate,JPA,Struts等
7.spring属于低侵入式设计,代码的污染极低
8.独立于各种应用服务器
9.Spring的DI机制降低了业务对象替换的复杂性
10.Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可以自由选择spring的部分或全部
什么是DI机制
依赖注入(Dependecy Injection)和控制反转(Inversion of Control)是同一个概念,具体的讲:当某个角色 需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中 创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 因此也称为依赖注入。 spring以动态灵活的方式来管理对象 , 注入的两种方式,设置注入和构造注入。
设置注入的优点:直观,自然
- 通过setter访问器实现
- 灵活性好,但setter方法数量较多
- 时效性差
- 通过无参构造函数实例化
构造注入的优点:可以在构造器中决定依赖关系的顺序。
- 通过构造方法实现
- 灵活性差,仅靠重载限制太多
- 时效性好
- 通过匹配的构造方法实例化,但建议保留无参构造
面试就是要装B
必须得深化点,你得告诉他,aop实现原理其实是java动态代理,但是jdk的动态代理必须实现接口,所以spring的aop是用cglib这个库实现的,cglib使用了asm这个直接操纵字节码的框架,所以可以做到不实现接口的情况下完成动态代理。
最好拿张纸手写两个例子给他,然后他就没什么好问的了
AOP可以说是对OOP的补充和完善。
OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。
也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码
1. AOP
AOP(面向切面)是一种编程范式,提供从另一个角度来考虑程序结构以完善面向对象编程(OOP)。
AOP为开发者提供了一种描述横切关注点的机制,并能够自动将横切关注点织入到面向对象的软件系统中,从而实现了横切关注点的模块化。
AOP能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任,例如事务处理、日志管理、权限控制等,封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
使用AOP的好处
- 降低模块的耦合度
- 使系统容易扩展
- 提高代码复用性
AOP的基本概念
- 连接点(JoinPoint):需要在程序中插入横切关注点的点,连接点可能是在类初始化、方法调用、字段调用或处理异常等等。Spring中只支持方法执行连接点。
- 切入点(Pointcut):一组相关连接点的集合。
- 通知(Advice):在连接点上执行的行为,增强提供了在AOP中需要在切入点所选择的连接点处进行扩展现有行为的手段。包括前置增强(before advice)、后置增强 (after advice)、环绕增强 (around advice)。
- 切面(Aspect):通知和切入点的结合。
- 织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程。
- 代理(Proxy):通过代理方式来对目标对象应用切面。AOP代理可以用JDK动态代理或CGLIB代理实现。
-
目标对象(Target):需要被织入关注点的对象。即被代理的对象。
实现AOP的主要设计模式就是动态代理。
Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理。
1、JDK动态代理模拟
JDK动态代理的两个核心接口(类)分别是InvocationHandler和Proxy。注意:只能代理接口。
public class TimeHandler implements InvocationHandler { // 目标对象 private Object targetObject; public TimeHandler(Object targetObject){ this.targetObject = targetObject; } @Override //关联的这个实现类的方法被调用时将被执行 /*InvocationHandler接口的方法,proxy表示代理,method表示原对象被调用的方法, args表示方法的参数*/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret=null; try{ System.out.println("方法之前:"+System.currentTimeMillis()); //调用目标方法 ret=method.invoke(targetObject, args); System.out.println("方法之后:"+System.currentTimeMillis()); }catch(Exception e){ e.printStackTrace(); System.out.println("error"); throw e; } return ret; } }
TimeHandler 类实现了InvocationHandler接口。实现核心方法invoke,共有3个参数。第一个参数 生成的代理类实例,第二个参数 目标对象的方法,第三个参数 方法的参数值数组。
public class ProxyUtil { @SuppressWarnings("unchecked") public static <T> T proxyOne(ClassLoader loader,Class<?>[] clz,InvocationHandler handler){ return (T)Proxy.newProxyInstance(loader, clz, handler); } }
ProxyUtil 类简单封装了一下Proxy.newProxyInstance()方法。该方法也有3个参数。第一个参数产生代理对象的类加载器,第二个参数目标对象的接口数组,第三个参数就是实现InvocationHandler接口的类实例。
public interface UserManager { public void addUser(String userId, String userName); } public class UserManagerImpl implements UserManager { @Override public void addUser(String userId, String userName) { System.out.println("addUser(id:"+userId+",name:"+userName+")"); } } public static void main(String[] args) { UserManager um = new UserManagerImpl(); LogHandler log = new LogHandler(um); um = ProxyUtil.proxyOne(um.getClass().getClassLoader(), um.getClass().getInterfaces(), log); TimeHandler time = new TimeHandler(um); um=ProxyUtil.proxyOne(um.getClass().getClassLoader(), um.getClass().getInterfaces(), time); um.addUser("1111", "张三"); }
为了演示需要,这边又增加了一个LogHandler,跟TimeHandler代码一样。
2、CGLIB动态代理模拟
CGLIB动态代理的两个核心接口(类)分别是MethodInterceptor和Enhancer。
是不是跟JDK动态代理很相似,用法也差不多。但CGLIB可以代理类和接口。
注意:不能代理final类。
public class TimeInterceptor implements MethodInterceptor { private Object target; public TimeInterceptor(Object target) { this.target = target; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy invocation) throws Throwable { System.out.println("方法之前:"+System.currentTimeMillis()); Object ret = invocation.invoke(target, args); System.out.println("方法之后:"+System.currentTimeMillis()); return ret; } }
intercept方法4个参数。
1. 生成的代理类实例。
2. 被代理对象的方法引用。
3. 方法参数值数组。
4. 代理类对方法的代理引用。
public class ProxyUtil { @SuppressWarnings("unchecked") public static <T> T proxyOne(Class<?> clz,MethodInterceptor interceptor){ return (T)Enhancer.create(clz, interceptor); } }
Enhancer类是CGLib中的字节码增强器。
public class UserManage { public void addUser(String userId, String userName) { System.out.println("addUser(id:"+userId+",name:"+userName+")"); } } public static void main(String[] args) { UserManage um = new UserManage(); TimeInterceptor time = new TimeInterceptor(um); um = ProxyUtil.proxyOne(um.getClass(), time); um.addUser("111", "老王"); }
2. IOC
IOC(控制反转)就是依赖倒置原则的一种代码设计思路。就是把原先在代码里面需要实现的对象创建、对象之间的依赖,反转给容器来帮忙实现。
Spring IOC容器通过xml,注解等其它方式配置类及类之间的依赖关系,完成了对象的创建和依赖的管理注入。实现IOC的主要设计模式是工厂模式。
使用IOC的好处
集中管理,实现类的可配置和易管理。
降低了类与类之间的耦合度。
简单模拟IOC
public interface BeanFactory { Object getBean(String id); } public class ClassPathXmlApplicationContext implements BeanFactory { // 容器,用来存放注入的Bean private Map<String, Object> container = new HashMap<String, Object>(); // 解析xml文件,通过反射将配置的bean放到container中 public ClassPathXmlApplicationContext(String fileName) throws Exception{ SAXBuilder sb = new SAXBuilder(); Document doc =sb.build(ClassPathXmlApplicationContext.class.getResource("/"+fileName)); Element root = doc.getRootElement(); List<Element> list = XPath.selectNodes(root, "/beans/bean"); for (int i = 0; i < list.size(); i++) { Element bean = list.get(i); String id = bean.getAttributeValue("id"); String clazz = bean.getAttributeValue("class"); Object o = Class.forName(clazz).newInstance(); container.put(id, o); } } @Override public Object getBean(String id) { return container.get(id); } }
需要导入 jdom.jar包。
<?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="people" class="com.ioc.People" /> <bean id="chicken" class="com.ioc.Chicken" /> <bean id="dog" class="com.ioc.Dog" /> </beans> public interface Animal { void say(); } public class Dog implements Animal { @Override public void say() { System.out.println("汪汪"); } } public class Chicken implements Animal { @Override public void say() { System.out.println("鸡你很美"); } } public class People { public void info(){ System.out.println("小明-23岁"); } } public static void main(String[] args) throws Exception { // 加载配置文件 BeanFactory f = new ClassPathXmlApplicationContext("applicationContext.xml"); Object os = f.getBean("dog"); Animal dog = (Animal)os; dog.say(); Object op = f.getBean("chicken"); Animal chicken = (Animal)op; chicken.say(); Object p = f.getBean("people"); People people= (Animal)p; people.info(); }
参考资料
参考推荐:
Maven 搭建 SpringMVC+Spring+MyBatis 框架实战
Spring 具体分析 applicationContext.xml 和 spring3-servlet.xml
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2021-02-25 09:09:07
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!