Spring AOP

为什么使用AOP?原始做法存在以下问题
代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀,每个方法在处理核心逻辑的同事还必须兼顾其他多个关注点。
代码分散:以日志需求为例,只是为了满足这个单一的需求,就不得不在多个模块(方法)里多次重复相同的日志代码,如果日志需求发生变化,必须修改所有模块。

使用动态代理可以实现上述需求,在代理类执行方法前后添加日志操作。

AOP(面向切面编程):是一种方法论,对传统的OOP(面向对象编程)的补充,AOP的主要编程对象是切面(aspect)
在这里插入图片描述

切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象(类比上图把验证、日志单独抽取出来成为一个类)
通知(Advice):切面必须要完成的工作(上图验证参数,前置日志,后置日志,是需要做的工作)
目标(Target):被通知的对象(业务逻辑)
代理(Proxy):向目标对象应用通知之后创建的对象(将验证参数,前置日志,后置日志和业务逻辑相结合之后创建的对象,和动态代理类似)
连接点(Joinpoint):程序执行的某个特定位置,如类某个方法调用前、调用后、方法抛出异常后等。
连接点由两个信息确定:方法表示的程序执行点,相对点表示的方位,例如ArithAop.add()方法执行前的连接点,执行点为ArithAop.add(),方位为该方法执行前的位置。
切点(Pointcut):每个类都有多个连接点,例如ArithAop中所有的方法实际上都是连接点,即连接点是程序类中客观存在的。AOP通过切点定位到特定的连接点。就好比连接点是是数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。

在Spring中启用AspectJ注解支持
要在Spring应用中使用AspectJ注解,必须在classpath添加AspectJ 类库,主要依赖于aopalliance.jar,aspectj.weaver.jar和spring-aspects.jar
将aop 命名空间添加到XML文件中,要在SpringIOC容器中启用AspectJ注解支持,只要在Bean配置文件中定义一个空的XML元素aop:aspectj-autoproxy
将SpringIOC容器侦测到Bean配置文件中的aop:aspectj-autoproxy元素时,会自动为与AspectJ切面匹配的Bean创建代理。

将横切关注点的代码抽象AOP切面类中,定义切面,同时也是SpringIOC容器的Bean

@Component
@Aspect
public class LogAspect {

	@Before("execution(public int com.cmzy.spring.aop.ArithAopImpl.add(int,int))")
	public void beforeMethod() {
		System.out.println("在方法执行之前执行。。。。。");
	}
}

在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,通知是标注有某种注解的简单的Java方法。
AspectJ支持5种类型的通知注解:
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行之后执行(后置通知是在连接完成之后执行的,即连接点返回结果或者抛出异常的时候,后置通知都会执行,相当于在finally中执行,并且在后置通知中不能访问目标方法的执行结果)
@AfterRunning:返回通知,在方法返回结果之后执行(在方法正常结束后执行的,返回通知是可以访问到方法的返回值)
@AfterThrowing:异常通知,在方法抛出异常之后
@Around:环绕通知,围绕着方法执行

通知注解中的表示语法(execution)
execution(* com.cmzy.spring.aop.ArithAopImpl.(…)):表示匹配ArithAopImpl中声明的所有方法,第一个参数代表任意修饰符及任意返回值。第二个代表任意方法,…表示匹配任意数量的参数。若目标类和接口与该切面在同一个包中,则可以省略包名。
execution(public * com.cmzy.spring.aop.ArithAopImpl.(…)):表示匹配ArithAopImpl的所有public 方法。
execution(public double com.cmzy.spring.aop.ArithAopImpl.
(…)):表示匹配ArithAopImpl中所有public的,返回double的方法
execution(public double com.cmzy.spring.aop.ArithAopImpl.*(double,…)):表示匹配ArithAopImpl中所有public的,返回double的,并且第一个参数为double类型的,后面匹配任意类型的参数的方法。

如果需要在通知方法中访问连接点的细节,如访问方法的签名,方法的参数等,可以在通知方法中声明一个类型为JoinPoint的参数。

@Component
@Aspect
public class LogAspect {
	
	@Pointcut("execution(public int com.cmzy.spring.aop.ArithAop.add(int,int))")
	public void pointCutTest() {
		
	}
	@Before("pointCutTest()")
	public void beforeMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		System.out.println("在方法执行之前执行。。。。。");
	}
	
	@After("execution(* ArithAopImpl.*(..))")
	public void after() {
		System.out.println("在方法执行后执行。。。。。");
	}
}

