Spring–AOP的概念
1 什么是AOP
- AOP:Aspect Oriented Programming的缩写,意思是面向切面编程,通过预编译方式运行期动态代理实现程序功能统一维护的一种技术
- 主要功能是:日志记录,性能统计,安全控制,事务处理,异常处理等
2 AOP实现方式
- 预编译:
- AspectJ
- 运行期间动态代理(JDK动态代理,CGLib动态代理)
- SpringAOP,JbossAOP
3 AOP相关关键词
名称 | 说明 |
---|---|
切面(Aspect) | 一个关注点的模块化,这个关注点可能会横切多个对象 |
连接点(Joinpoint) | 程序执行过程中的某个特定的点 |
通知(Advice) | 在切面的某个特定的连接点上执行的动作 |
切入点(Pointcut) | 匹配连接点的断言,在AOP中通知和一个切入点表达式关联 |
引入(Introduction) | 在不修改类的代码的前提下,为类添加新的方法和属性 |
目标对象(Target Object) | 被一个或者多个切面所通知的对象 |
AOP代理(AOP Proxy) | AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能) |
织入(Weaving) | 把切面链接到其他的应用程序类型或者对象上,忙并创建一个被通知的对象,分为:编译的织入,类加载的织入,执行时织入 |
4 Advice的类型
名称 | 说明 |
---|---|
前置通知(Before advice) | 在某连接点(join point)之前执行的通知,但不能阻止连接点前执行(除非它抛出一个异常) |
返回后通知(After returning advice) | 在某连接点(join point)正常完成后执行的通知 |
抛出异常后通知(After throwing advice) | 在方法抛出异常退出时执行的通知 |
后通知(After(finally)advice) | 当某连接点退出的时后执行的通知(不论是正常返回还是异常退出) |
环绕通知(Around Advice) | 包围一个连接点(join point)的通知 |
5 Spring框架中AOP用途
- 提供了声明式的企业服务,特别是EBJ的替代服务的声明
- EBJ解释:EJB (Enterprise JavaBean)是J2EE的一部分,定义了一个用于开发基于组件的企业多重应用程序的标准。其特点包括网络服务支持和核心开发工具(SDK)
- 允许用户定制自己的方面,已完成OOP和AOP的互补作用
6 Spring的AOP实现
- 纯Java 的实现,无需特殊的编译过程,不需要控制类的加载层次
- 目前只支持方法执行连接点(通知Spring Bean的方法执行)
- 不是提供了最完整的AOP实现;而是侧重于提供一种AOP实现和Spring Ioc容器之间的整合,用于帮助解决企业应用中的常见问题
- Spring AOP不会与AspectJ竞争,从而提供综合全面的AOP解决方案
7 有接口和无接口的Spring AOP实现区别
- Spring AOP默认使用标准的JavaSE动态代理作为AOP代理,这使得任何接口(或者接口集)都可以被代理
- Spring AOP中也可以使用CGLIB代理(如果一个业务对象并没有实现一个接口)
8 Advisors简介
- advisor就像一个小的自包含的面,只有一个advice
- 切面自身就是一个bean表示,并且必须实现某个advice接口,同时,advisor也可以很好的利用AspectJ的切入点表达式
- Spring通过配置文件中元素支持advisor实际使用中,大多数情况下它会和transactional advice配合使用
- 为了定义一个advisor的优先级以便让advice可以有序,使用order属性来定义advisor的顺序
9 AOP详解参考博文:
———-==博客参考链接(1)==———–
———-==博客参考链接(2)==——
10 SpringAOP配置的四种方式:
说了这么多,进入正题,如何配置Spring的AOP才是重点:
1. 经典的基于代理的AOP
2. @AspectJ注解驱动的切面
3. 纯POJO切面
4. 注入式AspectJ切面
10.1 经典的基于代理的AOP
介绍
- Spring1.2的历史用法现在的最新版本的框架依然支持
- SpringAOP的基础
- 现在的用法也是基于历史的,只不过更加方便了
实例
用到的接口和类
接口 | 类 | 切面类 |
---|---|---|
BizLogic | BizLogicalImpl | LogAfter,LogBefore |
接口:
package com.bart.aopfour;
public interface BizLogic {
public String say();
}
接口实现类:
package com.bart.aopfour;
public class BizLogicImpl implements BizLogic{
@Override
public String say() {
System.out.println("BizLogicImpl:say()...");
return "BizLogicImpl`s say";
}
}
切面类:
package com.bart.aopfour;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class LogBefore implements MethodBeforeAdvice {
public LogBefore(){}
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("LogBefore:"+arg0.getName()+" "+
arg2.getClass());
}
}
=======================分割线===========================
package com.bart.aopfour;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
public class LogAfter implements AfterReturningAdvice {
public LogAfter(){}
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("LogAfter:"+arg0.getClass()+" "+
arg1.getName()+arg3.getClass().getName());
}
}
aopfour.xml文件的配置:
==此处注意:==
xml文件中根节点<beans ... />
的属性值必须完整跟示例的XML一样,否则会无法匹配相关的bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用Spring API 配置切面 -->
<bean id="bizLogicImpl" class="com.bart.aopfour.BizLogicImpl"/>
<bean id="logBefore" class="com.bart.aopfour.LogBefore" />
<bean id="logAfter" class="com.bart.aopfour.LogAfter" />
<!-- 配置切点有两种实现类
1. org.springframework.aop.support.JdkRegexpMethodPointcut
2. org.springframework.aop.support.NameMatchMethodPointcut
-->
<bean id="logPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames" >
<list>
<value>say*</value>
</list>
</property>
</bean>
<!-- 配置通知 -->
<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="logBefore"/>
<property name="pointcut" ref="logPointcut"/>
</bean>
<!-- 配置代理 -->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bizLogicImpl"/>
<property name="proxyInterfaces" value="com.bart.aopfour.BizLogic"/>
<property name="interceptorNames" >
<list>
<value>logAdvisor</value>
<value>logAfter</value>
</list>
</property>
</bean>
</beans>
测试:
context = new ClassPathXmlApplicationContext("aopfour.xml");
BizLogic bizLogic = (BizLogic) context.getBean("logProxy");
bizLogic.say();
输出结果:
LogBefore:say--class com.bart.aopfour.BizLogicImpl
BizLogicImpl:say()...
LogAfter:class java.lang.String--say--com.bart.aopfour.BizLogicImpl
==注意事项==
在使用Spring API配置切面的Bean标签的时候注意一些id的单词是Spring框架固定的,不能改变,否则会报各种错误
1. 在配置pointcut的的时候如果要配置多个切点的list则注意此处
<bean id="logPointcut" class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames" >
<list>
<value>say*</value>
</list>
</property>
</bean>
<property />
标签的name值必须是”mappedNames”
2. 配置通知 的时候
<!-- 配置通知 -->
<bean id="logAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="logBefore"/>
<property name="pointcut" ref="logPointcut"/>
</bean>
<property />
标签的name值必须是”advice”还有另外一个<property />
的name值必须是pointcut
3. 配置proxyBean代理的时候
<!-- 配置代理 -->
<bean id="logProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="bizLogicImpl"/>
<property name="proxyInterfaces" value="com.bart.aopfour.BizLogic"/>
<property name="interceptorNames" >
<list>
<value>logAdvisor</value>
<value>logAfter</value>
</list>
</property>
</bean>
<property />
标签的name值依次固定格式为:target,proxyInterfaces,interceptorNames
==以上注意事项,千万谨记。否则会报出各种的错误==
10.2 @AspectJ注解驱动的切面
AspectJ的特点
- @AspectJ的风格类似纯java注解的普通java类
- Spring可以使用AspectJ来坐切入点解析
- AOP的运行时仍旧是纯的Spring AOP,对AspectJ的编译器或者组织无依赖性
AspectJ的来两种配置方式
- 对@AspectJ支持可以使用XML或者Java风格的配置
- 确保AspectJ的aspectjweaver.jar库包含在应用程序(jdk1.68或者更高)的classpath中
注解中用到的一些属性
属性名 | 含义 |
---|---|
execution | 匹配方法执行的连接点 |
within | 限定匹配特定类型的连接点 |
this | 匹配特定类型连接点的bean引用是指定类型的实例的限制 |
target | 限定匹配特定连接点的目标对象是指定类型的实例 |
args | 限定匹配特定连接点的参数是给定类型的实例 |
@target | 限定匹配特定连接点的类执行对象具有给定类型的注解 |
@args | 限定匹配到内具有给定的注释类型的连接点 |
@within | 限定匹配到内具有给定的注释类型的连接点 |
@annotation | 限定匹配特定连接点的主体具有给定的注解 |
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
<aop:aspectj-autoproxy />
实例:
用到的接口和类
接口 | 类 | 切面类 |
---|---|---|
BizLogicalInterface | BizLogicalImpl | BartAspect |
接口:
package com.bart.aopfive;
public interface BizLogicalInterface {
public String say(String arg);
public String talk();
}
实现类和切面:
import org.springframework.stereotype.Service;
@Service
public class BizLogicalImpl implements BizLogicalInterface {
@Override
public String say(String arg) {
System.out.println("BizLogicalImpl:say()--"+arg);
// throw new RuntimeException("抛出异常");
return "say is successed";
}
@Override
public String talk() {
System.out.println("BizLogicalImpl:talk()...");
return null;
}
}
=========================分割线==================================
package com.bart.aopfive;
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;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class BartAspect {
//配置切点 注意此处的execution后面的*say(..)括号里要加上两个点,否则无法匹配参数
@Pointcut("execution(* com.bart.aopfive.BizLogicalImpl.*say(..))")
public void pointcut(){}
@Pointcut("execution(* com.bart.aopfive.BizLogicalImpl.*talk())")
public void bizPointcut(){}
//配置前置通知
@Before("pointcut()")
public void before() {
System.out.println("BartAspect:before()...");
}
//前置通知带参数
@Before("pointcut() && args(arg)")
public void beforeWithParam(String arg) {
System.out.println("BartAspect:beforeWithArgs--"+arg);
}
//配置后只通知
@After("pointcut()")
public void after() {
System.out.println("BartAspect:after()...");
}
//配置后置返回通知
@AfterReturning(pointcut="pointcut()",returning="returnValue")
public void afterReturning(Object returnValue) {
System.out.println("BartAspect:afterReturning--"+returnValue);
}
//配置around通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("BartAspect:around--1");
Object obj = pjp.proceed();
System.out.println("BartAspect:around--1");
System.out.println("BartAspect:around--"+obj);
return obj;
}
//配置抛出异常通知
@AfterThrowing(pointcut="pointcut()",throwing="ex")
public void afterThrowing(RuntimeException ex) {
System.out.println("BartAspect:afterThrowing:"+ex.getMessage());
}
}
配置aopfive.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 使用 AspectJ 注解 配置切面 -->
<context:component-scan base-package="com.bart.aopfive"/>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试:
context = new ClassPathXmlApplicationContext("aopfive.xml");
//此处必须视线接口才不会报错,否则要在aopfive.xml文件中配置proxyTargetClass属性为true,
//意思是强制使用CGLIB代理
BizLogicalInterface bli = (BizLogicalInterface) context.getBean("bizLogicalImpl");
bli.say("test param arg");
//bli.talk();
测试结果:
BartAspect:around--1
BartAspect:before()...
BartAspect:beforeWithArgs--test param arg
BizLogicalImpl:say()--test param arg
BartAspect:around--1
BartAspect:around--say is successed
BartAspect:after()...
BartAspect:afterReturning--say is successed
10.3 纯POJO切面
每个关注点集中于一处,而不是分散到多处代码中
服务模块更加简洁,因为它们只包含主要关注点的代码,次要关注点被转移到切面中了
Introduction简介:
允许一个==切面==(AspectImpl)声明一个==指定接口==(AdviceInterface)的通知对象
并且提供一个==接口的实现类==(AdviceImpl)来代表这些对象
本质意义是在不改变==目标类==(BartExp)的源码的前提下,增添新的方法
相当于给目标类实现了一个接口,但又不必更改目标类的源码
通过xml文件是配置来实现aop
实例:
用到的接口和类
目标类 | 切面接口 | 切面类 | Introduction接口 | Introduction实现类 |
---|---|---|---|---|
BartExp | AspectInterface | AspectImpl | AdviceInterface | AdviceImpl |
目标类:
/*
* 被切面作用的对象
* */
package com.bart.aopthree;
public class BartExp {
public void bartSay() {
System.out.println("BartExp: bartSay() method");
//测试afterThrowing
//throw new RuntimeException("say failured...");
}
public void bartInit(String name,int times) {
System.out.println("BartExp: bartInit():"+name+" "+times);
}
}
切面接口及其实现类:
package com.bart.aopthree;
public interface AspectInterface {
public void say();
}
===============分割线======================
/*
* 切面对象
* */
package com.bart.aopthree;
import org.aspectj.lang.ProceedingJoinPoint;
public class AspectImpl implements AspectInterface{
public void say() {
System.out.println("AspectImpl:say() method");
}
public void before() {
System.out.println("AspectImpl: before");
}
public void afterReturning() {
System.out.println("AspectImpl: afterReturning");
}
public void afterThrowing() {
System.out.println("AspectImpl: afterThrowing");
}
public void after() {
System.out.println("AspectImpl: after");
}
public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("AspectImpl:around---1");
obj = pjp.proceed();
System.out.println("AspectImpl:around---2");
System.out.println("AspectImpl:around---"+obj);
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
public Object aroundInit(ProceedingJoinPoint pjp,String name,int times) {
System.out.println("AspectImpl:aroundInit:"+name+" "+times);
Object obj = null;
try {
System.out.println("AspectImpl:around---1");
obj = pjp.proceed();
System.out.println("AspectImpl:around---2");
System.out.println("AspectImpl:around---"+obj);
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
Introduction(简介)接口及其实现类:
package com.bart.aopthree;
public interface AdviceInterface {
public void say();
public void eat();
}
================分割线================
package com.bart.aopthree;
public class AdviceImpl implements AdviceInterface {
public void say() {
System.out.println("AdviceImpl:say()");
}
public void eat() {
System.out.println("AdviceImpl:eat()");
}
}
aopthree.xml配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="aspectImpl" class="com.bart.aopthree.AspectImpl"/>
<bean id="bartExp" class="com.bart.aopthree.BartExp"/>
<aop:config>
<aop:aspect id="aspectImplAOP" ref="aspectImpl">
<aop:pointcut expression="execution(* com.bart.aopthree.BartExp.bartSay())" id="aspectImplPointcut"/>
<aop:before method="before" pointcut-ref="aspectImplPointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="aspectImplPointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="aspectImplPointcut"/>
<aop:after method="after" pointcut-ref="aspectImplPointcut"/>
<aop:around method="around" pointcut-ref="aspectImplPointcut"/>
<!-- 带参数的环绕 -->
<aop:around method="aroundInit"
pointcut="execution(* com.bart.aopthree.BartExp.bartInit(String,int)) and args(name,times)"/>
<!-- introductions(简介) -->
<aop:declare-parents
types-matching="com.bart.aopthree.BartExp"
implement-interface="com.bart.aopthree.AdviceInterface"
default-impl="com.bart.aopthree.AdviceImpl"/>
</aop:aspect>
</aop:config>
</beans>
测试1:
context=new ClassPathXmlApplicationContext("aopthree.xml");
BartExp bartExp = (BartExp) context.getBean("bartExp");
bartExp.bartSay();
测试1输出结果:
AspectImpl: before
AspectImpl:around---1
BartExp: bartSay() method
AspectImpl:around---2
AspectImpl:around---null
AspectImpl: after
AspectImpl: afterReturning
测试2:
context=new ClassPathXmlApplicationContext("aopthree.xml");
BartExp bartExp = (BartExp) context.getBean("bartExp");
bartExp.bartInit("Bart", 3);
测试2输出结果:
AspectImpl:aroundInit:Bart 3
AspectImpl:aroundInit---1
BartExp: bartInit():Bart 3
AspectImpl:aroundInit---2
AspectImpl:aroundInit---null
测试3(Introduction):
context=new ClassPathXmlApplicationContext("aopthree.xml");
BartExp bartExp = (BartExp) context.getBean("bartExp");
/* introduction(简介)的使用测试
* 在不更改目标类(BartExp)的前提下增加新的方法
*/
AdviceInterface adviceInterface =(AdviceInterface)context.getBean("bartExp");
//添加的方法
adviceInterface.say();
adviceInterface.eat();
//bart原来的方法
BartExp bartExp = (BartExp)adviceInterface;
bartExp.bartSay();
测试3输出结果:
AdviceImpl:say()
AdviceImpl:eat()
AspectImpl: before
AspectImpl:around---1
BartExp: bartSay() method
AspectImpl:around---2
AspectImpl:around---null
AspectImpl: after
AspectImpl: afterReturning
10.4 注入式AspectJ切面
该方法类似于纯POJO切面,只不过POJO在运行期编译,而AspectJ在运行前编译
详细了解==参考博文==
在初学的过程中遇到过很多不懂的地方自己也是查阅资料和阅读各位大神们的博客才搞明白的。
也这篇博客记录了学习Spring的一些基础的知识。大部分是简单的小例子。便于初学者理解。
博客的代码都是经过本人测试正确运行的。由于本人才学疏浅,难免会有不足或者遗漏的地方,还望读者见谅。