跟开振学习Spring AOP第二篇:AOP的概念和流程

Posted ykzhen2015

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟开振学习Spring AOP第二篇:AOP的概念和流程相关的知识,希望对你有一定的参考价值。

有了第一篇,谈谈Spring AOP第一篇,谈谈约定编程

大家可以看到博主使用动态代理技术将你的代码织入到对应的流程中,并且给出约定。好吧,我想说Spring也可以,并且给出它和我们的约定这便叫做AOP,好轻松的一句话。

好吧用AOP的概念来代替掉上一篇的约定内容的概念。

AOP概念对应第一篇约定的内容备注
连接点HelloServiceImpl的sayHello方法就是具体是那个方法被切面拦截
切面拦截器MyInterceptor 拦截器,被称为一个切面
前置通知拦截器MyInterceptor的before方法Spring依据流程约定织入流程
环绕通知拦截器MyInterceptor的around方法Spring依据流程约定织入流程
事后通知拦截器MyInterceptor的after方法Spring依据流程约定织入流程
异常返回通知拦截器MyInterceptor的afterThrowing方法Spring依据流程约定织入流程
正常返回通知拦截器MyInterceptor的afterReturning方法Spring依据流程约定织入流程
织入ProxyBean 采用的动态代理生成代理对象的方法在Spring中默认,对象有接口,采用JDK动态代理,否则采用CGLIB
瞧,解决了那些生涩的概念了吧,嘿嘿,AOP也就是那么回事,它搞了一些,你看得不明不白的概念,忽悠你一下而已。

为了更加明确,我们介绍一下AOP的术语:

连接点:就是你具体需要拦截的方法,比如上一篇的类HelloServiceImpl的sayHello方法就是一个连接点。

切面:就是你需要织入的各类通知所组成,在动态代理里,你可以认为它是一个拦截器,它还可以定义后面介绍的切点,还有通知等内容,你可以认为大部分需要织入流程的东西和配置都可以在切面配置。

通知:分为前置(before),后置(after),环绕(around),异常返回通知(afterThrowing),正常返回通知(afterReturning),它们将依据约定织入对应的流程中。

织入:生成动态代理对象,将各类通知织入流程的过程。Spring采用动态代理技术,织入流程,当默认的情况下,存在接口的,使用JDK动态代理,否则使用CGLIB动态代理织入。


为了更好的理解这些,笔者给出Spring AOP约定的流程图:


这就是Spring AOP和我们约定的流程。


当然这里还有两个概念,一个叫做切点一个叫做引入,我们还没有解释,这篇还会解释切点,等待有机会我们再解释引入吧,不过有了上表的描述,AOP的概念就简单多了,当然Spring实现AOP,比博主模拟的要复杂得多,不过道理是一样的,就是给约定,然后把你开发的代码织入到AOP约定的流程中。

光说不练,可不是码农的特色,好吧,我们来玩玩,首先我们搞一个切面

package com.learn.spring.aop.aspect;

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.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

//@Aspect,告诉Spring这是一个切面(拦截器)
@Aspect
public class MyAspect 
	
	//切点,告诉Spring你要拦截什么东西
	@Pointcut("execution(* com.learn.spring.aop.service.impl.AopServiceImpl.sayHello(..))")
	public void sayHello() 
		
	
	
	//前置通知,根据注解@Before织入流程
	@Before("sayHello()")
	public void before() 
		System.out.println("before");
	

	//后置通知,根据注解@After织入流程
	@After("sayHello()")
	public void after() 
		System.out.println("after");
	

	
	//环绕通知,根据注解@Around织入流程
	@Around("sayHello()") 
	public void around(ProceedingJoinPoint jp) 
		System.out.println("around before");
		try 
			jp.proceed();
		 catch (Throwable e) 
			e.printStackTrace();
		
		System.out.println("around after");
	

	//异常通知,根据注解@AfterThrowing织入流程
	@AfterThrowing("sayHello()")
	public void afterThrowing() 
		System.out.println("afterThrowing");
	

	//返回通知,根据注解@AfterReturning织入流程
	@AfterReturning("sayHello()")
	public void afterReturning() 
		System.out.println("afterReturning");
	



