Spring AOP

AOP简介

AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待。简单说就是,在不改变原有的逻辑的基础上,增加一些额外的功能。

AOP和OOP

AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。

OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性

AOP主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等。如图:
在这里插入图片描述

AOP相关概念

  • 切面(Aspect):共有功能的实现。如日志切面、权限切面、验签切面等。在实际开发中通常是一个存放共有功能实现的标准Java类。当Java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面
  • 通知(Advice):切面的具体实现。就是要给目标对象织入的事情。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分
  • 连接点(JoinPoint):程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出等。Spring只支持方法级的连接点。一个类的所有方法前、后、抛出异常时等都是连接点
  • 切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。比如,在上面所说的连接点的基础上,来定义切入点。我们有一个类,类里有10个方法,那就产生了几十个连接点。但是我们并不想在所有方法上都织入通知,我们只想让其中的几个方法,在调用之前检验下入参是否合法,那么就用切点来定义这几个方法,让切点来筛选连接点,选中我们想要的方法。切入点就是来定义哪些类里面的哪些方法会得到通知
  • 目标对象(Target)那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入。
  • 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象
  • 织入(Weaving)将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现

通过注解配置AOP

5种通知类型

  1. Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可

  2. After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式

  3. AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值

  4. AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象

  5. Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint

切点表达式

Pointcut格式为:

execution(modifier-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

修饰符匹配 modifier-pattern? 例:public private
返回值匹配 ret-type-pattern 可以用 * 表示任意返回值
类路径匹配 declaring-type-pattern? 全路径的类名
方法名匹配 name-pattern 可以指定方法名或者用 * 表示所有方法;set* 表示所有以set开头的方法
参数匹配 (param-pattern) 可以指定具体的参数类型,多个参数用“,”分隔;可以用 * 表示匹配任意类型的参数;可以用 (…) 表示零个或多个任意参数
异常类型匹配throws-pattern? 例:throws Exception
其中后面跟着 ? 表示可选项

例子:
限定该切点仅匹配的包是 com.sharpcj.aopdemo.test1,可以使用:

execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && within(com.sharpcj.aopdemo.test1.*)

在切点中选择 bean,可以使用:(切面只会对 Girl.java 这个类生效)

execution(* com.sharpcj.aopdemo.test1.IBuy.buy(..)) && bean(girl)

例子

切面类

package com.enjoy.cap10.aop;
 
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
 
//日志切面类
@Aspect
public class LogAspects {
	//声明切点表达,该方法的内容并不重要,方法名也不重要,实际上它只是作为一个标识,供通知使用
	@Pointcut("execution(public int com.enjoy.cap10.aop.Calculator.*(..))")
	public void pointCut(){};//这里可以不叫pointCut(),下面通知相应改变即可
	
	//@before代表在目标方法执行前切入, 并指定在哪个方法前切入
	@Before("pointCut()")
	public void logStart(){
		System.out.println("@Before:除法运行....参数列表是:{}");
	}
	@After("pointCut()")
	public void logEnd(){
		System.out.println("@After:除法结束......");
	}
	@AfterReturning("pointCut()")
	public void logReturn(){
		System.out.println("@AfterReturning:除法正常返回......运行结果是:{}");
	}
	@AfterThrowing("pointCut()")
	public void logException(){
		System.out.println("@AfterThrowing:运行异常......异常信息是:{}");
	}
	@Around("pointCut()")
	public Object Around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
		System.out.println("@Arount:执行目标方法之前...");
		Object obj = proceedingJoinPoint.proceed();//相当于开始调div地
		System.out.println("@Arount:执行目标方法之后...");
		return obj;
	}
}

目标方法

package com.enjoy.cap10.aop;
 
public class Calculator {
	//业务逻辑方法
	public int div(int i, int j){
		System.out.println("--------");
		return i/j;
	}
}

配置类

package com.enjoy.cap10.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
 
import com.enjoy.cap10.aop.Calculator;
import com.enjoy.cap10.aop.LogAspects;
 
@Configuration
@EnableAspectJAutoProxy
public class Cap10MainConfig {
	@Bean
	public Calculator calculator(){
		return new Calculator();
	}
 
	@Bean
	public LogAspects logAspects(){
		return new LogAspects();
	}
}

测试类

public class Cap10Test {
	@Test
	public void test01(){
		AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(Cap10MainConfig.class);	
		Calculator c = app.getBean(Calculator.class);
		int result = c.div(4, 3);
		System.out.println(result);
		app.close();
 
	}
}

执行结果

@Arount:执行目标方法之前...
@Before:除法运行....参数列表是:{}
@Arount:执行目标方法之后...
@After除法结束......
@AfterReturning除法正常返回......运行结果是:{}
1

切面类中值得注意的是 ,@Around 修饰的环绕通知类型,是将整个目标方法封装起来了,在使用时,我们传入了 ProceedingJoinPoint 类型的参数,这个对象是必须要有的,并且需要调用 ProceedingJoinPoint 的 proceed() 方法,如果不调用该对象的 proceed() 方法,表示原目标方法被阻塞调用

配置类中值得注意的是,我们使用注解@EnableAspectJAutoProxy来启用Spring AOP ,它其实有个参数proxyTargetClass,默认为false,可以这样设为true:
@EnableAspectJAutoProxy(proxyTargetClass = true)

  • 这跟Spring AOP 动态代理的机制有关,这个 proxyTargetClass 参数决定了代理的机制。如果proxyTargetClass 为 true,则会使用 cglib 的动态代理方式。这种方式的缺点是拓展类的方法被final修饰时,无法进行织入。当这个参数为 false 时,通过jdk的基于接口的方式进行织入,这时候代理生成的是一个接口对象。例子中我们获取对象的方法如下:
    Calculator c = app.getBean(Calculator.class);
    如果我们用下面的方法获取会报错,将这个接口对象强制转换为实现该接口的一个类,就抛出了类型转换异常。:
    Calculator c= (Calculator) context.getBean("calculator");

通过XML配置AOP

参考这个XML配置例子

通过动态代理配置AOP

参考这个动态代理配置例子


参考链接:Spring AOP及实现方式
参考链接:深入理解Spring两大特性:IoC和AOP
参考链接:Spring AOP详细介绍
参考链接:Spring AOP

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值