Spring之AOP篇

Spring之AOP篇:

AOP框架是Spring的一个重要组成部分.但是Spring IOC 并不依赖于AOP,这就意味着你有权力选择是否使用AOP,AOP作为Spring IOC容器的一个补充,使它成为一个强大的中间件解决方案。

一、AOP(Aspect-Oriented Programming)

    AOP的目标:横切关注点  这种问题用面向对象很难解决

  AOP是用来解决什么问题的呢?

横越多个模块的业务关注点,比如记录日志,事务管理

1、AOP的解决方案——拦截

1)由谁来拦截?--代理类Proxy和本类实现同一个接口

就像是这样:action发出命令--代理类(处理一些操作)--本类

2)拦截的过程:

I.将Proxy(如UserServiceProxy)交给调用者(如UserAction),调用者调用需要的方法

II.Proxy先拦截该方法,执行拦截逻辑

III.然后执行调用者真正的实现类(如UserServiceImp)

3)核心概念:

a、代理对象Proxy:是核心,负责拦截,由工具自动创建

b、拦截器(Interceptor):实现拦截逻辑的对象

c、目标对象(Target):是Proxy所代理的对象,是已有的类

以上三个类实现了一个“金三角”

Proxy--拦截-->Interceptor--实现拦截逻辑--处理目标对象-->Target

2、代理如何实现?

有两种方式:

1)JDK动态代理(使用这个比较多)

由JDK自带的动态代码生成技术,可以对实现了接口的类进行处理

2)CGLib

对没有实现任何接口的类进行处理

这两种方式的共同点:(都是在后台自动生成的)

  在程序运行期间,动态的生成代码并进行动态编译和加载

     

问题:Spring的AOP是如何实现代理的?是自己实现的吗?

注:Spring借用了JDK动态代理和CGLib来实现代理对象。

Spring进行如下判断来实现代理:

i、如果目标类实现了接口,Spring则调用JDK动态代理;

ii、反之,调用CGLib

Proxy是核心:

创建Proxy的方法:ProxyFactoryBean

<beanid="arithProxy"

class="org.springframework.aop.framework.ProxyFactoryBean">

<propertyname="target"ref="arith"/>

<propertyname="interceptorNames">

<list>

<!--<value>logBefore</value>

<value>logAfter</value>

<value>logThrows</value>-->

<value>logAround</value>

</list>

     </property>

      JDK动态代理的实现方式:(工厂模式)

UserDaouserDao=newUserDaoImp();

UserDaoproxy=(UserDao)

Proxy.newProxyInstance(userDao.getClass().getClassLoader(),

userDao.getClass().getInterfaces(),

    new LogHandler(userDao));

二、Spring中的AOP方式(在Spring中将拦截称为通知)

拦截逻辑放在Advice之中,Advice按时间将拦截器分为四类:

1)BeforeAdvice:方法执行之前进行拦截

    public class LogBeforeAdvice implements MethodBeforeAdvice{

 //Method method拦截的方法

//Object[]args方法中的参数

//target目标对象

@Override

publicvoidbefore

(Methodmethod,Object[]args,Objecttarget)

throwsThrowable{

MyLoggerlogger=newMyLogger();

System.out.println("Methods:"+method.getName()+

"begins,args:"+Arrays.toString(args));

}

}

2)AfterAdvice:方法执行之后进行拦截

publicclassLogAfterAdviceimplementsAfterReturningAdvice{

@Override

publicvoidafterReturning(ObjectresultValue,Methodmethod,Object[]args,

Objecttarget)throwsThrowable{

MyLoggerlogger=newMyLogger();

System.out.println("Methods:"+method.getName()+

"ends,result:"+resultValue);

}

}

3)AfterThrowingAdvice:方法异常结束之后进行拦截

publicclassLogThrowingAdviceimplementsThrowsAdvice{

//ThrowsAdvice没有方法,但方法名必须是afterThrowing

publicvoidafterThrowing(IllegalArgumentExceptione)

throwsThrowable{

MyLoggerlogger=newMyLogger();

logger.log("IllegalArgumentException!");

}

}

