Spring AOP
1. 代理模式
1.1. 静态代理
程序中经常需要为某些动作或事件作下记录,以便在事后检测或作为排错的依据,先看一个简单的例子:
import java.util.logging.*;
public class HelloSpeaker {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void hello(String name) {
logger.log(Level.INFO, "hello method starts....");
System.out.println("Hello, " + name);
logger.log(Level.INFO, "hello method ends....");
}
}
HelloSpeaker在执行hello方法时,我们希望能记录该方法已经执行及结束,最简单的做法就是在执行前后加上记录动作,然而Logger介入了HelloSpeaker中,记录这个动作并不属于HelloSpeaker,这使得HelloSpeaker的职责加重。
如果程序中这种记录的动作到处都有需求,上面的这种写法势必造成我们必须复制记录动作的程序代码,使得记录动作的难度加大。
通过静态代理可以适当的解决上面出现的问题。
例如:
1) 定义一接口:
public interface IHello {
public void hello(String name);
}
2) 定义一实现类
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
3) 定义代理类:
import java.util.logging.*;
public class HelloProxy implements IHello {
private Logger logger = Logger.getLogger(this.getClass().getName());
private IHello helloObject;
public HelloProxy(IHello helloObject) {
this.helloObject = helloObject;
}
public void hello(String name) {
logger.log(Level.INFO, "hello method starts....");
helloObject.hello(name);
logger.log(Level.INFO, "hello method ends....");
}
}
4) 定义测试类:
public class TestStaticProxy {
public static void main(String[] args){
IHello helloProxy = new HelloProxy(new HelloSpeaker());
helloProxy.hello("Justin");
}
}
代理类HelloProxy将代理真正的HelloSpeaker来执行hello(),并在其后加上记录的动作,这使得我们的HelloSpeaker在撰写时不必介入记录动作,HelloSpeaker可以专心於它的职责。
1.2. 动态代理
通过学习静态代理的基本范例,我们可以了解到,代理物件的一个界面只服务于一种类型的物件,而且如果要代理的方法很多,我们必须为每个方法进行代理,静态代理在程式规模稍大时就必定无法胜任。
Java在JDK1.3之后动态代理的引入很好的解决了这个问题,一个handler服务于各个物件,前提是这个个hander必须实现java.lang.reflect.InvocationHandler这个接口.
例如:
1) 定义一接口:
public interface IHello {
public void hello(String name);
}
2) 定义一实现类
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
}
3) 定义动态代理类
import java.util.logging.*;
import java.lang.reflect.*;
public class LogHandler implements InvocationHandler {
private Logger logger = Logger.getLogger(this.getClass().getName());
private Object delegate; //定义目标对象
public Object bind(Object delegate) { //方法是要返回代理对象,参数代表要传过来的目标对象。
this.delegate = delegate;
return Proxy.newProxyInstance( 返回代理类
delegate.getClass().getClassLoader(),//返回类装载器
delegate.getClass().getInterfaces(),返回类所实现的接口
this);//代表实现了InvocationHandler接口的一个对象,这里传入的是自身
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
try {
logger.log(Level.INFO, "method starts..." + method);
result = method.invoke(delegate, args);
logger.log(Level.INFO, "method ends..." + method);
} catch (Exception e){
logger.log(Level.INFO, e.toString());
}
return result;
}
}
Proxy为动态代理类,是动态代理的核心类,其作用类似于代理模式中的代理。Proxy类中包含的全是静态的方法和成员变量,这是一个纯粹的工具类。
java.lang.reflect.Proxy.newProxyInstance方法根据传入的接口类型(obj.getClass().getInterfaces())动态构造一个代理类实例返回,这个代理类是JVM。
在内存中动态构造的动态类,它实现了传入的接口列表中所包含的所有接口。
程序调用代理的目标方法时,自动调用invoke方法,该处理类并未与任何接口或类耦合,完全是通用的,它的目标实例是object类型,可以是任何类型。
invoke方法中第一个参数是指具体的被代理类,即代理模式中的目标对象;method是被代理的方法,args为该方法的参数数组。
4) 定义测试类
public class TestDynamicProxy {
public static void main(String[] args){
LogHandler logHandler = new LogHandler();
IHello helloProxy = (IHello) logHandler.bind(new HelloSpeaker());
helloProxy.hello("Justin");}
}
LogHandler不在服务于特定物件,而HelloSpeaker也不用插入任何有关于记录的动作,它不用意识到记录动作的存在。
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理(invoke).
Dynamic Proxy要求被代理的必须是接口的实现类,否则无法为其构造相应的动态类。因此,Spring对接口实现类采用DynamicProxy实现AOP,而对没有实现任何接口的类,则通过CGLIB实现AOP代理。
2. AOP基础
需求
· 为ProductDaoImpl的save方法增加授权检查功能
· 为ProductDaoImpl的save方法增加事务的处理功能
· 为ProductDaoImpl的save方法增加打开关闭连接的功能
2.1. 基本概念
AOP(Aspect Orient Programming),面向切面编程,作为面向对象编程(oop)的一种补充,面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。
oop对业务处理过程中的实体及其属性和行为进行了抽象封装,以获得更加清晰高效的逻辑划分,研究的是一种”静态的”领域。
Aop则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,研究的是一种“动态的”领域。
可以这样理解,面向对象编程是从静态角度考虑程序结构,而面向切面编程是从动态角度考虑程序运行过程。
Spring AOP是Spring框架的一个重要组件,极好的补充了Spring IOC容器的功能。Spring AOP将Spring IOC容器与AOP组件紧密结合,丰富了IOC容器的功能。
Aop的关键是发现横切性问题,最后把它模块化。这个模块化的类通常称为Aspect.
Spring缺省使用j2se动态代理(dynamic proxies)来作为AOP代理。Spring也支持使用CGLIB代理,对于需要代理类而不是代理接口的时候CGLIB是很有必要的。如果一个业务对象并没有实现一个接口,默认会使用CGLIB。作为面向对象的最佳实践,业务对象都会实现一个或多个接口。
2.2. AOP术语
1) 切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ风格)来实现。
2) 连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点 总是 代表一个方法的执行。 通过声明一个org.aspectj.lang.JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。
3) 通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的处理动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。。
4) 切入点(Pointcut): 一系列连接点的集合,它指明处理方式(Advice)将在何时被触发。
5) 引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。 Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。 例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
6) 目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
7) AOP代理(AOP Proxy): AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
8) 织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。
通知的类型:
1) 前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
2) 返回后通知(After returning advice): 在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
3) 抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。
4) 后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
5) 环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
2.3. AOP代理
所谓的AOP代理,就是AOP框架动态创建的对象,这个对象可以作为目标对象的替代品,而AOP代理提供比目标对象更加强大的功能。
Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也可以使用CGLIB代理。实现类的代理,而不是接口。如果业务对象没有实现接口,默认使用CGLIB代理。
第一个Spring AOP程式
1) 定义接口:
package com.sinoest;
public interface IHello {
public void hello(String name);
public void morning(String name);
}
2) 定义接口实现类:
package com.sinoest;
public class HelloSpeaker implements IHello {
public void hello(String name) {
System.out.println("Hello, " + name);
}
public void morning(String name) {
System.out.println("Morning, " + name);
}
}
3) 定义代理类:
Aopalliance.jar
package com.sinoest;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.util.logging.*;
public class LogInterceptor implements MethodInterceptor {
private Logger logger = Logger.getLogger(this.getClass().getName());
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
logger.log(Level.INFO, "method starts..." + methodInvocation.getMethod());
try {
Object result = methodInvocation.proceed();
return result;
}
finally {
logger.log(Level.INFO, "method ends..." + methodInvocation.getMethod() + "\n");
}
}
}
Invoke()中的MethodInvocation参数包括了下一个Interceptor的信息、被执行的方法、以及方法所需要的参数。
4) 编写Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.sinoest.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>logInterceptor</value>
</list>
</property>
</bean>
</beans>
proxyInterfaces属性设定所要代理的介面,如果没有设定这个属性,则会基于targat属性所设定的bean来自动检测介面,基本上要代理一个类别所有的方法也是可能的,target属性则是我们要代理的目标物件,而interceptorNames属性用来设定advices,也可以是advisors,其中interceptorNames中设定的顺序就是advices执行的顺序,所有advices执行完后,会执行target物件上的方法。
interceptorNames中所设定的advices会套用至target物件上所代理的所有方法,如果您希望只套用在某些方法上,可以使用
org.springframework.aop.support.RegexpMethodPointcutAdvisor。
例如:希望执行target物件上的hello()方法时才介入interceptor.则可以如下设定。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="regexpMethodPointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logInterceptor"/>
</property>
<property name="patterns">
<value>.*hello</value>
</property>
</bean>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.sinoest.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>regexpMethodPointcutAdvisor</value>
</list>
</property>
</bean>
</beans>
5) 编写测试类
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");;
IHello helloProxy = (IHello) context.getBean("helloProxy");
helloProxy.hello("Justin");
helloProxy.morning("momor");
}
}
2.4. Spring通知(Advice)
Advice类型(advice type)有Around、Before、Throws、After Returning。这些Advice类型会分别在以下的时间被调用:在JoinPoint前后、JointPoint前、JointPoint丢出例外时、JointPoint执行完后。
² Interception Around通知
在上一个例子中,我们在hello()这个JointPoint上会调用ArountdAdvice. 即我们的LogHander.
² Before Advice 或 After Returning Advice
Spring中实现 Before Advice 或是 After Returning Advice,作法与Interceptor类似,只要分别实现org.springframework.aop.MethodBeforeAdvice。
及org.springframework.aop.AfterReturningAdvice。两个介面的定义如下:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
MethodBeforeAdvice的before()方法会在被呼叫的方法之前调用。
AfterReturningAdvice的afterReturn()方法会在被呼叫的方法之后调用。
例如:
package com.sinoest;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.logging.*;
public class LogBeforeAdvice implements MethodBeforeAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void before(Method method, Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "method starts..." + method);
}
}
package com.sinoest;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
import java.util.logging.*;
public class LogAfterAdvice implements AfterReturningAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterReturning(Object object, Method method,
Object[] args, Object target)
throws Throwable {
logger.log(Level.INFO, "method ends..." + method);
}
}
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="logBeforeAdvice" class="com.sinoest.LogBeforeAdvice"/>
<bean id="logAfterAdvice" class="com.sinoest.LogAfterAdvice"/>
<bean id="logBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logBeforeAdvice"/>
</property>
<property name="patterns">
<value>com\.sinoest\.IHello\.hello</value>
</property>
</bean>
<bean id="logAfterAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logAfterAdvice"/>
</property>
<property name="patterns">
<value>com\.sinoest\.IHello\.morning</value>
</property>
</bean>
<bean id="helloSpeaker" class="com.sinoest.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>onlyfun.caterpillar.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>logBeforeAdvisor</value>
<value>logAfterAdvisor</value>
</list>
</property>
</bean>
</beans>
这里仍使用了RegexMethodPointcutAdvisor,分别设定在执行hello()之前介入logBeforeAdvisor,以及morning()方法之后使用logAfterAdvisor.
² Throws通知
若想在例外发生时通知作某些事,您可以使用Throws Advice,在Spring中想要使用Throws Advice,必须继承org.springframework.aop.ThrowsAdvice界面。这个界面没有定义任何方法,
您可以在当中定义任意的方法名称,但必须遵循以下的形式:
代码:
methodName([Method], [args], [target], subclassOfThrowable);
Method、args与targer都是可以省略的,方法中一定要的是subclassofThrowable,在例外发生时,会检测所设定的Throws Advice是否有符合例外类型的方法,如果有的就通知它执行。
例如:
定义IHello接口
package com.sinoest;
public interface IHello {
public void hello(String name) throws Exception;
public void morning(String name) throws Exception;
}
定义实现类:
package com.sinoest;
public class HelloSpeaker implements IHello {
public void hello(String name) throws Exception{
System.out.println("Hello, " + name);
}
public void morning(String name) throws Exception {
System.out.println("Morning, " + name);
throw new Exception("exception happened!");
}
}
定义例外处理:
package com.sinoest;
import java.lang.reflect.*;
import java.util.logging.*;
import org.springframework.aop.ThrowsAdvice;
public class CustomExceptionAdvice implements ThrowsAdvice {
private Logger logger = Logger.getLogger(this.getClass().getName());
public void afterThrowing(Method method,
Object[] args,
Object target,
Throwable subclass) {
// log the exception
logger.log(Level.INFO,
"Logging that a " +
subclass +
"Exception was thrown in " + method);
}
}
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="customExceptionAdvice" class="onlyfun.caterpillar.CustomExceptionAdvice"/>
<bean id="helloSpeaker" class="onlyfun.caterpillar.HelloSpeaker"/>
<bean id="helloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>onlyfun.caterpillar.IHello</value>
</property>
<property name="target">
<ref bean="helloSpeaker"/>
</property>
<property name="interceptorNames">
<list>
<value>customExceptionAdvice</value>
</list>
</property>
</bean>
</beans>
定义测试类:
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
IHello helloProxy = (IHello) context.getBean("helloProxy");
try {
helloProxy.hello("Justin");
helloProxy.morning("momor");
}
catch(Exception e) {
// do some exception handling here
}
}
}
² Introduction通知
不常用
2.5. Spring自动代理
Spring AOP 主要是基于动态代理完成的,也就是说每一个要使用Spring AOP完成某个动作的物件,都需要为其建立一个代理,如果您想要为每一个物件的某些(或所有)方法加上记录动作,为每一个物件都建立一个代理物件,是一件很重复且无趣的工作。
使用自动代理可以解决上述问题,自动代理需要使用org.springframework.aop.framework.autoproxy,defaultAdvisorAutoProxyCreator,当呼叫某个Bean时,defaultAdvisorAutoProxyCreator会自动寻找Advisor的类型,然后自动为该物件建立代理。
定义配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
<bean id="logInterceptor" class="com.sinoest.LogInterceptor"/>
<bean id="regexpMethodPointcutAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice">
<ref bean="logInterceptor"/>
</property>
<property name="patterns">
<value>.*hello</value>
</property>
</bean>
<bean id="helloSpeaker" class="onlyfun.caterpillar.HelloSpeaker"/>
</beans>
定义测试类:
package com.sinoest;
import org.springframework.context.*;
import org.springframework.context.support.*;
public class SpringTest {
public static void main(String[] args) {
ApplicationContext context =new ClassPathXmlApplicationContext("applicationContext.xml");
IHello helloSpeaker = (IHello) context.getBean("helloSpeaker");
try {
helloSpeaker.hello("Justin");
helloSpeaker.morning("momor");
}
catch(Exception e) {
// do some exception handling here
}
}
}
3. @AspectJ支持
“@AspectJ”使用了Java 5注解,可以将切面声明为普通的Java类。Spring2.0使用了和AspectJ5一样的注解,使用了AspectJ提供的的一个库来做切点(pointcut)解析和匹配。
3.1. 启用@AspectJ支持
为了在Spring配置中使用@AspectJ aspects,你必须首先启用Spring对基于@AspectJ aspects的配置支持,自动代理(autoproxying)基于通知是否来自这些切面。自动代理是指Spring会判断一个bean是够使用了一个或多个切面通知,并据此自动生成相应的代理以拦截其方法调用,并且确认通知是否如期举行。
通过在你的Spring的配置中引入下列元素来启用Spring对@AspectJ的支持:
<aop:aspectj-autoproxy/>
启用Spring对@AspectJ支持的前提是需要在你的应用程序的classpath 中引入两个AspectJ库:
aspectjweaver.jar和aspectjrt.jar.
3.2. 声明一个切面
在启用@AspectJ支持的情况下,在application context中定义的任意带有一个@Aspect切面的bean都将被Spring自动识别并用于配置在Spring AOP.
例如:
@Aspect
public class SecurityHandler {
}
配置文件中定义
<bean id=”securityHandler” class="com".sinoest.spring.SecutityHandler”/>
切面(用@Aspect注解的类)和其他类一样有方法和字段定义。他们可能包括切入点和通知等。
3.3. 声明一个切入点(pointcut)
一个切入点的声明有两个部分:
1、 一个包含名字和任意参数的签名。(可以通过一个普通方法的定义来提供)
2、 一个切入点表达式。(该表达式决定了我们关注哪个方法的执行,并且切入点表达式使用@Pointcut注解来表示)
切入点表达式可以使用’&’,’||’和’!’来合并。
例如:
@Pointcut("execution(* add*(..)) || execution(* del*(..))")//the pointcut expression
private void allAddMethod(){}; //the pointcut signature
3.4. 声明通知
通知是跟一个切入点表达式关联起来的,并且在切入点匹配的方法执行之前或者之后或者之前和之后运行。
² 前置通知(Before advice)
一个切面里使用@Before注解声明前置通知。
@Before("allAddMethod()")
private void checkSecurity() {
System.out.println("----------checkSecurity()---------------");
}
常见表达式的例子。
任意公共方法的执行:
execution(public **(..)):
任何一个以”set”开始的方法的执行:
Execution(* set*(..));
AccountService接口的任意方法的执行:
Execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
Execution(* com.xyz.service.*.*(..));
定义在service包里的任意方法的执行:
Execution(* com.xyz.service.*.*(..)):
² 返回后通知(Before advice)
返回后通知通常在一个匹配的方法返回的时候执行。
Import org.aspectj.lang.annotaion.Aspect;
Import org.aspectj.lang.annotation.AfterReturnning;
@Aspect
Public class AfterReturnningExample{
@AfterReturnning(“com.xyz.myapp.SystemArchitecture.dataAccessOperation”)
Public void doAccessCheck(){
}
}
² 抛出后通知(After throwing advice)
抛出后通知在一个方法抛出异常后执行。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
Public class AfterThrowingExample {
@AfterThrowing(“com.xyz.myapp.SystemArchitecture.dataAccessOperation()”)
public void doRecoveryActions(){
}
}
² 后通知(After (finally) advice)
不论一个方法如何结束,在它结束后都会运行。
Import org.aspectj.lang.annotation.Aspect;
Import org.aspectj.lang.annotation.After;
@Aspect
Public class AfterFinnallyExample{
@After(“com.xyz.myapp.SystemArchitecture.dataAccessOperation()”)
Public void doReleaseLock(){
}
}
² 环绕通知(Around Advice)
环绕通知在一个方法执行之前和之后执行。它使得通知有机会既在一个方法执行之前又在执行之后运行。并且,它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。环绕通知经常在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。请尽量使用简单的满足你需求的通知。(比如前置通知(beforeadvice)也可适用的情况下不要适用环绕通知)
环绕通知适用@Around注解来声明。通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法将会导致潜在的连接点方法执行。
Import org.aspect.lang.annotation.Aspect;
Import org.aspectj.lang.annotation.Around;
Import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
Public class AroundExample{
@Around(“com.xyz.myapp.SystemArchitecture.businessService()”);
Public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable{
//start stopwatch
Object retval=pjp.proceed()
//stop stopwatch
Return retVal;
}
}
4. Schema-based AOP support支持
Spring 2.0也提供了使用新的”aop”命名空间来定义一个切面。
4.1. 声明一个切面
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config><!—切面跟元素->
<aop:aspect id="security" ref="securityHandler">
..
</aop:aspect>
</aop:config>
</beans>
4.2. 声明一个切入点
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config>
<aop:aspect id="security" ref="securityHandler">
<!—声明一切入点—>
<aop:pointcut id="allAddMethod"expression="execution(* add*(..))"/>
<aop:before method="checkSecurity"pointcut-ref="allAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
4.2. 声明通知
<beans>
<bean id="securityHandler" class="com.sinoest.spring.SecurityHandler"/>
<bean id="userManager" class="com.sinoest.spring.UserManagerImpl"/>
<aop:config>
<aop:aspect id="security" ref="securityHandler">
<!—声明一切入点—>
<aop:pointcut id="allAddMethod"expression="execution(* com.sinoest.spring.UserManagerImpl.add*(..))"/>
<!—声明通知à
<aop:before method="checkSecurity" pointcut-ref="allAddMethod"/>
</aop:aspect>
</aop:config>
</beans>
5.代理模式选择
² Spring AOP还是完全使用AspectJ?
做能起作用的最简单的事。Spring AOP比完全使用AspectJ更加简单,因为它不需要引入AspectJ的编译器到你的开发和构建过程中。
² Spring AOP中使用@AspectJ还是XML?
如果你选择使用Spring AOP,那么你可以选择@AspectJ或XML风格。总的来说,如果你使用Java5,我们建议使用@AspectJ风格。显然如果你不是运行在Java5上,xml风格是最佳的选择。
XML风格对现有的Spring用户来说更加习惯,当使用AOP作为工具来配置企业服务时,XML会是一个很好的选择。对于XML风格,从你的配置中可以清晰的表明在系统中存在哪些切面。
XML风格有两个缺点:
1、它不能完全将需求实现的地方封装到一个位置。
2、XML风格同@AspectJ风格所能表达的内容相比有更多的限制。仅仅支持”sigleton”切面实例模型,并且不能在XML中组合命名连接点的声明。
6.Spring代理机制
Spring AOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理。(建议尽量使用JDK的动态代理)
如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理。若目标对象没有实现任何接口,则创建一个CGLIB代理。
7. 练习和作业
7.1. 练习
练习课上的例子.
7.2. 作业
总结所学内容
_______________________________________________________________________________________________
IOC
spring ioc原理(浅显易懂)
IoC与DI
首先想说说IoC(Inversionof Control,控制倒转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。
那么IoC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,比如长得像李嘉欣,身材像林熙雷,唱歌像周杰伦,速度像卡洛斯,技术像齐达内之类的,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。如果你还不明白的话,我决定放弃。
IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(DependencyInjection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢?Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。关于反射的相关资料请查阅javadoc。
理解了IoC和DI的概念后,一切都将变得简单明了,剩下的工作只是在spring的框架中堆积木而已。
如果还不明白,放弃java吧!
下面来让大家了解一下Spring到底是怎么运行的。
Java代码
publicstatic void main(String[] args) {
ApplicationContext context = newFileSystemXmlApplicationContext(
"applicationContext.xml");
Animal animal = (Animal)context.getBean("animal");
animal.say();
}
这段代码你一定很熟悉吧,不过还是让我们分析一下它吧,首先是applicationContext.xml
Java代码
<beanid="animal" class="phz.springframework.test.Cat">
<property name="name"value="kitty" />
</bean>
他有一个类phz.springframework.test.Cat
Java代码
publicclass Cat implements Animal {
private String name;
public void say() {
System.out.println("I am " + name +"!");
}
public void setName(String name) {
this.name = name;
}
}
实现了phz.springframework.test.Animal接口
Java代码
publicinterface Animal {
public void say();
}
很明显上面的代码输出I amkitty!
那么到底Spring是如何做到的呢?
接下来就让我们自己写个Spring来看看Spring到底是怎么运行的吧!
首先,我们定义一个Bean类,这个类用来存放一个Bean拥有的属性
Java代码
/*Bean Id */
private String id;
/* Bean Class */
private String type;
/* Bean Property */
private Map<String, Object> properties =new HashMap<String, Object>();
一个Bean包括id,type,和Properties。
接下来Spring就开始加载我们的配置文件了,将我们配置的信息保存在一个HashMap中,HashMap的key就是Bean的 Id ,HasMap的value是这个Bean,只有这样我们才能通过context.getBean("animal")这个方法获得Animal这个类。我们都知道Spirng可以注入基本类型,而且可以注入像List,Map这样的类型,接下来就让我们以Map为例看看Spring是怎么保存的吧
Map配置可以像下面的
Java代码
<beanid="test" class="Test">
<property name="testMap">
<map>
<entry key="a">
<value>1</value>
</entry>
<entry key="b">
<value>2</value>
</entry>
</map>
</property>
</bean>
Spring是怎样保存上面的配置呢?,代码如下:
Java代码
if(beanProperty.element("map") != null) {
Map<String, Object> propertiesMap =new HashMap<String, Object>();
Element propertiesListMap = (Element)beanProperty
.elements().get(0);
Iterator<?> propertiesIterator =propertiesListMap
.elements().iterator();
while (propertiesIterator.hasNext()) {
Element vet = (Element)propertiesIterator.next();
if (vet.getName().equals("entry")){
String key =vet.attributeValue("key");
Iterator<?> valuesIterator =vet.elements()
.iterator();
while (valuesIterator.hasNext()) {
Element value = (Element)valuesIterator.next();
if(value.getName().equals("value")) {
propertiesMap.put(key,value.getText());
}
if(value.getName().equals("ref")) {
propertiesMap.put(key, new String[] {value
.attributeValue("bean")});
}
}
}
}
bean.getProperties().put(name,propertiesMap);
}
接下来就进入最核心部分了,让我们看看Spring到底是怎么依赖注入的吧,其实依赖注入的思想也很简单,它是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。让我们看看具体它是怎么做的吧。
首先实例化一个类,像这样
Java代码
publicstatic Object newInstance(String className) {
Class<?> cls = null;
Object obj = null;
try {
cls = Class.forName(className);
obj = cls.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
return obj;
}
接着它将这个类的依赖注入进去,像这样
Java代码
publicstatic void setProperty(Object obj, String name, String value) {
Class<? extends Object> clazz =obj.getClass();
try {
String methodName =returnSetMthodName(name);
Method[] ms = clazz.getMethods();
for (Method m : ms) {
if (m.getName().equals(methodName)) {
if (m.getParameterTypes().length == 1) {
Class<?> clazzParameterType =m.getParameterTypes()[0];
setFieldValue(clazzParameterType.getName(), value, m,
obj);
break;
}
}
}
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
最后它将这个类的实例返回给我们,我们就可以用了。我们还是以Map为例看看它是怎么做的,我写的代码里面是创建一个HashMap并把该HashMap注入到需要注入的类中,像这样,
Java代码
if(value instanceof Map) {
Iterator<?> entryIterator =((Map<?, ?>) value).entrySet()
.iterator();
Map<String, Object> map = newHashMap<String, Object>();
while (entryIterator.hasNext()) {
Entry<?, ?> entryMap = (Entry<?,?>) entryIterator.next();
if (entryMap.getValue() instanceofString[]) {
map.put((String) entryMap.getKey(),
getBean(((String[])entryMap.getValue())[0]));
}
}
BeanProcesser.setProperty(obj, property,map);
}
好了,这样我们就可以用Spring 给我们创建的类了,是不是也不是很难啊?当然Spring能做到的远不止这些,这个示例程序仅仅提供了Spring最核心的依赖注入功能中的一部分。