好了,这就是一个切面用注解@Aspect标注,这里博主用@Pointcut定义了一个切点,切点的定义就是告诉Spring,那些类需要启用AOP编程,

其定义的表达式:execution(* com.learn.spring.aop.service.impl.AopServiceImpl.sayHello(..))我们稍微探讨一下

execution表示在运行某个连接点(实际就是能和正则式匹配的方法)的时候执行,

正则式中:

  • 第一个*代表任意返回类型的方法
  • 而后面的:com.learn.spring.aop.service.impl.AopServiceImpl.sayHello则指向了com.learn.spring.aop.service.impl.AopServiceImpl的sayHello方法。
  • 括号中的..代表任意参数。

这样这个切点就指向了对应的类和方法,它还可以针对于AopServiceImpl的所有方法,比如配置为:

execution(* com.learn.spring.aop.service.impl.AopServiceImpl.*(..))

就会拦截

com.learn.spring.aop.service.impl.AopServiceImpl类所有的public方法

大家可以看到,可以通过切点去定义那些方法(连接点)被AOP拦截。
跟着我们搞一个接口:


其他的注解,就是前置通知(@Before),后置通知(@After),环绕通知(@Around),正常返回通知(@AfterReturning)和异常返回通知(@AfterThrowing)了,有了上面概念和流程的约定,相信大家也不难理解。


为了测试,我们先定义一个接口:


package com.learn.spring.aop.service;

public interface AopService 
	
	public void sayHello();


然后,就是它的实现类

package com.learn.spring.aop.service.impl;

import org.springframework.stereotype.Component;
import com.learn.spring.aop.service.AopService;

@Component
public class AopServiceImpl implements AopService 

	@Override
	public void sayHello() 
		System.out.println("hello world");
	



好了这里标注了@Component,说明它可以被扫描出来,那么我们来个Java配置文件

package com.learn.spring.aop.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.FilterType;

import com.learn.spring.aop.aspect.MyAspect;

@Configuration
//启动@AspectJ切面编程
@EnableAspectJAutoProxy
//定义扫描包
@ComponentScan(basePackages = "com.learn.spring.aop.*", 
    excludeFilters = @Filter(type = FilterType.REGEX, pattern = "com.learn.spring.aop.config.*"))
public class AopConfig 
	
	//声明切面Bean
	@Bean(name = "myAspect")
	public MyAspect initAspect() 
		return new MyAspect();
	



好了,这样就完成了我们的内容,跟着就是测试了。

package com.learn.spring.aop.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.learn.spring.aop.config.AopConfig;
import com.learn.spring.aop.service.AopService;

public class AopTest 

	public static void main(String[] args) 
		try 
			ApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
			AopService aopService = ctx.getBean(AopService.class);
			aopService.sayHello();
		 catch (Exception e) 
			e.printStackTrace();
		
	


那么结果就是:

 around before
before
hello world
around after
after
afterReturning

瞧这个结果并不是博主期待的,博主期待的是,

before
around before
hello world
around after
after
afterReturning
博主使用的是spring 4.3.2,当我使用XML配置的时候就可以得到期待结果,估计Spring注解存在一定的bug,不管它了。大家可以看到Spring AOP把对应的通知织入了约定的流程里,这就是AOP编程本质。

瞧,上面Spring的AOP,也是和博主第一篇的概念一样,给你通知和切点这些内容的约定,有了约定,然后它会按照约定的流程,将切面的代码织入到流程中,这便是AOP编程。下一篇博主再向你们解释为什么要用AOP。这篇就介绍一些概念和一个简单的实例,后面还要走着瞧。






以上是关于跟开振学习Spring AOP第二篇:AOP的概念和流程的主要内容,如果未能解决你的问题,请参考以下文章

跟开振学习Spring AOP第一篇:开启约定编程之路

Spring入门详细教程

Spring5学习——4AOP概念

Spring学习十三----------Spring AOP的基本概念

Spring5学习笔记 — “Spring AOP的概念相关术语”

Spring5学习笔记 — “Spring AOP的概念相关术语”