AOP 简介
AOP的概念
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程OOP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面,而用这种思维去设计编程的方式叫做面向切面编程
![](https://i-blog.csdnimg.cn/direct/dc377976820948d9921203f6b000f3e5.png)
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相关概念
![](https://i-blog.csdnimg.cn/direct/8b12ee2b8f224f32807e735381085cc1.png)
![](https://i-blog.csdnimg.cn/direct/90ad93177ca7446db39c2e4832fc972d.png)
基于xml配置的AOP
xml方式AOP快速入门
前面我们自己编写的AOP基础代码还是存在一些问题的,主要如下:
被增强的包名在代码写死了
通知对象的方法在代码中写死了
![](https://i-blog.csdnimg.cn/direct/0635e0f1ade34fe0a9785035b17c2d68.png)
通过配置文件的方式去解决上述问题
配置哪些包、哪些类、哪些方法需要被增强
配置目标方法要被哪些通知方法所增强,在目标方法执行之前还是之后执行增强
配置方式的设计、配置文件(注解)的解析工作,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的包了,所以就不用额外导入了
![](https://i-blog.csdnimg.cn/direct/08fafc4cd1ca44e8a7f48497e9642bf8.png)
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的通知由以下五种类型
![](https://i-blog.csdnimg.cn/direct/0137ce0f0ff74e229d047c060d02e40c.png)
环绕通知
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可以为其传递一些必要的参数
![](https://i-blog.csdnimg.cn/direct/27d7778d37fa4be9883c36380a8b76c6.png)
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的子功能接口
![](https://i-blog.csdnimg.cn/direct/d00d2f65f0f44c168a3c758a3a1e7312.png)
例如:通知类实现了前置通知和后置通知接口
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原理剖析
通过xml方式配置AOP时,我们引入了AOP的命名空间,根据讲解的,要去找spring-aop包下的META-INF,在去找spring.handlers文件
![](https://i-blog.csdnimg.cn/direct/7d2c4a75e1044918ba98f5b1db48cf21.png)
http\://www.springframework.org/schema/aop
=
org.springframework.aop.config.AopNamespaceHandler
最终加载的是 AopNamespaceHandler,该Handler的init方法中注册了config标签对应的解析器
this
.registerBeanDefinitionParser(
"config"
,
new
ConfigBeanDefinitionParser());
以ConfigBeanDefinitionParser作为入口进行源码剖析,最终会注册一个AspectJAwareAdvisorAutoProxyCreator进入到Spring容器中,那该类作用是什么呢?看一下集成体系图
![](https://i-blog.csdnimg.cn/direct/38c28705c3d8448ebef26026cbe2a017.png)
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
![](https://i-blog.csdnimg.cn/direct/b50a552976c447b899de8fcf775f0400.png)
高级软件人才培训专家
2211
ublic class
MyAdvice {