Spring学习笔记3

第03章 AOP概述
 
AOP概述
●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
●AOP编程操作的主要对象是切面(aspect),而切面模块化横切关注点
●在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就被模块化到特殊的类里——这样的类我们通常称之为“切面”。
●AOP的好处:
○每个事物逻辑位于一个位置,代码不分散,便于维护和升级
○业务模块更简洁,只包含核心业务代码

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 接口进行描述,它使用类和方法作为连接点的查询条件。

AspectJ
3.1 简介
AspectJ:Java社区里最完整最流行的AOP框架。
在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
        AspectJ 支持 5 种类型的通知注解:
   @Before:前置通知, 在方法执行之前执行
   @After:后置通知, 在方法执行之后执行
   @AfterRunning:返回通知, 在方法返回结果之后执行
   @AfterThrowing:异常通知, 在方法抛出异常之后
  @Around: 环绕通知, 围绕着方法执行

使用 Spring 的 AOP 配置前置通知的基本步骤,如下:
4.1.导入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 
           spring-aspects-4.0.0.RELEASE.jar 
  4.2.引入aop名称空间

4.3. 基于注解的方式来使用 AOP 
(1)在配置文件中配置自动扫描的包:
<context:component-scan base-package="com.atguigu.spring.aop"></context:component-scan>

(2) 加入使 AspjectJ 注解起作用的配置:
配置自动为匹配aspectJ注解的Java类生成代理对象
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

配置切面 (将横切关注点代码抽象到切面类中)
(1)切面必须是 IOC 中的 bean,即添加 @Component 注解; 
(2)声明是一个切面:添加 @Aspect; 
通过添加 @Aspect 注解声明一个 bean 是一个切面。 
@Aspect
@Component
public class LoggingAspect {
(3)声明通知:即额外加入功能对应的方法。 
前置通知:
@Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int,int))")
public void beforeMethod(JoinPoint joinPoint){
      String methodName = joinPoint.getSignature().getName();
      Object[] args = joinPoint.getArgs();
      System.out.println("The method " + methodName + " begins with " + Arrays.asList(args));
}

其中:@Before 表示在目标方法执行之前执行 @Before 标记的方法的方法体。 
@Before 里面的是切入点表达式。 
切入点表达式是根据方法的签名来匹配各种方法:
execution * com.atguigu.spring.ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.

execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法.

execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法

execution public double ArithmeticCalculator.*(double, ..): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数

execution public double ArithmeticCalculator.*(double, double): 匹配参数类型为 double, double 类型的方法.

在通知中访问连接细节:可以在通知方法中添加 JoinPoint 类型的参数,从中可以访问到方法的签名和方法的参数。 
@After 表示后置通知:在方法执行之后(无论是否发生异常),执行的通知。
      在后置通知中不能访问目标方法执行的结果
/**
* 在方法执行之后执行的代码. 无论该方法是否出现异常
*/
@After("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int,int))")
public void afterMethod(JoinPoint joinPoint){
       String methodName = joinPoint.getSignature().getName();
       System.out.println("The method " + methodName + " ends");
}

返回通知
/**
* 在方法正常结束后执行的代码
* 因为紧挨着目标方法,所以返回通知可以访问到方法的返回值的!
*/
@AfterReturning(value="execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int,int))",
returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){
       String methodName = joinPoint.getSignature().getName();
       System.out.println("The method " + methodName + " ends with " + result);
}

异常通知
/**
* 在目标方法出现异常时会执行的代码.
* 可以访问到异常对象; 且可以指定在出现特定异常时在执行通知代码
*/
@AfterThrowing(value="execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int,int))",
throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
          String methodName = joinPoint.getSignature().getName();
          System.out.println("The method " + methodName + " occurs excetion:" + e);
}

环绕通知
/**
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数. 
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
*/
@Around("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
public Object aroundMethod(ProceedingJoinPoint pjd){
  Object result = null;
  String methodName = pjd.getSignature().getName();
  try {
    //前置通知
    System.out.println("The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs()));
    //执行目标方法
    result = pjd.proceed();
    //返回通知
    System.out.println("The method " + methodName + " ends with " + result);
   } catch (Throwable e) {
     //异常通知
     System.out.println("The method " + methodName + " occurs exception:" + e);
     throw new RuntimeException(e);
   }
   //后置通知
   System.out.println("The method " + methodName + " ends");
   return result;
}

测试类:
@Test
public void test01() {
      ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
      ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator)ctx.getBean("arithmeticCalculator");
      System.out.println(arithmeticCalculator.getClass().getName());

      int result = arithmeticCalculator.add(11, 12);
      System.out.println("result:" + result);

      result = arithmeticCalculator.div(10, 0);
      System.out.println("result:" + result);
}

测试结果:


重用切入点表达式
  • 在编写 AspectJ 切面时, 可以直接在通知注解中书写切入点表达式. 但同一个切点表达式可能会在多个通知中重复出现.
  • 在 AspectJ 切面中, 可以通过 @Pointcut 注解将一个切入点声明成简单的方法. 切入点的方法体通常是空的, 因为将切入点定义与应用程序逻辑混在一起是不合理的. 
  • 切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.
  • 其他通知可以通过方法名称引入该切入点.
根据重用切入点表达式将上面的方法全部修改为:
/**
* 定义一个方法, 用于声明切入点表达式. 一般该方法中不需要添入其他的代码.
* 使用 @Pointcut 来声明切入点表达式.
* 后面的其他通知直接使用方法名来引用当前的切入点表达式.
*/
@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int,int))")
public void declareJoint(){}

@Before("declareJoint()")
public void beforeMethod(JoinPoint joinPoint){

@After("declareJoint()")
public void afterMethod(JoinPoint joinPoint)

@AfterReturning(value="declareJoint()", returning="result")
public void afterReturning(JoinPoint joinPoint,Object result){

@AfterThrowing(value="declareJoint()",throwing="e")
public void afterThrowing(JoinPoint joinPoint, Exception e){
测试结果:

结果是一样的

切面的优先级

在同一个连接点上应用不止一个切面时, 除非明确指定, 否则它们的优先级是不确定的.
切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.

实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.

若使用 @Order 注解, 序号出现在注解中

@Order(1)
@Aspect
@Component
public class VlidationAspect {

    @Before("com.atguigu.spring.aop.LoggingAspect.declareJointPointExpression()")
    public void validateArgs(JoinPoint joinPoint){
	System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs()));
	}
}

@Order(2)
@Aspect
@Component
public class LoggingAspect {
	
	/**
	* 定义一个方法, 用于声明切入点表达式. 一般该方法中不需要添入其他的代码. 
	* 使用 @Pointcut 来声明切入点表达式. 
	* 后面的其他通知直接使用方法名来引用当前的切入点表达式. 
	*/
	@Pointcut("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(..))")
	public void declareJointPointExpression(){}
验证结果:



VlidationAspect的order值小 LoggingAspect的order值大 所以validate先打印。

 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值