4)AroundAdvice:环绕通知

  public class LogAroundAdvice implements MethodInterceptor {

 //MethodInvocation相当于Method的包装

@Override

publicObjectinvoke(MethodInvocationmethodInvocation)

throwsThrowable{

MyLoggerlogger=newMyLogger();

try{

//methodInvocation.getMethod()获得方法

//methodInvocation.getArguments()获得方法的参数

logger.log("before:"+

methodInvocation.getMethod().getName()+

",args:"+methodInvocation.getArguments());

//在环绕通知里面,必须执行目标方法!!!!!!!!!!!!!!

Objectresult=methodInvocation.proceed();

logger.log("ends:"+

methodInvocation.getMethod().getName());

returnresult;

}catch(Exceptione){

logger.log("Exception:"+e.getMessage());

throwe;

}

 }

  注意:虽然环绕通知包含了另外三种,但还是要依据业务逻辑来选择,这样有利于代码的编程量       并且环绕通知最好不要和另外三种混用,并且并许执行目标方法proceed().

 也可以大致分成两组——

i、BeforeAdvice,AfterReturning,Throwing

ii、AroundAdvice:需要在内部负责调用目标类的方法。

      注意:AroundAdvice应单独使用

<!--TargetObject-->

<bean id="arith" class="myspring.calculator.ArithmeticCalculatorImp"></bean>

<!-- Advice -->

<beanid="logBefore"class="myspring.aop.LogBeforeAdvice"/>

<beanid="logAfter"class="myspring.aop.LogAfterAdvice"/>

<beanid="logThrows"class="myspring.aop.LogThrowingAdvice"/>

<bean id="logAround" class="myspring.aop.LogAroundAdvice"/>

<!-- Proxy -->

<beanid="arithProxy"

class="org.springframework.aop.framework.ProxyFactoryBean">

<propertyname="target"ref="arith"/>

<propertyname="interceptorNames">

<list>

<!--<value>logBefore</value>

<value>logAfter</value>

<value>logThrows</value>-->

<value>logAround</value>

</list>

</property>

</bean>

在test类中这样用:ApplicationContext ac =

newClassPathXmlApplicationContext("aop-base.xml");

IArithmeticCalculatorproxy=

   (IArithmeticCalculator)ac.getBean("arithProxy");

 面试中可能会问到:

proxyTargetClass属性

将其加入到代理bean中去,则Spring将调用CGLib来实现创建Proxy对象。

(无论Target有无实现接口)

形如

<beanid=""class="....ProxyFactoryBean">

.....

<propertyname="proxyTargetClass"value="true"/>

</bean>

 三、Spring AOP基本方式  AspectJ Spring2.5之后用的

Spring对AspectJ的支持

应用于有多个target,不想一个一个的配置时,用AspectJ

1)POJO类(Aspect类),使用@Aspect

基本信息:Advice和Pointcut

2)定义Bean:在Bean定义文件中定义这个Aspect类

3)启用Spring的自动代理

  <aop:aspectj-autoproxy />  

   1、JoinPoint(连接点):被拦截的方法

程序执行过程中的点,就好像方法中的一个调用,

或者一个特别的被抛出的异常

在SpringAOP中,一个连接点通常是方法调用.

Spring并不明显地使用"连接点"作为术语,

连接点信息可以通过MathodInvocation的参数穿过拦截器访问,

相当于实现org.springframework.aop.Pointcut接口.

2、JoinPoint对象

获取连接点信息(获取目标方法以及目标对象的信息)

1)目标方法:

i、getSignature():方法签名

joinPoint.getSignature().getName()

ii、getArgs():方法的实参(方法的实际参数)

joinPoint.getArgs()

打印输出时:Arrays.toString(joinPoint.getArgs())

2)目标对象:getTarget()

    目标对象实际类型:getTarget().getClass()

 3、PointCut(切入点):一组连接点。

作用:定义了一组被拦截的方法

PointCut(切入点):当一个通知被激活的时候,

会指定一些连接点.一个AOP框架必须允许开发者指定切入点。

  一个AOP框架必须允许开发者指定切入点:例如使用正则表达式.

  只有引入了切入点,才能很好的进行拦截

  PointCut的意义:可以一次性定义多个类的

多个方法作为拦截目标(即JoinPoint)

PointCut代表了Target一组Target组成了PointCut

一组Target可以理解为一个类的多个方法,或者多个类的多个方法

  代理、拦截器、目标

  PointCut

在AspectJ中,切入点是用PointCutExpression来

定义的。(切入点表达式可以理解为过滤条件)

PointCut表达式

格式execution(表达式)

表达式一个表达式就是一个匹配规则

*myspring.calculator.IArithmeticCalculator.add(..)

*理解为publicvoidmyspring.calculator.IArithmeticCalculator.add(inta,intb)

*取代publicvoid通配一切字符

