AOP 简介
AOP的概念
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
AOP思想的实现方案
模拟AOP的基础代码
其实在之前学习BeanPostProcessor时,在BeanPostProcessor的after方法中使用动态代理对Bean进行了增强,实际存储到单例池singleObjects中的不是当前目标对象本身,而是当前目标对象的代理对象Proxy,这样在调用目标对象方法时,实际调用的是代理对象Proxy的同名方法,起到了目标方法前后都进行增强的功能,对该方式进行一下优化,将增强的方法提取出去到一个增强类中,且只对com.itheima.service.impl包下的任何类的任何方法进行增强
// 自定义增强类
public class MyAdvice {
public void beforeAdvice(){
System. out .println( "beforeAdvice ..." );
}
public void afterAdvice(){
System. out .println( "afterAdvice ..." );
}
}
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {private ApplicationContext applicationContext;//注入Spring容器对象public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {MyAdvice myAdvice = applicationContext.getBean(MyAdvice.class);//获得Advice对象String packageName = bean.getClass().getPackage().getName();if("com.itheima.service.impl".equals(packageName)){//对Bean进行动态代理,返回的是Proxy代理对象Object proxyBean = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(Object proxy, Method method, Object[] args) -> {myAdvice.beforeAdvice();//执行Advice的before方法Object result = method.invoke(bean, args);//执行目标myAdvice.afterAdvice();//执行Advice的after方法return result; });//返回代理对象return proxyBean; }return bean; }public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;
}}
AOP相关概念
基于xml配置的AOP
xml方式AOP快速入门
前面我们自己编写的AOP基础代码还是存在一些问题的,主要如下:
被增强的包名在代码写死了
通知对象的方法在代码中写死了
通过配置文件的方式去解决上述问题
配置哪些包、哪些类、哪些方法需要被增强
配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强
配置方式的设计、配置文件(注解)的解析工作,Spring已经帮我们封装好了
xml方式配置AOP的步骤:
1、导入AOP相关坐标;
2、准备目标类、准备增强类,并配置给Spring管理;
3、配置切点表达式(哪些方法被增强);
4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)。
1、导入AOP相关坐标
< dependency >
< groupId >org.aspectj</ groupId >
< artifactId >aspectjweaver</ artifactId >
< version >1.9.6</ version >
</ dependency >
Spring-context坐标下已经包含spring-aop的包了,所以就不用额外导入了
2、准备目标类、准备增强类,并配置给Spring管理
public interface UserService {
void show1();
void show2();
}
public class UserServiceImpl implements UserService {
public void show1() {
System. out .println( "show1..." );
}
public void show2() {
System. out .println( "show2..." );
}
}
public class MyAdvice {
public void beforeAdvice(){
System. out .println( "beforeAdvice" );
}
public void afterAdvice(){
System. out .println( "afterAdvice" );
}
}
<!-- 配置目标类 , 内部的方法是连接点 -->
< bean id= "userService" class= "com.itheima.service.impl.UserServiceImpl" />
<!-- 配置通知类 , 内部的方法是增强方法 -->
< bean id= “ myAdvice" class= "com.itheima.advice.MyAdvice" />
3、配置切点表达式(哪些方法被增强)
4、配置织入(切点被哪些通知方法增强,是前置增强还是后置增强)
< aop :config >
<!--配置切点表达式 , 对哪些方法进行增强 -->
< aop :pointcut id= "myPointcut" expression= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
<!--切面 = 切点 + 通知 -->
< aop :aspect ref= "myAdvice" >
<!--指定前置通知方法是beforeAdvice-->
< aop :before method= "beforeAdvice" pointcut-ref= "myPointcut" />
<!--指定后置通知方法是afterAdvice-->
< aop :after-returning method= "afterAdvice" pointcut-ref= "myPointcut" />
</ aop :aspect >
</ aop :config >
xml方式AOP配置详解
xml配置AOP的方式还是比较简单的,下面看一下AOP详细配置的细节:
1.切点表达式的配置方式
2.切点表达式的配置语法
3.通知的类型
4.AOP的配置的两种方式
切点表达式的配置方式有两种,直接将切点表达式配置在通知上,也可以将切点表达式抽取到外面,在通知上进行引用
< aop :config >
<!--配置切点表达式 , 对哪些方法进行增强 -->
< aop :pointcut id= "myPointcut" expression= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
<!--切面 = 切点 + 通知 -->
< aop :aspect ref= "myAdvice" >
<!--指定前置通知方法是beforeAdvice-->
< aop :before method= "beforeAdvice" pointcut-ref= "myPointcut" />
<!--指定后置通知方法是afterAdvice-->
< aop :after-returning method= "afterAdvice" pointcut= "execution(void com.itheima.service.impl.UserServiceImpl.show1())" />
</ aop :aspect >
</ aop :config >
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution([ 访问修饰符 ] 返回值类型 包名 . 类名 . 方法名 ( 参数 ))
配置规则
1.访问修饰符可以省略不写;
2.返回值类型、某一级包名、类名、方法名 可以使用 * 表示任意;
3.包名与类名之间使用单点 . 表示该包下的类,使用双点 .. 表示该包及其子包下的类;
4.参数列表可以使用两个点 .. 表示任意参数。
切点表达式举几个例子方便理解
// 表示访问修饰符为 public 、无返回值、在 com.itheima.aop 包下的 TargetImpl 类的无参方法 show
execution(public void com.itheima.aop.TargetImpl.show())
// 表述 com.itheima.aop 包下的 TargetImpl 类的任意方法
execution(* com.itheima.aop.TargetImpl.*(..))
// 表示 com.itheima.aop 包下的任意类的任意方法
execution(* com.itheima.aop.*.*(..))
// 表示 com.itheima.aop 包及其子包下的任意类的任意方法
execution(* com.itheima.aop..*.*(..))
// 表示任意包中的任意类的任意方法
execution(* *..*.*(..))
AspectJ的通知由以下五种类型
环绕通知
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
//环绕前
System. out .println( " 环绕前通知 " );
//目标方法
joinPoint.proceed();
///环绕后
System. out .println( " 环绕后通知 " );
}
< aop :around method= "around" pointcut-ref= "myPointcut" />
异常通知,当目标方法抛出异常时,异常通知方法执行,且后置通知和环绕后通知不在执行
public void afterThrowing(){
System. out .println( " 目标方法抛出异常了,后置通知和环绕后通知不在执行 " );
}
< aop :after-throwing method= "afterThrowing" pointcut-ref= "myPointcut" />
最终通知,类似异常捕获中的finally,不管目标方法有没有异常,最终都会执行的通知
public void after(){
System. out .println( " 不管目标方法有无异常,我都会执行 " );
}
< aop :after method= "after" pointcut-ref= "myPointcut" />
通知方法在被调用时,Spring可以为其传递一些必要的参数
JoinPoint 对象
public void 通知方法名称 (JoinPoint joinPoint){
//获得目标方法的参数
System. out .println(joinPoint.getArgs());
//获得目标对象
System. out .println(joinPoint.getTarget());
//获得精确的切点表达式信息
System. out .println(joinPoint.getStaticPart());
}
ProceedingJoinPoint对象
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System. out .println(joinPoint.getArgs()); // 获得目标方法的参数
System. out .println(joinPoint.getTarget()); // 获得目标对象
System. out .println(joinPoint.getStaticPart()); // 获得精确的切点表达式信息
Object result = joinPoint.proceed(); // 执行目标方法
return result; // 返回目标方法返回值
}
Throwable对象
public void afterThrowing(JoinPoint joinPoint,Throwable th){
//获得异常信息
System. out .println( " 异常对象是: " +th+ " 异常信息是: " +th.getMessage());
}
< aop :after-throwing method= "afterThrowing" pointcut-ref= "myPointcut" throwing= "th" />
AOP的另一种配置方式,该方式需要通知类实现Advice的子功能接口
public interface Advice {
}
注解方式AOP配置详解
Advice的子功能接口
例如:通知类实现了前置通知和后置通知接口
public class Advices implements MethodBeforeAdvice, AfterReturningAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System. out .println( "This is before Advice ..." );
}
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws
Throwable {
System. out .println( "This is afterReturn Advice ..." );
}
}
切面使用advisor标签配置
< aop :config >
<!-- 将通知和切点进行结合 -->
< aop :advisor advice-ref ="advices" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
</ aop :config >
又例如:通知类实现了方法拦截器接口
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System. out .println( " 前置逻辑功能 ..." );
//执行目标方法
Object invoke =
methodInvocation.getMethod().invoke(methodInvocation.getThis(),methodInvocation.getArguments());
System. out .println( " 后置逻辑功能 ..." );
return invoke;}
}
切面使用advisor标签配置
< aop :config >
<!-- 将通知和切点进行结合 -->
< aop :advisor advice-ref = “ myMethodInterceptor" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
</ aop :config >
使用aspect和advisor配置区别如下:
<!-- 使用 advisor 配置 -->
< aop :config >
<!-- advice-ref:通知 Bean 的 id -->
< aop :advisor advice-ref ="advices" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
</ aop :config >
<!-- 使用 aspect 配置 -->
< aop :config >
<!-- ref:通知 Bean 的 id -->
< aop :aspect ref ="advices" >
< aop :before method ="before" pointcut ="execution(void
com.itheima.aop.TargetImpl.show())" />
</ aop :aspect >
</ aop :config >
2)通知类的定义要求不同,advisor 需要的通知类需要实现Advice的子功能接口:
public class Advices implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System. out .println( "This is before Advice ..." );
}
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws
Throwable {
System. out .println( "This is afterReturn Advice ..." );
}
}
aspect 不需要通知类实现任何接口,在配置的时候指定哪些方法属于哪种通知类型即可,更加灵活方便:
public class Advices {
public void before() {
System. out .println( "This is before Advice ..." );
}
public void afterReturning() {
System. out .println( "This is afterReturn Advice ..." );
}
}
3)可配置的切面数量不同:
一个advisor只能配置一个固定通知和一个切点表达式;
一个aspect可以配置多个通知和多个切点表达式任意组合,粒度更细。
4)使用场景不同:
1.如果通知类型多、允许随意搭配情况下可以使用aspect进行配置;
2.如果通知类型单一、且通知类中通知方法一次性都会使用到的情况下可以使用advisor进行配置;
3.在通知类型已经固定,不用人为指定通知类型时,可以使用advisor进行配置,例如后面要学习的Spring事务控制的配置;
由于实际开发中,自定义aop功能的配置大多使用aspect的配置方式,所以我们后面主要讲解aspect的配置,advisor是为了后面Spring声明式事务控制做铺垫。
xml方式AOP原理剖析
最终加载的是 AopNamespaceHandler,该Handler的init方法中注册了config标签对应的解析器
this .registerBeanDefinitionParser( "config" , new ConfigBeanDefinitionParser());
以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator进入到Spring容器中,那该类作用是什么呢?看一下集成体系图
AspectJAwareAdvisorAutoProxyCreator 的上上级父类AbstractAutoProxyCreator中的
postProcessAfterInitialization方法
// 参数 bean :为目标对象
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null ) {
Object cacheKey = this .getCacheKey(bean.getClass(), beanName);
if ( this .earlyProxyReferences.remove(cacheKey) != bean) {
//如果需要被增强,则wrapIfNecessary方法最终返回的就是一个 Proxy 对象
return this .wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
通过断点方式观察,当bean是匹配切点表达式时,this.wrapIfNecessary(bean, beanName, cacheKey)返回的是一个JDKDynamicAopProxy
高级软件人才培训专家
2211
ublic class MyAdvice {