面向切面编程(即AOP):把项目中须要再多处使用的功能好比日志、安全和事务等集中到一个类中处理,而不用在每一个须要用到该功能的地方显式调用。java
横切关注点:在软件开发过程当中分散于应用中多处的功能正则表达式
切面:切点和通知的结合,通知和切点共同定义了切面的所有内容:是什么以及在什么时候和何处完成其功能express
通知:切面要完成的工做。除了描述切面要完成的工做,还解决了什么时候完成什么时候执行这个工做的问题。Spring的通知有5种类型:before、after、after-returnning、after-throwing和around这五种类型编程
链接点:链接点表示在何种操做发生时应用切面。好比方法调用时、修改字段时以及抛出异常时;安全
切点:通常使用明确的类和方法名称或是利用正则表达式匹配的类和方法名称来指定在何处应用切面,通常应用切面的点就被称为切点,通常使用切点来指定链接点。ide
引入:咱们能够建立一个通知类建立新的属性和方法,就能够在不修改切点类的前提下让他们具备新的行为功能测试
织入:织入是指把切面应用到目标对象并建立新的代理对象的过程。切面在指定的链接点被织入到目标对象,在目标对象的生命周期里有多个点能够进行织入:this
编译期:切面在目标类编译时被织入(例如AspectJ的织入编译器织入切面)spa
类加载期:切面在目标类加载到JVM时被织入(例如AspectJ5的加载时织入).net
运行期:切面在运行时的某个时刻被织入(Spring AOP以这种方式织入切面)
Spring提供了五种类型的AOP支持:
1)基于代理的经典Spring AOP;
2)纯POJO切面;
3)@AspectJ注解驱动的切面;
4)注入式AspectJ切面
前三种方式均为Spring AOP实现的变体,构建在动态代理的基础上,所以Spring对AOP的支持局限于方法拦截
Spring支持的AspectJ切点指示器
链接点=切点+执行时机
AspectJ指示器 | 描述 |
args() | 限制链接点匹配参数为指定类型的执行方法 |
@args() | 限制链接点匹配参数由指定注解标注的执行方法 |
execution() | 用于匹配链接点的执行方法,一般用于编写切点表达式的指示器 |
this() | 限制链接点匹配AOP代理的Bean引用为指定类型的类 |
target | 限制链接点匹配目标对象为指定类型的类 |
@target() | 限制链接点匹配特定的执行对象,这些对象对应的类要具备指定类型的注解 |
within() | 限制链接点匹配指定的类型 |
@within() | 限制链接点匹配指定注解标注的类型(当使用Spring AOP时,切点方法定义在由指定注解所标注的类里) |
@annotation | 限定匹配带有指定注解的链接点 |
切点表达式的组成详解
execution(* concert.Performance.perform(..))
下面解析下表达式各个部分
* : 代表该切点表达式不关心方法返回值类型,任何返回值类型均匹配
concert.Performance : 这部分指定方法所属类,用于限定切点匹配的类,可使用*模糊匹配
perform : 切点匹配的方法名
(..) : 两个点号代表切点匹配任意perform的重载方法不管入参是什么。
在切点表达式中限制选择范围
除了上述Spring支持的AspectJ切点指示器以外,Spring引入了新的bean()指示器,他支持基于Bean Id或Bean Name做为参数限制切点只匹配特定Bean。咱们能够在 xml或者注解配置中基于逻辑操做符and、or组合使用execution和其余指示器来进一步限制匹配切点,例如:
execution(* concert.Performance.perform()) and bean('woodstock')
Spring提供的定义通知的五个注解
注解 | 通知 |
@After | 通知方法会在目标方法返回或抛出异常后调用 |
@AfterReturning | 通知方法会在目标方法返回后调用 |
@AfterThrowing | 通知方法会在目标方法抛出异常后调用 |
@Around | 通知方法会将目标方法封装起来 |
@Before | 通知方法会在目标方法调用以前执行 |
目标类
@Component public class Performance { public void perform(){ System.out.println("method perform invoking"); } }
定义切面代码
@Component @Aspect public class PerformanceAspect { @Pointcut("execution (* com.example.demo.service.Performance.perform(..))") public void performancePointCut(){ } @Before("performancePointCut()") public void beforePerform(){ System.out.println("before method invoke"); } @Around("performancePointCut()") public void handler(ProceedingJoinPoint point) { try { System.out.println("method perform invoke Around start"); point.proceed(); System.out.println("method perform invoke Around end"); }catch(Throwable ex){ System.out.println("method perform throw an exception "); } } }
测试代码
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform(); } }
若是切面所通知的方法含有参数切点表达式怎么将参数传递给通知方法,相关代码以下:
目标类代码
@Component public class Performance { public void perform(String userName){ System.out.println(userName+" is performing"); } }
切面类代码
@Component @Aspect public class PerformanceAspect { @Pointcut("execution (* com.example.demo.service.Performance.perform(String))" +"&& args(userName)") public void performancePointCut(String userName){ } @Before("performancePointCut(userName)") public void beforePerform(String userName){ System.out.println(userName+" before perform"); } @Around("performancePointCut(userName)") public void handler(ProceedingJoinPoint point, String userName) { try { System.out.println(userName+" perform Around start"); point.proceed(); System.out.println(userName+" perform Around end"); }catch(Throwable ex){ System.out.println(userName+" perform throw an exception "); } } }
测试类代码
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform("zhangsan"); } }
利用引入的AOP概念,切面能够为Spring Bean添加新的方法。其基本实现原理是基于自动动态代理机制,用户经过实现一个新的接口并把该接口引入Bean中,这样看起来Bean彷佛实现了该接口的方法但其实是Bean对应的代理类接口实现被拆分到了多个类中,每次Spring发现一个Bean实现了@Aspect注解时都会为他建立一个代理,而后方法调用都会委托给被代理的bean或者被引入的接口实现,这取决于调用方法属于被代理Bean仍是引入接口。如下是使用示例:
public interface PerformanceEncorable { void performanceEncore(); } @Component @Aspect public class PerformanceEncorableIntroducer { @DeclareParents(value="com.example.demo.service.Performance", defaultImpl = DefaultPerformanceEncorableImpl.class) @Autowired private PerformanceEncorable performanceEncorable; } @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(classes = DemoApplication.class) public class PerformanceAspectTest { @Autowired private Performance performance; @Test public void testAspect(){ performance.perform("zhangsan"); PerformanceEncorable performanceEncorable = (PerformanceEncorable)performance; performanceEncorable.performanceEncore(); } }
Spring的AOP命名空间提供的声明切面相关的元素
AOP配置元素 | 用途描述 |
<aop:advisor> | 定义aop通知器 |
<aop:after> | 定义aop后置通知 |
<aop:after-returning> | 定义aop返回通知 |
<aop:after-throwing> | 定义aop的异常通知 |
<aop:around> | 定义aop环绕通知 |
<aop:aspect> | 定义一个切面 |
<aop:asoectj-autoproxy> | 启用@AspectJ注解驱动的切面 |
<aop:before> | 定义一个AOP前置通知 |
<aop:config> | 顶层的AOP配置元素 |
<aop:delare-parents> | 以透明的方式为被通知的对象引入额外的接口 |
<aop:pointcut> | 定义一个切点 |
直接基于上述例子列举XML方式的实现
1 - 在XML中声明切面
<aop:config> <aop:aspect ref="performanceAspect"> <aop:pointcut id="performancePointCut" expression="execution(* com.example.demo.service.Performance.perform(..))" /> <aop:before pointcut-ref="performancePointCut" method="beforePerform" /> <aop:around pointcut-ref="performancePointCut" method="handler" /> </aop:aspect> </aop:config>
目标类和切面类
@Component public class Performance { public void perform(String userName){ System.out.println(userName+" is performing"); } } @Component public class PerformanceAspect { public void beforePerform(String userName){ System.out.println(userName+" before perform"); } public void handler(ProceedingJoinPoint point, String userName) { try { System.out.println(userName+" perform Around start"); point.proceed(); System.out.println(userName+" perform Around end"); }catch(Throwable ex){ System.out.println(userName+" perform throw an exception "); } } }
2 - 经过切面引入新的功能
引入接口于默认实现类
public interface PerformanceEncorable { void performanceEncore(); } public class DefaultPerformanceEncorableImpl implements PerformanceEncorable{ @Override public void performanceEncore() { System.out.println("method performanceEncore invoke"); } }
XML配置以下
<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.example.demo.service.Performance+" implement-interface="com.example.demo.service.PerformanceEncorable" default-impl="com.example.demo.service.DefaultPerformanceEncorableImpl" /> </aop:aspect> </aop:config>
<aop:declare-parents>声明了此切面所通知的bean要在他们的对象层次结构上有新的父类型,具体到本例子中即为全部实现了Performance接口的Bean示例在他们的父类结构上会增长PerformanceEncorable接口,除了使用上述default-impl方式标识引入接口实现还可使用delegate-ref属性来标识,以下所示:
<aop:config> <aop:aspect> <aop:declare-parents types-matching="com.example.demo.service.Performance+" implement-interface="com.example.demo.service.PerformanceEncorable" delegate-ref="defaultPerformanceEncorableImpl" /> </aop:aspect> </aop:config>
虽然Spring AOP可以知足许多应用的切面需求,可是与AspectJ相比,Spring AOP 是一个功能比较弱的AOP解决方案。AspectJ提供了Spring AOP所不能支持的许多类型的切点。例如,当咱们须要在建立对象时应用通知,构造器切点就很是方便。Spring基于代理的AOP没法把通知应用于对象的建立过程。
1 - 定义切面
public aspect ConstructAspect { public ConstructAspect(){} /** * 定义切点 */ pointcut performance():execution(* com.example.demo.service.Performance.perform(..)); pointcut construct():execution(aop.kind4.CriticismEngineImpl.new()); before() : performance(){ System.out.println("before performance method invoke"); } after():construct(){ System.out.println("Performance construct end"); } before():construct(){ System.out.println("Performance construct begin"); } }
XML配置
<bean class="com.example.demo.service.ConstructAspect" factory-method="aspectOf"> </bean>