返回通知的实现,可以获取返回值

	@AfterReturning(value = "execution(* ArithAopImpl.*(..))",returning = "result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		System.out.println("返回结果是"+ result);
	}

对比动态代理,可以看到相应的通知处在什么位置,其作用等

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		Object obj  = null;
		try {
			//前置通知
			obj = method.invoke(target, args);
			//返回通知,可以访问到方法的返回值
		} catch (Exception e) {
			//异常通知
			
		}
		
		//后置通知
		
		return obj;
	}

异常通知:在目标方法出现异常时会执行的代码,可以访问到异常对象,且可以指定在出现特定异常时执行相应代码,比如下面异常通知定义时是使用的Exception拦截所有异常,如果在此处定义NullPointException,而实际抛出的是其他的异常,那么该通知就不会执行。

@Component
@Aspect
public class LogAspect {
	
	@Pointcut("execution(* ArithAopImpl.*(..))")
	public void pointCutTest() {
		
	}
	
	@Before("pointCutTest()")
	public void beforeMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		System.out.println("在方法执行之前执行。。。。。");
	}
	
	@After("pointCutTest()")
	public void after() {
		System.out.println("在方法执行后执行。。。。。");
	}
	
	@AfterReturning(value = "pointCutTest()",returning = "result")
	public void afterReturning(JoinPoint joinPoint,Object result) {
		String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		System.out.println("返回结果是"+ result);
	}
	
	
	@AfterThrowing(pointcut = "pointCutTest()",throwing = "ex")
	public void afterThrow(Exception ex) {
		ex.printStackTrace();
	}
}

环绕通知:环绕通知需要携带ProceedingJoinPoint类型的参数,环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法,且环绕通知必须有返回值,返回值即为目标方法的返回值。

@Component
@Aspect
public class LogAspect {
	@Pointcut("execution(* ArithAopImpl.*(..))")
	public void pointCutTest() {
		
	}
	
	@Around(value = "pointCutTest()")
	public Object around(ProceedingJoinPoint pjp) {
		Object result = null;
		try {
			System.out.println("类似前置通知。。。。。");
			result = pjp.proceed();
			System.out.println("类似返回通知。。。。。");
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			System.out.println("类似异常通知。。。。。");
			e.printStackTrace();
		}
		System.out.println("类似后置通知。。。。");
		return result;
		
	}
}

切面优先级:当多个切面都用于同一个方法,那么可以设置切面的执行的先后顺序,在切面定义的时候使用@Order(number)注解,number值越小,优先级越高。
在这里插入图片描述

切面里面定义@PointCut注解可以用于被引用,如果不是在同一个切面中,其他切面需要引用,可以使用:切面的全类名.切点方法(),这种形式引用

常见的切面表达式

1 所有公有方法的执行
execution(public * *(..))

2 所有以set开头的公有方法的执行
execution(* set*(..))

3 AccountService接口下的所有方法的执行
execution(* com.xyz.service.AccountService.*(..))

4 com.xyz.service包下的所有方法的执行
execution(* com.xyz.service.*.*(..))

5 com.xyz.service包及其子包下的所有方法的执行
execution(* com.xyz.service..*.*(..))

6 匹配com.xyz.service包下的所有类的所有方法(不含子包)
within(com.xyz.service.*)

7 com.xyz.service包和子包的所有方法
within(com.xyz.service..*)

8 匹配AccountService的代理类(不支持通配符)
this(com.xyz.service.AccountService)

基于XML配置形式:

	<!-- 目标对象 -->
	<bean id="arithXMLAop" class="com.cmzy.spring.aop.ArithXMLAopImpl"></bean>
	<!-- 切面 -->
	<bean id="logXmlAspect" class="com.cmzy.spring.aop.LogXmlAspect"></bean>
	<!-- 配置AOP -->
	<aop:config>
		<!-- 配置切点 -->
		<aop:pointcut expression="execution(public * *(..))" id="xmlAspect"/>
		<!-- 配置增强,即切点和 通知的组成-->
		<aop:aspect ref="logXmlAspect">
			<aop:before method="beforeMethod" pointcut-ref="xmlAspect"/>
			<aop:after method="afterMethod" pointcut-ref="xmlAspect"/>
			<aop:after-returning method="" pointcut-ref="" returning=""/>
			<aop:after-throwing method="" pointcut-ref="" throwing=""/>
			<aop:around method=" " pointcut-ref=""/>
		</aop:aspect>
	</aop:config>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值