JAVA互联网架构学习之Spring之AOP

来源:互联网 发布:淘宝企业店铺公司更改 编辑:程序博客网 时间:2024/06/08 09:05
1.AOP概念:Aspect Oriented Programming 面向切面编程
2.作用:本质上来说是一种简化代码的方式
继承机制封装方法动态代理……
3.情景举例
1.数学计算器接口[MathCalculator]int add(int i,int j);int sub(int i,int j);int mul(int i, int j);int div(int i,int j);

2.提供简单实现[EasyImpl]

3.在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]

4.缺陷

[1]手动添加日志繁琐,重复[2]统一修改不便[3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护

5.使用动态代理实现

[1]创建一个类,让这个类能够提供一个目标对象的代理对象[2]在代理对象中打印日志
4.AOP术语
1.AOP概述

●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充。

●AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点。

●在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”

●AOP的好处:

○每个事物逻辑位于一个位置,代码不分散,便于维护和升级

○业务模块更简洁,只包含核心业务代码

2.AOP术语

2.1         横切关注点

从每个方法中抽取出来的同一类非核心业务。(抽离到方法中处理非核心业务)

2.2         切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

2.3         通知(Advice)

切面必须要完成的各个具体工作

2.4         目标(Target)

被通知的对象

2.5         代理(Proxy)

向目标对象应用通知之后创建的代理对象

2.6         连接点(Joinpoint)

横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方法调用前、调用后、方法捕获到异常后等。

在应用程序中可以使用横纵两个坐标来定位一个具体的连接点:

 

 

2.7         切入点(pointcut):

定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。

切点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

3          AspectJ

3.1         简介

AspectJ:Java社区里最完整最流行的AOP框架。

在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。

3.2         在Spring中启用AspectJ注解支持

①导入JAR包

         ●aopalliance.jar

          ●aspectj.weaver.jar

          ●spring-aspects.jar

②引入aop名称空间

③配置

<aop:aspectj-autoproxy>

Spring IOC容器侦测到bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的bean创建代理

 

3.3         用AspectJ注解声明切面

①要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例。

②当在SpringIOC容器中初始化AspectJ切面之后,SpringIOC容器就会为那些与AspectJ切面相匹配的bean创建代理。

③在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要包含很多通知。

④通知是标注有某种注解的简单的Java方法。

⑤AspectJ支持5种类型的通知注解:

[1]@Before:前置通知,在方法执行之前执行

[2]@After:后置通知,在方法执行之后执行

[3]@AfterRunning:返回通知,在方法返回结果之后执行

[4]@AfterThrowing:异常通知,在方法抛出异常之后执行

[5]@Around:环绕通知,围绕着方法执行

5.在Spring中使用AOP实现日志功能

1.Spring中可以使用注解或XML文件配置的方式实现AOP。

2.导入jar包  com.springsource.net.sf.cglib -2.2.0.jar  com.springsource.org.aopalliance-1.0.0 .jar  com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar  commons-logging-1.1.3. jar  spring-aop-4.0.0.RELEASE.jar  spring-aspects-4.0.0.RELEASE.jar  spring-beans-4.0.0.RELEASE.jar  spring-context-4.0.0.RELEASE.jar  spring-core-4.0.0.RELEASE.jar  spring-expression-4.0.0.RELEASE. jar  3.开启基于注解的AOP功能

< aop:aspectj-autoproxy />

4.声明一个切面类,并把这个切面类加入到IOC容器中

@Aspect//表示这是一个切面类@Component//加入IOC容器public class LogAspect {}


5.在切面类中声明通知方法
[1]前置通知:@Before
[2]返回通知:@AfterReturning
[3]异常通知:@AfterThrowing
[4]后置通知:@After
[5]环绕通知:@Around :环绕通知是前面四个通知的集合体!

@Aspect//表示这是一个切面类@Component//将本类对象加入到IOC容器中!public class LogAspect {@Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")public void showBeginLog(){System.out.println("AOP日志开始");}@After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")public void showReturnLog(){System.out.println("AOP方法返回");}@AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")public void showExceptionLog(){System.out.println("AOP方法异常");}@AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")public void showAfterLog(){System.out.println("AOP方法结束");}}



6.被代理的对象也需要加入IOC容器
@Component//加入IOC容器public class MathCalculatorImpl {public int add(int i,int j){int result = i+j;return result;}public int sub(int i,int j){int result = i-j;return result;}public int multi(int i,int j){int result = i*j;return result;}public int divide(int i,int j){int result = i/j;return result;}}



6.切入点表达式:
  1.上述案例通过junit测试,会发现,我们调用目标类的四个方法只有add方法被加入了4个通知,如果想所有的方法都加上这些通知,可以
在切入点表达式处,将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))这样只要是有两个参数,且
参数类型为int的方法在执行的时候都会执行其相应的通知方法!
 

2.

①切入点表达式的语法格式[参见第5章AOP细节]

execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

参见第5章AOP细节:演示验证
1.任意参数,任意类型
2.任意返回值
3.用@PointCut注解统一声明,然后在其它通知中引用该统一声明即可!
需要注意的是:权限是不支持写通配符的,当然你可以写一个*表示所有权限所有返回值!



