Spring5(五)——AOP
一、AOP
1、介绍
AOP(Aspect Oriented Programming),面向切面编程。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
AOP是对所有对象或者是一类对象编程,核心是在不增加代码的基础上,还增加新功能。实际上在开发框架本身用的多,在实际项目中,用的不多。
切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
通知(增强):切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。连接点(静态)-->切入点(动态)。
引入:为类添加新方法和属性。
目标对象:被通知的对象(被代理的对象)。既可以是你编写的类也可以是第三方类。
代理对象:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
①编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。
②类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。
③运行期:切面在应用系统运行时织入。
原理:AOP的核心思想为设计模式的动态代理模式。
2、案例
注意:接下来介绍的案例,更像是AOP的静态代理实现,个人理解。
定义目标对象(被代理的对象)
1 // 定义一个接口
2 public interface ITeacher {
3 void teach();
4 int add(int i, int j);
5 }
6
7 // 定义目标对象
8 public class Teacher implements ITeacher {
9 @Override
10 public void teach() {
11 System.out.println("老师正在上课");
12 }
13
14 @Override
15 public int add(int i, int j) {
16 int add = i + j;
17 System.out.println("执行目标方法:老师正在做加法,结果为:" + add);
18 // int throwable = 10 / 0; 测试异常通知
19 return add;
20 }
21
22 // 目标对象自己的方法,此方法不是接口所以无法代理
23 public void sayHello() {
24 System.out.println("老师会说hello");
25 }
26
27 }
编写一个通知(前置通知)
1 // 前置通知
2 public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
3
4 // method:被 代理对象 调用的方法(目标方法)
5 // objects:被 代理对象 调用的方法入参(参数)
6 // target:目标对象
7 @Override
8 public void before(Method method, Object[] objects, Object target) throws Throwable {
9 System.out.println("前置通知=====1======函数名:" + method.getName());
10 System.out.println("前置通知=====2======参数值:" + JSON.toJSONString(objects));
11 System.out.println("前置通知=====3======对象值:" + JSON.toJSONString(target));
12 }
13
14 }
配置 application.xml
1 <!-- 配置目标对象 -->
2 <bean id="teacher" class="com.lx.spring.day1.Teacher"/>
3
4 <!-- 配置前置通知 -->
5 <bean id="myMethodBeforeAdvice" class="com.lx.spring.day1.MyMethodBeforeAdvice"/>
6
7 <!-- 配置代理对象 -->
8 <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
9 <!-- 1.指定要代理的目标对象 -->
10 <property name="target" ref="teacher"/>
11
12 <!-- 2.指定要代理的接口集 -->
13 <property name="proxyInterfaces">
14 <list>
15 <value>com.lx.spring.day1.ITeacher</value>
16 </list>
17 </property>
18
19 <!-- 3.指定要织入的通知 -->
20 <property name="interceptorNames">
21 <list>
22 <value>myMethodBeforeAdvice</value>
23 </list>
24 </property>
25 </bean>
1 // 测试类
2 public class Main {
3 public static void main(String[] args) {
4 ApplicationContext app = new ClassPathXmlApplicationContext("app1.xml");
5 // 获取代理对象
6 ITeacher iTeacher = (ITeacher) app.getBean("proxyFactoryBean");
7 // 通过代理对象执行目标对象的方法
8 int add = iTeacher.add(1, 2);
9 }
10 }
11
12 // 前置通知=====1======函数名:add
13 // 前置通知=====2======参数值:[1,2]
14 // 前置通知=====3======对象值:{}
15 // 执行目标方法:老师正在做加法,结果为:3
原理剖析:案例中,重点在于理解ProxyFactoryBean这个Bean到底做了什么?有一个前置通知,在目标方法前调用(从打印结果也能看出)。
那么,用静态代理模式不难理解,简单理解上面的实现如下,详细的请查看源码。
1 // 代理对象,静态代理
2 public class ProxyFactoryBean implements ITeacher { // 2.指定要代理的接口集,即要实现哪些接口
3 // 目标对象,通过接口来聚合
4 private Teacher target;
5 // 前置通知
6 private MethodBeforeAdvice methodBeforeAdvice;
7
8 // 3.指定要织入的通知,即在执行目标对象方法要执行什么代码
9 public void setMethodBeforeAdvice(MethodBeforeAdvice methodBeforeAdvice) {
10 this.methodBeforeAdvice = methodBeforeAdvice;
11 }
12
13 public ProxyFactoryBean(Teacher teacher) { // 1.指定要代理的目标对象
14 this.target = teacher;
15 }
16
17 @Override
18 public void teach() {
19
20 }
21
22 // 代理方法的编写
23 @Override
24 public int add(int i, int j) {
25 try {
26 // 1.如果设置了前置通知,执行前置通知
27 if (methodBeforeAdvice != null) {
28 Method method = ITeacher.class.getMethod("add", int.class, int.class);
29 List<Object> objectList = new ArrayList<>();
30 objectList.add(i);
31 objectList.add(j);
32 Object[] objects = objectList.toArray();
33
34 methodBeforeAdvice.before(method, objects, target);
35 }
36
37 // 2.执行目标方法
38 return target.add(i, j);
39
40 // 3.如果设置了后置通知,执行后置通知
41 } catch (Throwable e) {
42 e.printStackTrace();
43 }
44 return 0;
45 }
46 }
1 // 测试类
2 public class Main {
3 public static void main(String[] args) {
4 // 创建代理对象,同时将创建的目标对象作为参数传递,即为谁代理.
5 ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean(new Teacher());
6 // 设置一个前置通知
7 proxyFactoryBean.setMethodBeforeAdvice(new MyMethodBeforeAdvice());
8 // 通过代理对象执行目标对象的方法.
9 proxyFactoryBean.add(1, 4);
10 }
11 }
12
13 // 前置通知=====1======函数名:add
14 // 前置通知=====2======参数值:[1,3]
15 // 前置通知=====3======对象值:{}
16 // 执行目标方法:老师正在做加法,结果为:4
3、通知类别
除了案例中使用的前置通知,Spring中还提供了如下几种通知:
通知类型
|
接口
|
描述
|
前置通知
|
org.springframework.aop.MethodBeforeAdvice
|
在目标方法前调用
|
后置通知
|
org.springframework.aop.AfterReturningAdvice
|
在目标方法后调用
|
环绕通知
|
org.aopalliance.intercept.MethodInterceptor
|
拦截对目标方法调用
|
异常通知
|
org.springframework.aop.ThrowsAdvice
|
目标方法抛出异常时调用
|
引入通知
|
org.springframework.aop.support.NameMatchMethodPointcutAdvisor
|
可以指定切入点
|
前置通知:接口提供了获得目标方法,参数和目标对象的机会。该接口唯一能阻止目标方法被调用的途径是抛出异常或(System.exit())。
后置通知:同前置通知类似。
环绕通知:该通知能够控制目标方法是否真的被调用。通过invocation.proceed()方法来调用。该通知可以控制返回的对象。可以返回一个与proceed()方法返回对象完全不同的对象。但要谨慎使用。特别注意:配置通知时,指定要织入的通知,环绕顺序不同,会影响执行顺序。
异常通知:该接口为标识性(tag)接口,没有任何方法,但实现该接口的类必须要有如下形式的方法:
void afterThrowing(Throwable throwable);
void afterThrowing(Method m,Object[] os,Object target,Exception e);
第一个方法只接受一个参数:需要抛出的异常。
第二个方法接受异常、被调用的方法、参数以及目标对象
引入通知:以前定义的通知类型是在目标对象的方法被调用的周围织入。引入通知给目标对象添加新的方法和属性。
几种通知:
1 // 前置通知(上面已介绍过)
2 public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
3 // 目标方法、参数、目标对象
4 @Override
5 public void before(Method method, Object[] objects, Object target) throws Throwable {
6 System.out.println("前置通知=====1======函数名:" + method.getName());
7 System.out.println("前置通知=====2======参数值:" + JSON.toJSONString(objects));
8 System.out.println("前置通知=====3======对象值:" + JSON.toJSONString(target));
9 }
10 }
11
12 // 后置通知
13 public class MyAfterReturningAdvice implements AfterReturningAdvice {
14 // object:方法的返回值
15 // method:目标方法
16 // objects:参数
17 // target:目标对象.注:该对象由前置通知传入的 target
18 @Override
19 public void afterReturning(Object object, Method method, Object[] objects, Object target) throws Throwable {
20 System.out.println("后置通知=====0======返回值:" + object);
21 System.out.println("后置通知=====1======函数名:" + method.getName());
22 System.out.println("后置通知=====2======参数值:" + JSON.toJSONString(objects));
23 System.out.println("后置通知=====3======对象值:" + JSON.toJSONString(target));
24 }
25 }
26
27 // 环绕通知
28 public class MyMethodInterceptor implements MethodInterceptor {
29 // object:目标方法的返回值
30 @Override
31 public Object invoke(MethodInvocation invocation) throws Throwable {
32 System.out.println("============环绕前==============");
33 Object object = invocation.proceed(); // 这里会切入目标方法
34 System.out.println("============环绕后==============");
35 return object;
36 }
37 }
38
39 // 异常通知
40 public class MyThrowsAdvice implements ThrowsAdvice {
41
42 public void afterThrowing(Method method, Object[] objects, Object target, Exception e) {
43 System.out.println("异常通知=====1======函数名:" + method.getName());
44 System.out.println("异常通知=====2======参数值:" + JSON.toJSONString(objects));
45 System.out.println("异常通知=====3======对象值:" + JSON.toJSONString(target));
46 System.out.println("异常通知=====4======异常值:" + JSON.toJSONString(e.getMessage()));
47 }
48 }
经过上面案例及四种通知的说明,可以看到,ProxyFactoryBean是一个在BeanFactory中显式创建代理对象的中心类,可以给它一个要代理的目标对象、一个要代理的接口集、一个要织入的通知,他将创建一个崭新的代理对象。
引入通知(切点):前四种通知已经指明了在目标方法前,还是后,还是环绕调用。如果不能表达在什么地方应用通知的话,通知将毫无用处,这就是切入点的用处。
切入点决定了一个特定的类的特定方法是否满足一定的规则。若符合,通知就应用到该方法上。引入通知可以指定切入点(即指定在哪些方法上织入通知)。
注:引入通知并不像前4个介绍的那样是一个通知。而重点思想在于:①在哪里(切点,或者说方法)引入?②引入一个什么样的通知?
规则如下:
符号
|
描述
|
示例
|
匹配
|
不匹配
|
.
|
匹配任何单个字符
|
setFoo.
|
setFooB
|
setFooBar setFooB
|
+
|
匹配前一个字符一次或多次
|
setFoo.+
|
setFooBar setFooB
|
setFoo
|
*
|
匹配前一个字符0次或多次
|
setFoo.*
|
setFoosetFooB, setFooBar
|
|
\
|
匹配任何正则表达式符号
|
\.setFoo.
|
bar.setFoo
|
setFoo
|
下面给出一份完整的配置文件 application.xml
1 <!-- 配置目标对象 -->
2 <bean id="teacher" class="com.lx.test.Teacher"/>
3
4 <!-- 配置前置通知 -->
5 <bean id="myMethodBeforeAdvice" class="com.lx.advice.MyMethodBeforeAdvice"/>
6 <!-- 配置后置通知 -->
7 <bean id="myAfterReturningAdvice" class="com.lx.advice.MyAfterReturningAdvice"/>
8 <!-- 配置环绕通知 -->
9 <bean id="myMethodInterceptor" class="com.lx.advice.MyMethodInterceptor"/>
10 <!-- 配置异常通知 -->
11 <bean id="myThrowsAdvice" class="com.lx.advice.MyThrowsAdvice"/>
12
13 <!-- 配置引入通知 -->
14 <bean id="pointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
15 <property name="advice" ref="myMethodBeforeAdvice"/>
16 <property name="mappedNames">
17 <list>
18 <value>add*</value> <!-- 对以add开头的函数织入前置通知 -->
19 <value>del*</value>
20 <value>teach*</value>
21 </list>
22 </property>
23 </bean>
24
25 <!-- 配置代理对象 -->
26 <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
27 <!-- 指定要代理的目标对象 -->
28 <property name="target" ref="teacher"/>
29
30 <!-- 指定要代理的接口集 -->
31 <property name="proxyInterfaces">
32 <list>
33 <value>com.lx.test.ITeacher</value>
34 </list>
35 </property>
36
37 <!-- 指定要织入的通知 -->
38 <property name="interceptorNames">
39 <list>
40 <value>myMethodBeforeAdvice</value>
41 <value>myAfterReturningAdvice</value>
42 <value>myMethodInterceptor</value>
43 <value>myThrowsAdvice</value>
44
45 <value>pointcutAdvisor</value>
46 </list>
47 </property>
48 </bean>
1 // 测试类
2 public class Main {
3 public static void main(String[] args) {
4 ApplicationContext app = new ClassPathXmlApplicationContext("beans.xml");
5 ITeacher iTeacher = (ITeacher) app.getBean("proxyFactoryBean");
6 int add = iTeacher.add(11, 22);
7 }
8 }
9
10 // 前置通知=====1======函数名:add
11 // 前置通知=====2======参数值:[11,22]
12 // 前置通知=====3======对象值:{}
13 // ============环绕前==============
14 // 前置通知=====1======函数名:add
15 // 前置通知=====2======参数值:[11,22]
16 // 前置通知=====3======对象值:{}
17 // 执行目标方法:老师正在做加法,结果为:33
18 // ============环绕后==============
19 // 后置通知=====0======返回值:33
20 // 后置通知=====1======函数名:add
21 // 后置通知=====2======参数值:[11,22]
22 // 后置通知=====3======对象值:{}
23
24 // row14、15、16是由于配置了引入通知又执行了一次前置通知
4、小结
Spring在运行时通知对象,在运行期创建代理,不需要特殊的编译器。Spring有两种代理方式:
若目标对象实现了若干接口:Spring使用JDK的java.lang.reflect.Proxy类代理。该类让Spring动态产生一个新类,它实现了所需的接口,织入了通知和代理对目标对象的所有请求。
若目标对象没有实现任何接口:Spring使用CGLIB库生成目标对象的子类。使用该方式时需要注意:
①对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
②标记为final的方法不能够被通知。Spring是为目标类产生子类,任何需要被通知的方法都需要被复写,将通知织入。final方法是不允许重写的。
Spring实现了aop联盟接口。Spring只支持方法连接点,不提供属性接入点。Spring的观点是属性拦截破坏了封装。面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。
问:说Spring的aop中,当你通过代理对象去实现aop的时候,获取的ProxyFactoryBean是什么类型?
答:返回的是一个代理对象,如果目标对象实现了接口,则Spring使用jdk动态代理技术;如果目标对象没有实现接口,则Spring使用CGLIB技术。
Spring5(五)——AOP的更多相关文章
- Spring(五)AOP简述
一.AOP简述 AOP全称是:aspect-oriented programming,它是面向切面编号的思想核心, AOP和OOP既面向对象的编程语言,不相冲突,它们是两个相辅相成的设计模式型 AOP ...
- Spring 学习十五 AOP
http://www.hongyanliren.com/2014m12/22797.html 1: 通知(advice): 就是你想要的功能,也就是安全.事物.日子等.先定义好,在想用的地方用一下.包 ...
- Spring学习之旅(五)--AOP
什么是 AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是 OOP(Object-Oriented Programing,面向对象编程)的补充和完善. OO ...
- Java开发学习(十五)----AOP入门案例及其工作流程解析
一.AOP简介 1.1 什么是AOP AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构. OOP(Object Oriented ...
- 惊人!Spring5 AOP 默认使用Cglib ?从现象到源码深度分析
Spring5 AOP 默认使用 Cglib 了?我第一次听到这个说法是在一个微信群里: 真的假的?查阅文档 刚看到这个说法的时候,我是保持怀疑态度的. 大家都知道 Spring5 之前的版本 AOP ...
- Spring AOP With AspectJ
一.AOP和拦截器 某些情况下,AOP和拦截器包括Filter能够实现同样的功能,一般都是请求即controller层的操作,这三个执行顺序为Filter>Interceptor>AOP, ...
- Spring Aop的执行顺序
Spring Aop的执行顺序 首先回忆一下 AOP 的常用注解 @Before:前置通知:目标方法之前执行 @After:后置通知:目标方法之后执行 @AfterReturning:返回后通知:执行 ...
- 20181123_控制反转(IOC)和依赖注入(DI)
一. 控制反转和依赖注入: 控制反转的前提, 是依赖倒置原则, 系统架构时,高层模块不应该依赖于低层模块,二者通过抽象来依赖 (依赖抽象,而不是细节) 如果要想做到控制反转(IOC), 就必须要使 ...
- Spring之旅第五篇-AOP详解
一.什么是AOP? Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如 ...
随机推荐
- 大厂Android岗高频面试问题:说说你对Zygote的理解!
前言 Zygote可以说是Android开发面试很高频的一道问题,但总有小伙伴在回答这道问题总不能让面试满意, 在这你就要搞清楚面试问你对Zygote的理解时,面试官最想听到的和其实想问的应该是哪些? ...
- testlink在win7下的安装\配置\使用
1.xampp >解压并安装xampp >在安装目录下点击setup_xampp(这一步是为了初始化一些环境的配置) >再启动xampp-control,运行Apache和MySQL ...
- 用 getchar putchar 来输入和接收 但是要清空缓冲区
1 //用 getchar putchar 来输入和接收 但是要清空缓冲区 2 3 #include <stdio.h> 4 int main() 5 { 6 char ch1,ch2; ...
- 【LeetCode】860. 柠檬水找零
860. 柠檬水找零 知识点:贪心 题目描述 在柠檬水摊上,每一杯柠檬水的售价为 5 美元. 顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯. 每位顾客只买一杯柠檬水,然后向你付 ...
- Ubuntu上安装gevent
安装libevent; 安装greenlet: sudo easy_install gevent (need net access) bingo! -> python-dev -> lib ...
- Note about Cobertura
Workflow of Unit Test without Cobertura compile source code; compile test code; run unit test; Workf ...
- Linux中的DNS的正解析
目录 一.DNS概述 1.1.DNS定义 1.2.域名结构 1.3.DNS域名解析的方式 1.4.DNS服务器类型 1.5.BIND服务 BIND服务器端程序 二.构建DNS域名正向解析步骤 一.DN ...
- AI+云原生,把卫星遥感虐的死去活来
摘要:遥感影像,作为地球自拍照,能够从更广阔的视角,为人们提供更多维度的辅助信息,来帮助人类感知自然资源.农林水利.交通灾害等多领域信息. 本文分享自华为云社区<AI+云原生,把卫星遥感虐的死去 ...
- 嵌入式Linux可用的防火墙——iptables:实现ip白名单、mac地址白名单
iptables是linux系统下的一个功能强大的模块,不仅可以用作防火墙,还可以实现NAT等众多路由功能.iptables的容器有很清晰的层次关系: 1. iptables是表的容器,iptable ...
- 常见web中间件漏洞(三)Nginx漏洞
nginx是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务,有 开源,内存占用少,并发能力强,自由模块化,支持epoll模型,可限制连接数,支持热部署,简单 ...