Spring中的AOP 知识点
Posted xiao-cui-tong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring中的AOP 知识点相关的知识,希望对你有一定的参考价值。
什么是AOP?
AOP:全称是Aspect Oriented Programming即:面向切面编程。
AOP可以对业务逻辑的各个部分进行隔离,使之耦合度降低,提高程序的重用性,提高开发效率.
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
作用:在程序运行期间,不修改源码对已有方法进行增强。
优势:减少重复代码 提高开发效率 维护方便
AOP的实现方式: 使用动态代理技术
动态代理常用的有两种方式:
基于接口的动态代理
提供者:JDK官方的Proxy类。
要求:被代理类最少实现一个接口。
涉及的类:Proxy
涉及的方法: newProxyInstance():用于创建代理对象。
方法的参数:
ClassLoader:类加载器,和被代理对象使用相同的类加载器。一般都是固定写法。
Class[]:实现的接口。和被代理对象实现相同的接口。一般固定写法
InvocationHandler: 如何代理。(想怎么增强方法,都写在这。)
它是一个接口,我们需要提供该接口的匿名内部类
public class BeanFactory { private IAccountService accountService; //IAccountService: 接口 private TransactionManager transactionManager; public void setTransactionManager(TransactionManager transactionManager) { this.transactionManager = transactionManager; } public void setAccountService(IAccountService accountService) { this.accountService = accountService; } public IAccountService createAccountServiceProxy() {
//accountServiceProxy:返回的代理对象 IAccountService accountServiceProxy = (IAccountService) Proxy.newProxyInstance( accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override //proxy:代理对象 method:将要执行的方法 args:将要执行的方法的参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { transactionManager.transfer(); //accountService:被代理对象 Object invoke = method.invoke(accountService, args); //执行的是被代理对象的方法 上下行都是对此方法的增强 也可以用equals方法选择性的对被代理对象方法进行增强 transactionManager.commit(); return invoke; } catch (Exception e) { transactionManager.rollback(); throw new RuntimeException(); } finally { transactionManager.release(); } } }); return accountServiceProxy; } }
基于子类的动态代理
提供者:第三方的CGLib
要求:被代理类不能用final修饰的类(最终类)。
涉及到的类:Enhancer
涉及到的方法:create
方法中的参数:
第一个参数:被代理对象的Class即被代理对象的字节码对象
第二个参数:是一个回调函数接口,它和jdk动态代理中的InvocationHanlder的作用和用法是一样的,需要使用其匿名内部类或者其实现类的intercept方法,实现对已有方法的增强.
public class demo { public static void main(String[] args) { final Actor actor = new Actor(); Actor actorProxy = (Actor) Enhancer.create(actor.getClass(), new MethodInterceptor() { /** * obj:代理对象 * method:将要执行的方法 * args:将要执行的方法的参数 * proxy:方法的代理对象 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("ooo"); Object invoke = method.invoke(actor, args); return invoke; } }); } }
Spring中的AOP:
开发阶段:2.把公用代码抽取出来,制作成通知
3.在配置文件中,声明切入点与通知间的关系
运行阶段(Spring框架完成的):
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
AOP相关术语:
Joinpoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是可以被增强的方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.即将要增强的方法
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
针对类层面的,动态的给一个类添加一个属性或者方法.
Target(目标对象):
被代理对象
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。优先使用jkd的动态代理.
Aspect(切面):
是切入点和通知(引介)的结合。
关于代理的选择:
在spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
配置aop:1.配置springAOP依赖 2.配置配置文件约束 3.然后就是具体的配置
基于xml的AOP配置:
<!-- 配置通知类 --> <bean id="logger" class="cn.it.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切面 id:切面的唯一标识 ref:引用通知类 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知 method:配置通知方法(即具体进行增强的方法) pointcut:配置AspectJ表达式,即将通知增强到哪个方法 execution:使用AspectJ的切入点表达式 execution(修饰符 返回值类型 包名.类名.方法名(参数列表)) --> <aop:before method="printLog" pointcut="execution(public void cn.it.service.impl.AccountServiceImpl.saveAccount())" /> </aop:aspect> </aop:config>
<!-- execution:匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void cn.it.service.impl.AccountServiceImpl.saveAccount(cn.it.domain.Account) 访问修饰符可以省略 void cn.it.service.impl.AccountServiceImpl.saveAccount(cn.it.domain.Account) 返回值可以使用*号,表示任意返回值 * cn.it.service.impl.AccountServiceImpl.saveAccount(cn.it.domain.Account) 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.AccountServiceImpl.saveAccount(cn.it.domain.Account) 使用..来表示当前包,及其子包 * cn..AccountServiceImpl.saveAccount(cn.it.domain.Account) 类名可以使用*号,表示任意类 * cn..*.saveAccount(cn.it.domain.Account) 方法名可以使用*号,表示任意方法 * cn..*.*( cn.it.domain.Account) 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * cn..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * cn..*.*(..) 全通配方式: * *..*.*(..) 注: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。 execution(* cn.it.service.impl.*.*(..)) -->
为了使用相同的表达式方便,因此可以定义一个通用的表达式来进行引用。 <!-- 配置aop --> <aop:config> <!-- 配置切入点表达式方便被引用 --> <aop:pointcut expression="execution(* cn.it.service.impl.*.*(..))" id="pt1"/> <aop:aspect id="logAdvice" ref="logger"> <aop:before method="printLog" pointcut-ref="pt1"/> <aop:before method="printLog2" pointcut-ref="pt1"/> <aop:before method="printLog3" pointcut-ref="pt1"/> </aop:aspect> </aop:config>
常用标签:
<aop:config>
作用:用于声明开始aop的配置
配置:<aop:config></aop:config>
<aop:aspect>
作用: 用于配置切面。
属性:
id:给切面提供一个唯一标识。
ref:引用配置好的通知类bean的id。
配置:<aop:aspect id="logAdvice" ref="logger">
<aop:pointcut>
作用:用于配置切入点表达式
属性:
expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识。
配置:<aop:pointcut expression="execution(* cn.it.service.impl.*.*(..))" id="pt1"/>
<aop:before>
作用:用于配置前置通知
属性:
method:指定通知中方法的名称。
pointcut:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:before method="beforePrintLog" pointcut-ref="pt1"/>
<aop:after-returning>
作用:用于配置后置通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"/>
<aop:after-throwing>
作用:用于配置异常通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"/>
<aop:after>
作用:用于配置最终通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:after method="afterPrintLog" pointcut-ref="pt1"/>
<aop:around>
作用:用于配置环绕通知
属性:
method:指定通知中方法的名称。
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
配置:<aop:around method="transactionAround" pointcut-ref="pt1"/>
说明:它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:通常情况下,环绕通知都是独立使用的
基于注解的AOP配置:
//把通知类也使用注解配置 @Component("logger") public class Logger { }
//在通知类上使用@Aspect注解声明为切面 //作用:把当前类声明为切面类。 @Component("logger") @Aspect//表示当前类是一个切面类(也可以称之为通知类) public class Logger {
}
/**使用注解配置通知类型 @Before 作用: 把当前方法看成是前置通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。 */ /** * 前置通知 */ @Before("execution(* cn.it.service.impl.*.*(..))") public void beforePrintLog() { System.out.println("前置通知"); } @AfterReturning 作用: 把当前方法看成是后置通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 /** * 后置通知 */ @AfterReturning("execution(* cn.it.service.impl.*.*(..))") public void afterReturningPrintLog() { System.out.println("后置通知执行了"); } @AfterThrowing 作用: 把当前方法看成是异常通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 /** * 异常通知 */ @AfterThrowing("execution(* cn.it.service.impl.*.*(..))") public void afterThrowingPrintLog() { System.out.println("异常通知执行了"); } @After 作用: 把当前方法看成是最终通知。 属性: value:用于指定切入点表达式,还可以指定切入点表达式的引用 /** * 最终通知 */ @After("execution(* cn.it.service.impl.*.*(..))") public void afterPrintLog() { System.out.println("最终通知执行了"); } 在spring配置文件中开启spring对注解AOP的支持 <!-- 开启spring对注解AOP的支持 --> <aop:aspectj-autoproxy/>
/**环绕通知注解配置 @Around 作用:把当前方法看成是环绕通知。 属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。 */ /** * 环绕通知 * spring框架为我们提供了一个接口,该接口可以作为环绕通知的方法参数来使用 * ProceedingJoinPoint。当环绕通知执行时,spring框架会为我们注入该接口的实现类。 * 它有一个方法proceed(),就相当于invoke,明确的业务层方法调用 * * spring的环绕通知: * 它是spring为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。 */ @Around("execution(* cn.it.service.impl.*.*(..))") public void aroundPrintLog(ProceedingJoinPoint pjp) { try { System.out.println("前置通知"); pjp.proceed();//明确的方法调用 System.out.println("后置通知"); } catch (Throwable e) { System.out.println("异常通知"); e.printStackTrace(); }finally { System.out.println("最终通知"); } }
切入点表达式注解 @Pointcut 作用:指定切入点表达式 属性:value:指定表达式的内容 @Pointcut("execution(* cn.it.service.impl.*.*(..))") private void pt1() { } 引用方式: /** * 环绕通知 * @param pjp * @return */ @Around("pt1()")//注意:别忘了写括号 public void aroundPrintLog(ProceedingJoinPoint pjp) { }
纯注解的AOP配置:
//创建配置类 @Configuration//声明这是个配置类 @ComponentScan("cn.it")//配置包扫描 @EnableAspectJAutoProxy//开启AOP的注解扫描 public class SpringConfiguration { }
以上是关于Spring中的AOP 知识点的主要内容,如果未能解决你的问题,请参考以下文章