AOP概念
AOP:Aspect Oriented Programming,面向切面编程。
通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。
主要功能:日志记录、性能统计、安全控制、事务处理、异常处理等。
AOP相关概念
概念 | 说明 |
切面(Aspect) | 一个关注点的模块化,可能会横切多个对象。 |
连接点(Joinpoint) | 符合条件的每个被拦截处为连接点 |
通知/建言(Advice) | 在切面某个特定的连接点上执行的动作,Before、After、Around、After-returning、After-throwing。 |
切入点(Pointcut) | 匹配连接点的断言,在AOP中Advice和一个Pointcut表达式关联 |
引入(Introduction) | 在不修改类代码的前提下,为类添加新的方法和属性 |
目标对象(TargetObject) | 被一个或者多个切面所通知的对象 |
AOP代理(AOP Proxy) | AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能) |
织入(Weaving) | 把切面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象,分为:编译时织入、类加载时织入、执行时织入 |
Spring框架中AOP,提供了声明式的企业服务,特别是EJB的替代服务的声明。允许用户制定自己的切面,以完成OOP与AOP的互补使用。
Spring AOP,纯java实现,无需特殊的编译过程,不需要控制类加载器层次。目前只支持方法执行连接点(通知Spring Bean的方法执行)。侧重于提供一种AOP实现和Spring IoC容器的整合,用于帮助解决企业应用中的常见问题。
schema-defined aspects只支持singleton model,即所有基于配置文件的切面配置只支持单例模式。
基于配置的实现(理解)
配置切面Aspect<aop:aspect>
Spring所有切面和通知器都必须放在<aop:config>内,可以配置多个<aop:config>元素,每个可以包含pointcut,advisor,aspect元素(必须按照这个顺序声明)。
大量使用了Spring的自动代理机制。
<aop:config> <aop:aspect id="moocAspectAOP" ref="moocAspect"> <aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))" id="moocPiontcut"/> <aop:before method="before" pointcut-ref="moocPiontcut"/> <aop:after-returning method="afterReturning" pointcut-ref="moocPiontcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="moocPiontcut"/> <aop:after method="after" pointcut-ref="moocPiontcut"/> <aop:around method="around" pointcut-ref="moocPiontcut"/> <aop:around method="aroundInit" pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String, int)) and args(bizName, times)"/> <aop:declare-parents types-matching="com.imooc.aop.schema.advice.biz.*(+)" implement-interface="com.imooc.aop.schema.advice.Fit" default-impl="com.imooc.aop.schema.advice.FitImpl"/> </aop:aspect> </aop:config>
配置切入点Pointcut<aop:pointcut>
expression表达式可以查阅相关文档,用到的时候再学习,不需死记硬背。。。
execution(public **(..))//切入点为执行所有public方法时
execution(* set*(..))//切入点为执行所有set开始的方法时
execution(*com.xyz.service.AccountService.*(..))//切入点为执行AccountService类中的所有方法时
execution(* com.xyz.service..(..))//切入点为执行com.xyz.service包下所有方法时
execution(* com.xyz.service...(..))//(三个点)切入点为执行该包及其子包下所有方法时
within()
this()
target()
args()
bean()
@target
@within
@annotation
<aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))" id="moocPiontcut"/>
配置通知Advice<aop:before>..<aop:after>
Advice的类型:前置通知(aop:before)、返回后通知(aop:after-returning)、抛出异常后通知(after-throwing)、后通知(aop:after)、环绕通知(aop:around)
<aop:aspect id="moocAspectAOP" ref="moocAspect"> <aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz.*(..))" id="moocPiontcut"/> <aop:before method="before" pointcut-ref="moocPiontcut"/>
说明:定义一个切面“MOOCAspectAOP”(对应的Java Bean为ref=”moocAspect”),定义切点为“moocPiontcut”(所有以Biz结尾的类的所有方法),声明前置通知“before”(在bean中实现的方法)。则会在Biz结尾类的方法调用前调用before方法,before/after-returning/after/after-throwing类似。
环绕通知Around advice
通知方法第一个参数必须是ProceedingJoinPoint类型
例1(方法无参数)
<aop:around method="around" pointcut-ref="moocPiontcut"/>
public Object around(ProceedingJoinPoint pjp) { Object obj = null; try { System.out.println("MoocAspect around 1."); obj = pjp.proceed();//代表具体业务方法执行,obj为返回值 System.out.println("MoocAspect around 2."); } catch (Throwable e) { e.printStackTrace(); } return obj; }
例2(方法有参数)
<aop:around method="aroundInit" pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String, int)) and args(bizName, times)"/>
public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) { System.out.println(bizName + " " + times); Object obj = null; try { System.out.println("MoocAspect aroundInit 1."); obj = pjp.proceed();//代表具体业务方法执行,obj为返回值 System.out.println("MoocAspect aroundInit 2."); } catch (Throwable e) { e.printStackTrace(); } return obj; }
如果两个都声明则都会执行环绕通知,顺序和声明顺序有关。
配置Introductions<aop:declare-parents>
简介允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些(被匹配的)对象。
由<aop:aspect>中的<aop:declare-parents>元素声明该元素用于声明所匹配的类型拥有一个新的parent。
<aop:declare-parents
types-matching="com.imooc.aop.schema.advice.biz.*(+)" implement-interface="com.imooc.aop.schema.advice.Fit" default-impl="com.imooc.aop.schema.advice.FitImpl"/>
types-matching=匹配什么样的类
implement-interface=具体使用哪一个接口(匹配的类中并没有实现)
default-impl=指定接口的实现类
Fit fit = (Fit)super.getBean("aspectBiz"); fit.filter(); fit.biz();
网上的一个解释,有助于理解:
我来解释(逗逼)一下,赞同楼上说法,introduction译成“引入”较合理。
那个introduction的意思是:
有个类叫小明(biz),小明隔壁住着老王(FitImpl),老王实现了一个技能叫开豪车(Fit)
现在上帝声明了一个切面,这个切面给小明指定一个新的爹叫老王,于是小明每次叫爸爸的时候就能开豪车了~
类匹配(小明):type-matching
接口(开豪车):implement-interface
接口的实现类(老王):default-impl
引入的作用:小明干自己的事的时候(叫爸爸)能莫名其妙地开上豪车而不用做多余的工作,这些工作由上帝(AOP)帮他完成,这叫“解耦”
配置advisors???
基于注解的实现(主流)
l @Aspect声明是一个切面
l @After、@Before、@Around定义建言(advice),可以直接将拦截规则(切点)作为参数
l @PointCut专门定义拦截规则,然后在建言的参数中调用
定义拦截规则的注解,使用@Action注解其他方法(切点)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Action { String name() default "默认name"; }
定义Service类,使用注解定义切点,也可以使用匹配规则(在切面中定义)
@Service public class DemoAnnotationService { @Action(name = "注解式拦截的信息")//不传参数则用默认值 public void fun1(){System.out.println("提供服务1");} public void fun2(){System.out.println("提供服务2");} }
定义切面类,
@Aspect//定义为切面 @Component//Spring管理的普通Bean public class LogAspect { @Pointcut("@annotation(com.sunlin.test_SpringByMaven.aop.Action)")//注解声明的切点 public void annotationPointCut(){} @Before("annotationPointCut()")//声明Before建言,使用@PointCut为切点 public void before1(JoinPoint joinPoint){ MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); Action action = method.getAnnotation(Action.class); System.out.println("注解式拦截:"+action.name()); } //使用拦截规则作为参数 @Before("execution(* com.sunlin.test_SpringByMaven.aop.DemoAnnotationService.fun2(..))") public void before2(JoinPoint joinPoint){ MethodSignature signature = (MethodSignature)joinPoint.getSignature(); Method method = signature.getMethod(); System.out.println("方法规则式拦截:"+method.getName()); } }
定义配置类
@Configuration @ComponentScan("com.sunlin.test_SpringByMaven.aop") @EnableAspectJAutoProxy//开启Spring对AspectJ的支持 public class AopConfig { }
运行
@Test public void test_AnnotationAOP() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AopConfig.class); DemoAnnotationService demoAnnotationService = context.getBean(DemoAnnotationService.class); demoAnnotationService.fun1(); demoAnnotationService.fun2(); context.close(); }
输出:
注解式拦截:注解式拦截的信息
提供服务1
方法规则式拦截:fun2
提供服务2