首先要知道 Spring两大核心IOC和AOP(Java轻量级业务层框架Spring两大核心IOC和AOP原理)
IOC:
1.从Java最基本的创建对象开始
如Interface Driven Design接口驱动,接口驱动有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等等,但是接口一定是需要实现的,也就是如下语句迟早要执行:AInterface a = new AInterfaceImp(); 这样一来,耦合关系就产生了,如:
classA { AInterface a; A(){} AMethod()//一个方法 { a = new AInterfaceImp(); } }
Class A与AInterfaceImp就是依赖关系,如果想使用AInterface的另外一个实现就需要更改代码了。当然我们可以建立一个Factory来根据条件生成想要的AInterface的具体实现,即:
InterfaceImplFactory { AInterface create(Object condition) { if(condition == condA) { return new AInterfaceImpA(); } else if(condition == condB) { return new AInterfaceImpB(); } else { return new AInterfaceImp(); } } }
表面上是在一定程度上缓解了以上问题,但实质上这种代码耦合并没有改变。
2.再开始说Spring的原理——IOC
通过IoC模式可以彻底解决这种耦合,它把耦合从代码中移出去,放到统一的XML 文件中,通过一个容器在需要的时候把这个依赖关系形成,即把需要的接口实现注入到需要它的类中,这可能就是“依赖注入”说法的来源了。
IoC模式,系统中通过引入实现了IoC模式的IoC容器,即可由IoC容器来管理对象的生命周期、依赖关系等,从而使得应用程序的配置和依赖性规范与实际的应用程序代码分开。其中一个特点就是通过文本的配置文件进行应用程序组件间相互关系的配置,而不用重新修改并编译具体的代码。
可以把IoC模式看做是工厂模式的升华(动态代理),可以把IoC看作是一个大工厂,只不过这个大工厂里要生成的对象都是在XML文件中给出定义的,然后利用Java 的“反射”编程,根据XML中给出的类名生成相应的对象。从实现来看,IoC是把以前在工厂方法里写死的对象生成代码,改变为由XML文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
(JVM允许Java程序在运行时,去查找Java对象Class.forName(包名+类名),也是动态代理实现的方式,简单工厂定义一个外部键值文件,在工厂里面加载该文件,当要创建某个对象的时候,通过读取文件用键获取值,然后用Class.forName(包名+类名)可以实现一个比较简单的动态代理创建一个对象)
IoC中最基本的Java技术就是“反射”编程。反射又是一个生涩的名词,通俗的说反射就是根据给出的类名(字符串)来生成对象。这种编程方式可以让对象在生成时才决定要生成哪一种对象。反射的应用是很广泛的,像Hibernate、Spring中都是用“反射”做为最基本的技术手段。
IoC最大的好处是什么?因为把对象生成放在了XML里定义,所以当我们需要换一个实现子类将会变成很简单(一般这样的对象都是实现于某种接口的),只要修改XML就可以了,这样我们甚至可以实现对象的热插拔(有点像USB接口和SCSI硬盘了)。
IoC最大的缺点是什么?(1)生成一个对象的步骤变复杂了(事实上操作上还是挺简单的),对于不习惯这种方式的人,会觉得有些别扭和不直观。(2)对象生成因为是使用反射编程,在效率上有些损耗。但相对于IoC提高的维护性和灵活性来说,这点损耗是微不足道的,除非某对象的生成对效率要求特别高。(3)缺少IDE重构操作的支持,如果在Eclipse要对类改名,那么你还需要去XML文件里手工去改了,这似乎是所有XML方式的缺陷所在。
AOP
1.先说为什么要用AOP
比如说登录拦截,按照OOP思想,创建一个抽象类,然后让各个要检验是否登录的类继承该抽象类进行登录拦截,但是由于Java单继承的特点,有些类是无法再次继承的,这样就处理起来就很麻烦,不可能在每个类中写拦截的方法并在需要判断的地方做判断拦截处理,这样代码盈余,重用性低,维护性极差。因此这时候OOP就不适用了。(用接口的做法也是不行的,每个实现类都要实现该方法,当然如果用公共类去做这块的处理,耦合太高,不便于维护。)
但是要记住AOP实际上是一种编程思想,AOP面向切面编程基于IoC,是对OOP的有益补充。
2.再开始说Spring的原理——AOP
Filter的实现、interceputor的实现以及struts2的拦截器的实现都是AOP思想的体现 将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,比如日志记录,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
下面一个例子来说明问题
引用于:http://supben.iteye.com/blog/1520126
先看 advice 类
配置文件
package com.supben.advice; import java.lang.reflect.Method; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; /** * 实现spring advice 接口 * * @author shencl * */ public class TestAdvice implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice { private static final Logger log = LoggerFactory.getLogger(TestAdvice.class); /** * before 通知 */ public void before(Method method, Object[] args, Object target) throws Throwable { log.info(target.getClass().getSimpleName() + "类的" + method.getName() + "方法,执行TestAdvice的before通知"); // 通知要做的业务 if (method.getName().startsWith("get")) { log.info("只有方法名是以get开始的方法,才会执行到这句话...."); } } /** * after 通知 */ public void afterReturning(Object arg0, Method method, Object[] arg2, Object target) throws Throwable { log.info(target.getClass().getSimpleName() + "类的" + method.getName() + "方法,执行TestAdvice的after通知"); } /** * 异常通知 */ public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable { log.info(target.getClass().getSimpleName() + "类的" + method.getName() + "方法,执行TestAdvice的throwing通知"); } }
配置文件
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <context:annotation-config /> <!-- 扫描com.supben 下所有的包--> <context:component-scan base-package="com.supben" /> <bean id="testAdvice" class="com.supben.advice.TestAdvice" /> <aop:config> <aop:advisor pointcut="execution(* *..service.*Service.*(..))" advice-ref="testAdvice" /> </aop:config> </beans>
service接口
package com.supben.service; public interface FirstService { public void get(); public void exception(); }
service实现类
package com.supben.service.impl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.supben.service.FirstService; @Service("firstService") public class FirstServiceImpl implements FirstService { private static final Logger log = LoggerFactory.getLogger(FirstServiceImpl.class); public void get() { log.info("方法执行ing....."); } public void exception() { throw new RuntimeException("测试异常"); } }
测试类
package com.supben.test; import junit.framework.TestCase; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.supben.service.FirstService; import com.supben.spring.SpringContextUtil; public class ServiceTest extends TestCase { /** * 装载spring 配置文件 */ static { new ClassPathXmlApplicationContext("application.xml"); } @Test public void testGet() { FirstService service = SpringContextUtil.getBean("firstService"); service.get(); } @Test public void testGet2() { FirstService service = SpringContextUtil.getBean("firstService"); service.exception(); } }
测试结果:
2012-05-09 15:10:10,028 INFO [com.supben.advice.TestAdvice] - FirstServiceImpl类的get方法,执行TestAdvice的before通知
2012-05-09 15:10:10,030 INFO [com.supben.advice.TestAdvice] - 只有方法名是以get开始的方法,才会执行到这句话....
2012-05-09 15:10:10,032 INFO [com.supben.service.impl.FirstServiceImpl] - 方法执行ing.....
2012-05-09 15:10:10,032 INFO [com.supben.advice.TestAdvice] - FirstServiceImpl类的get方法,执行TestAdvice的after通知
2012-05-09 15:10:10,035 INFO [com.supben.advice.TestAdvice] - FirstServiceImpl类的exception方法,执行TestAdvice的before通知
2012-05-09 15:10:10,035 INFO [com.supben.advice.TestAdvice] - FirstServiceImpl类的exception方法,执行TestAdvice的throwing通知
结果分析:
get方法满足执行之前会 执行 before通知,执行完成后会执行after通知。
exception方法执行之前会执行before 通知,因为方法名不是以get开头,所以不会执行before通知的业务逻辑。然后出现异常了会执行throwing通知,因为出异常了,方法没有执行完毕,所以不会触发after通知。
概念:
切面(aspect): 翻译成外貌更合适:整个程序相当于一个密封的圆柱体,即一个外貌,现在要面向这个东西编程,在不改变原来类(FirstServiceImpl)的情况下,改变里边的代码。通知(advice):TestAdvice里边的before,after,throwing方法都是通知。 常见的有前置通知,后置通知,异常通知。
切点(cut-point):定义通知应该应用在哪些地方,本例是FirstServiceImpl中的get方法和exception方法,一般用正则表达式定义。
切点表达式:配置文件中的execution(* *..service.*Service.*(..)) 是一个切点表达式,表示的是一个一个的方法.比如本例中的表达式,意思是 包目录的最后一级是service,类/接口名 后缀为Service的 class文件里的,方法名为任意名称,参数个数不限的 方法。 * 表示任意,(..)表示方法参数个数不限。
目标对象(traget):FirstService就是目标对象。
此外还有两个重要的概念
引入(Introduction):允许为已存在类添加新方法和属性。
代理(Proxy):将通知应用到目标对象后创建的对象。