Spring自动代理(即Spring自动生成Proxy对象)

    用<aop:aspectj-autoproxy />来启用

 Pointcut表达式语法

1)方法级别——execution

execution(**.*(..)):所有类的方法

execution(public*somepackage.SomeClass.someMethod(..))

总结:execution是对方法签名进行匹配

2)对象级别(类级别)——within

within(somepackage.*):somepackage包下所有的类

within(com.dang.service.*Service)

即com.dang.service包下所有以Service结尾的类

within(somepackage..*):somepackage以及所有子包中的类

within(somepackage.SomeInterface+):SomeInterface接口的所有实现类

 总结:within是对类名(带包名)进行匹配。 拦截这个类的所有方法 

4、Aspect类的组成一般都用注解重点

1)Advice(AspectJ中的通知类型)

i、Before

//@Before("LogPointcuts.logAdd()")方法1

@Before("execution(**.*(..))")方法2

publicvoidlogBefore(JoinPointjoinPoint){

logger.log("{before}themethod:"

+joinPoint.getSignature().getName()+

"args:"+Arrays.toString(joinPoint.getArgs()));

}

注:方法1和方法2都是可以的,方法1的话需要在定义一个方法.

在同一个类或不同的类都是可以的直接调用这个方法

@Pointcut("execution(**.*(..))")

publicvoidlogOperation(){}

ii、AfterReturning

@AfterReturning(pointcut="execution(**.*(..))",

returning="result")

publicvoidafterReturning(JoinPointjoinPoint,Objectresult){

logger.log("{AfterReturning}themethod:"

+joinPoint.getSignature().getName()

+"result:"+result);

}

注意:方法有两个参数,在注解中的returning的值必须和Object的值完全一样

iii、AfterThrowing

@AfterThrowing(pointcut="execution(**.*(..))",

throwing="e")

publicvoidafterThrowing(JoinPointjoinPoint,IllegalArgumentExceptione){

logger.log("{AfterThrowing}themethod:"

+joinPoint.getSignature().getName()

+"e:"+e.getMessage());

}

iv、After:相当于finally,它在AfterReturning

和AfterThrowing之后执行。

它的作用——一般为释放资源(如关闭Connection)

@After("execution(**.*(..))")

publicvoidafter(JoinPointjoinPoint){

logger.log("{After}method:"+joinPoint.getSignature().getName());

 }

  注意:After是肯定是要在最后执行的,

v、Around

@Around("execution(**.*(..))")

publicObjectlogAround(ProceedingJoinPointjoinPoint)throwsThrowable{

logger.log("{Arount}Beforemethod:"

+joinPoint.getSignature().getName());

try{

//一定不要忘记调用目标方法

Objectresult=joinPoint.proceed();

logger.log("{Arount}Aftermethod:"

+joinPoint.getSignature().getName()

+"result:"+result);

//返回目标方法的返回值

returnresult;

}

catch(IllegalArgumentExceptione){

logger.log("{Around}Exception:IllegalArgument"

+Arrays.toString(joinPoint.getArgs()));

throwe;

}

  注意:Around最好不要和其他四种同时使用

  把处理日志的代码放到一起就是切面,模块化(Aspect)

  5.Introduction(引入)

1)Introduction的定义

给正在运行的程序中的对象添加方法。

其实是一种动态技术

2)问题:如何给ArithmeticCalculatorImp类加入

求最大值和最小值的方法

假设:最大值和最小值的方法已经存在,且分别属于不同的类。

extendsMaxCalculatorImp,MinCalculatorImp不行,java不允许多继承

3)要点:

a、实现一个POJO类

定义@DeclareParents来"继承"的父类

@DeclareParents(

value="myspring.calculator.ArithmeticCalculatorImp",

defaultImpl=MaxCalculatorImp.class

)

privateMaxCalculatormaxCalculator;

@DeclareParents(

value="myspring.calculator.ArithmeticCalculatorImp",

defaultImpl=MinCalculatorImp.class

)

privateMinCalculatorminCalculator;

b、定义这个Bean:在Bean定义文件中声明一下。

<beanclass="myspring.calculator.CalculatorIntroduction"></bean>

在test类中测试时:ApplicationContextac=

newClassPathXmlApplicationContext("aop.xml");

IArithmeticCalculatorcal=(IArithmeticCalculator)

ac.getBean("arith");

cal.add(2,3);

cal.div(4,3);

MaxCalculatormax=(MaxCalculator)cal;

max.max(3,6);

MinCalculatormin=(MinCalculator)cal;

min.min(3,6);

相关推荐