最详细的切入点表达式:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入点表达式:
execution (* *.*(..))

7.统一声明切入点表达式
@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")public void myPointCut(){}


8.通知方法的细节
①在通知中获取目标方法的方法名和参数列表
[1]在通知方法中声明一个JoinPoint类型的形参
[2]调用JoinPoint对象的getSignature()方法获取目标方法的签名
[3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表

②在返回通知中获取方法的返回值
[1]在@AfterReturning注解中添加returning属性
@AfterReturning (value="myPointCut()", returning= "result")
[2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致
showReturnLog(JoinPoint joinPoint, Object result)
③在异常通知中获取异常对象
[1]在@ AfterThrowing注解中添加throwing属性
@AfterThrowing (value="myPointCut()",throwing= "throwable" )
[2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致
showExceptinLog(JoinPoint joinPoint, Throwable throwable)


9.根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并不是目标对象本身!



10.环绕通知:@Around
    1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数
@Around(value="pointCut()")
public void around(ProceedingJoinPoint joinPoint){
}
2.环绕通知会将其他4个通知能干的,自己都给干了!
注意:@Around修饰的方法一定要将方法的返回值返回!本身相当于代理!

@Around(value="pointCut()")public Object showLog(ProceedingJoinPoint point){Object[] args = point.getArgs();List<Object> argList = Arrays.asList(args);Object rs=null;try {try {System.out.println(point.getSignature().getName()+"方法执行了【,】参数列表:"+argList);rs = point.proceed(args);} finally {System.out.println("After【】");}System.out.println("AfterReturning【,】result:"+rs);} catch (Throwable e) {System.out.println(point.getSignature().getName()+"方法抛出了异常【】:"+e.getMessage());}return rs;}

注意:AfterReturning为函数返回一个返回值后的通知,即返回通知

    After为函数的后置通知,可以理解为函数所占用的资源释放后发送通知

故,后置通知在返回通知之前执行,也在异常通知之前!

在环绕通知中,我们使用try-finally包围方法的前置通知和方法的执行,不论方法是否成功执行,这层都不捕获异常,都将释放资源执行后置通知

在外层用try-catch包裹,捕获其中的异常,此时要用异常的最大父类Throwable

即:方法若不出错:try中的前置通知→方法执行→finally中的后置通知→try-finally外的返回通知

方法若抛出异常:try中的前置通知→方法执行→抛出异常→finally中的后置通知→外层try-catch捕获异常→catch中的异常通知

11.切面的优先级
对于同一个代理对象,可以同时有多个切面共同对它进行代理。
可以在切面类上通过@Order (value=50)注解来进行设置,值越小优先级越高!
@Aspect
@Component
@Order(value=40)
public class TxAspect {
@Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
List<Object> list = Arrays.asList(args);
Object result = null;
try {
//目标方法之前要执行的操作
System.out.println("[事务日志]"+methodName+"开始了,参数为:"+list);
//调用目标方法
result = joinPoint.proceed(args);

//目标方法正常执行之后的操作
System.out.println("[事务日志]"+methodName+"返回了,返回值为:"+result);
} catch (Throwable e) {
//目标方法抛出异常信息之后的操作
System.out.println("[事务日志]"+methodName+"出异常了,异常对象为:"+e);
throw new RuntimeException(e.getMessage());
}finally{
//方法最终结束时执行的操作!
System.out.println("[事务日志]"+methodName+"结束了!");
}
return result;
}

}



12.注意:上面的AOP都是通过注解实现的,AOP实际上也可以通过xml配置的方式实现!
<!-- 1.将需要加载到IOC容器中的bean配置好 --><bean id="calLog" class="com.neuedu.aspect.CalLog"></bean><bean id="orderAspect" class="com.neuedu.aspect.OrderAspect"></bean><bean id="realCalImpl" class="com.neuedu.entity.RealCalImpl"></bean><!-- 2.配置AOP,需要导入AOP名称空间 --><aop:config><!-- 声明切入点表达式 --><aop:pointcut expression="execution(* com.neuedu.entity.RealCalImpl.*(..))" id="MyPointCut"/><!-- 配置日志切面类,引用前面的类,荣国order属性控制优先级 --><aop:aspect ref="calLog"><!-- 配置日志切面类,引用前面的类,通过order属性优先级 --><aop:before method="showBeginLog" pointcut-ref="MyPointCut"/><aop:after method="showAfterLog" pointcut-ref="MyPointCut"/><aop:after-returning method="showReturnLog" pointcut-ref="MyPointCut" returning="result"/><aop:after-throwing method="showExecptionLog" pointcut-ref="MyPointCut" throwing="e"/></aop:aspect><!-- 配置事务切面类,引用前面的类 --><aop:aspect ref="orderAspect"><aop:around method="showLog" pointcut-ref="MyPointCut"/></aop:aspect></aop:config>



需要知道的是:事务的管理是和AOP是有很大关系的,即声明式事务的底层是用事务实现的!