Spring框架学习笔记(2)——面向切面编程AOP
介绍
概念
面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP。
假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会将此方法封装在一个类中,之后通过对象.方法名
调用
我们可以看作我们的业务代码被其他代码入侵或者是业务代码被其他与业务不相关的代码入侵了
这个时候,如果我们使用AOP进行编写代码,我们的业务代码就可以不需要写其他与业务相关的代码,这样就可以保证业务代码的纯洁性
AOP运行流程
通过配置文件,给各个业务方法标识切入点(PointCut),即切入点方法。
之后当程序运行到切入点方法的时候,就会发出一个通知(Advice),去通知执行某个切面方法(Aspect)
专业术语
项 | 描述 |
---|---|
Aspect | 一个模块具有一组提供横切需求的 APIs。例如,一个日志模块为了记录日志将被 AOP 方面调用。应用程序可以拥有任意数量的方面,这取决于需求。 |
Join point | 在你的应用程序中它代表一个点,你可以在插件 AOP 方面。你也能说,它是在实际的应用程序中,其中一个操作将使用 Spring AOP 框架。 |
Advice | 这是实际行动之前或之后执行的方法。这是在程序执行期间通过 Spring AOP 框架实际被调用的代码。 |
Pointcut | 这是一组一个或多个连接点,通知应该被执行。你可以使用表达式或模式指定切入点正如我们将在 AOP 的例子中看到的。 |
Introduction | 引用允许你添加新方法或属性到现有的类中。 |
Target object | 被一个或者多个方面所通知的对象,这个对象永远是一个被代理对象。也称为被通知对象。 |
Weaving | Weaving 把方面连接到其它的应用程序类型或者对象上,并创建一个被通知的对象。这些可以在编译时,类加载时和运行时完成。 |
Advice通知
通知 | 类型 |
---|---|
前置通知(Before Advice) | 在切入点方法执行之前,执行通知 |
环绕通知(Around Advice) | 在切入点方法执行的整个过程都可以执行通知 |
后置通知(After Returning Advice) | 在切入点方法执行之后,只有在方法成功执行时,才能执行通知。 |
最终通知(After Finally Advices) | 在一个方法执行之后,不管是方法是否成功执行 ,执行通知 |
异常通知(After Throwing Advice) | 在一个方法执行之后,只有在方法退出抛出异常时,才能执行通知。 |
PS:其实,这些通知就是相当于你可以在业务方法的执行前(前置通知)、执行中(环绕通知)、执行成功之后(后置通知)、发生异常(异常通知)、不管方法是发生异常还是执行成功(最终通知),执行某些与业务功能无关的功能代码。
这样就可以降低业务功能代码的入侵和污染
下面使用两种不同的方式来实现一个方法日志打印的简单例子
后置通知例子
下面的通知是基于xml配置的
1.添加依赖
除了之前的spring的jar包,还需要两个jar包,aopalliance.jar
和aspectjweaver.jar
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>RELEASE</version>
</dependency>
<!-- aop需要的jar -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>RELEASE</version>
</dependency>
2.业务代码
我编写了一个TeacherDao类,里面只有add和delete方法
package com.wan;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:34
* @description
*/
public class TeacherDao {
public void add(Teacher teacher) {
System.out.println("往数据库中插入一条数据");
}
public void delete(Teacher teacher) {
System.out.println("从数据库中删除一条数据");
}
}
3.编写MyLogging.java
前面说过了通知具有五种类型,我们根据需要,选择合适的通知类型,让某个类实现通知对应的接口,这里其实就是相当于编写切面方法
通知类型 | 接口 | 接口方法 | 接口方法参数说明 |
---|---|---|---|
前置通知 | org.springframework.aop.MethodBeforeAdvice | before(Method method, Object[] args, Object target) | method是方法,args是方法的参数,target是目标对象 |
环绕通知 | org.aopalliance.intercept.MethodInterceptor | invoke(MethodInvocation invocation) | invocation对象中包含有method,方法参数和目标对象 |
后置通知 | org.springframework.aop.AfterReturningAdvice | afterReturning(Object returnValue, Method method, Object[] args, Object target) | returnValue是方法的返回值,其他的参数和前置通知一样 |
最终通知 | org.springframework.aop.AfterAdvice | 无 | 无 |
异常通知 | org.springframework.aop.ThrowsAdvice | 无 | 无 |
我们日志输出,选择后置通知,也就是方法执行完成之后调用
MyLogging.java
package com.wan;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
public class MyLogging implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
String methodName = method.getName();//方法名
int size = args.length;//参数个数
System.out.println("调用了"+target+"的"+methodName+"方法,该方法的参数个数有"+size+"个");
}
}
4.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="mylog" class="com.wan.MyLogging"/>
<bean id="teacherdao" class="com.wan.TeacherDao"/>
<aop:config>
<aop:pointcut id="mypointcut" expression="execution(public void add(com.wan.Teacher))"/>
<aop:advisor advice-ref="mylog" pointcut-ref="mypointcut"/>
</aop:config>
</beans>
这里和之前一样,也需要引用aop
命名空间,IDEA可以智能帮我们导入,输入<aop:
,之后就会弹出提示
各元素和属性说明:
子元素/属性 | 含义 |
---|---|
aop:pointcut | 切入点,当执行当切入点方法的时候,就会根据通知(Advice)的类型,从而执行非业务功能的代码 |
id | 切入点的唯一表示,下面pointcut-ref属性需要引用此id |
expression | 表达式,只要是符合此表达式的方法,都会被当作切入点 |
aop:advisor | 通知 |
pointcut-ref | 引用切入点的id |
advice-ref | 引用切入点接口类的bean的id |
补充,关于expression的例子:
例子 | 说明 |
---|---|
public boolean addTeacher(com.wan.Teacher) | 所有返回类型为boolean,参数类型为com.wan.Teacher,方法名为addTeacher的方法 |
public void com.wan.TeacherDao.add(com.wan.Teacher) | 方法存在TeacherDao类中,返回类型为空,参数类型为Teacher,方法名为add的方法 |
public * addTeacher(com.wan.Teacher) | 所有返回类型为任意类型,参数类型为com.wan.Teacher,方法名为addTeacher的方法 |
public boolean *(com.wan.Teacher) | 所有返回类型为任意类型,参数类型为Teacher,方法名任意的方法 |
public boolean addTeacher(..) | 所有返回类型为任意类型,参数类型和个数不限,方法名为addTeacher的方法 |
* com.wan.*.*(..) | 在com.wan包下面的所有方法(不包括子包) |
* com.wan..*.*(..) | 在com.wan包下面的所有方法(包括子包) |
表达式要写在execution()的括号里面,多个条件可以使用or连接
5.测试
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
TeacherDao teacherdao = (TeacherDao) context.getBean("teacherdao");
teacherdao.add(new Teacher());
其他类型通知Advice使用
前置通知
前置通知和后置通知一样,也是实现对应的接口,然后重写before方法,这里就不过多说明了
异常通知
异常通知有点特殊,因为此接口是不需要重写方法的,但是,我们想要实现异常通知,得按照它定义的规则来
afterThrowing([Method method,Object[] args,Object target],Throwable ex)
- 方法名必须是afterThrowing
- 参数列表中的最后一个参数必须存在,可以是Throwable或者Throwable的子类
- 方法列表的前三个参数要么都存在,要么一个都不存在
环绕通知
此通知是spring的最强扩展,因为环绕通知可以拦截方法,对方法的传入参数的数值、返回值进行更改,或者是决定方法是否执行,也可以对目标进行异常处理。
如果对破解有所了解的话,环绕通知还可以被称为hook,像Android的Xposed框架就是通过hook原理,来达到自由更改系统目的。
实现MethodInterceptor接口,重写其的invoke方法
invoke方法可以获得像之前的前置通知的三个参数,method
,target
,args
,也可以获得返回值returnValue
package com.wan;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
public class MyLogging implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object target = invoke.getThis();
Method method = invoke.getMethod();
Object[] args = invoke.getArguments();
//执行方法,获得返回值
Object returnValue = invoke.proceed();
}
}
基于注解配置使用
上面说的几个例子都是基于xml配置文件,我们可以使用注解,从而达到简化的目的
注解 | 说明 |
---|---|
@Aspect | 标注切入点 |
@Before | 标注前置通知 |
@Around | 标注环绕通知 |
@AfterReturning | 标注后置通知 |
@After | 标注最终通知 |
@AfterThrowing | 标注异常通知 |
步骤
1. 导入相关jar(之前导入的那两个jar包)
2. 使用注解,标注类和方法
3. xml中开启配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="teacherdao" class="com.wan.TeacherDao"/>
<aop:aspectj-autoproxy/>
<bean class="com.wan.MyLogging"/>
</beans>
之后的测试代码和之前的一样
前置通知
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@Before("execution(public void add(com.wan.Teacher))")
public void sayHello() {
System.out.println("这是前置通知");
}
}
注解使用挺简单的,大概看一下示例代码就能知道怎么使用了
获得三个参数target、args、method
AOP中有个JoinPoint的接口,此接口可以获得target
、args
、method
这三个参数
方法名 | 说明 |
---|---|
getTarget() | 获得目标对象 |
getSignature() | 获得目标方法的Signature对象,由此对象的getName可以获得方法名 |
getArgs() | 获得参数列表 |
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@AfterReturning(pointcut="execution(public boolean add(com.wan.Teacher))",returning="returnValue")
public void test(JoinPoint jp,Object returnValue) {
//上面的注解的returning属性把方法的返回值赋值给了参数returnValue
}
}
环绕通知
环绕通知有个特殊的接口ProceedingJoinPoint
,此接口是JoinPoint
的子接口,比JoinPoint接口多了一个proceed方法,用于执行目的对象的方法获得返回值
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@Around("execution(public boolean add(com.wan.Teacher))")
public void test(ProceedingJoinPoint jp) {
Object returnValue = jp.proceed();
}
}
异常通知
package com.wan;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @author StarsOne
* @date Create in 2019/9/25 0025 16:53
* @description
*/
@Aspect
public class MyLogging {
@AfterThorwing(pointcut="execution(public boolean add(com.wan.Teacher))",throwing="e")
public void test(JoinPoint jp,NullPointException e) {
//上面的注解的throwing属性把异常赋值给了参数e
//参数中指定了异常为空指针异常,所有,发生异常为空指针异常时候,异常通知才会调用此方法
}
}
PS:除以上两种方式可以实现AOP,还有一种使用Schema进行配置,我看了一下步骤,觉得比上面两种还要繁琐,在这里就补充了
Spring框架学习笔记(2)——面向切面编程AOP的更多相关文章
- Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)
一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...
- spring框架(2)— 面相切面编程AOP
spring框架(2)— 面相切面编程AOP AOP(Aspect Oriented Programming),即面向切面编程. 可以说是OOP(Object Oriented Programming ...
- ASP.NET MVC 学习笔记之面向切面编程与过滤器
AOP(面向切面)是一种架构思想,用于把公共的逻辑放到一个单独的地方,这样就不用每个地方都写重复的代码了.比如程序中发生异常,不用每个地方都try…catch 只要在Golbal的Applicatio ...
- Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)
在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...
- 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制
spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...
- Spring学习手札(二)面向切面编程AOP
AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...
- Spring框架学习笔记(1)
Spring 框架学习笔记(1) 一.简介 Rod Johnson(spring之父) Spring是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架, ...
- Spring之控制反转——IoC、面向切面编程——AOP
控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...
- Spring框架学习笔记(5)——Spring Boot创建与使用
Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...
随机推荐
- Hibernate对象状态之间的神奇转换
状态分类 在Hibernate框架中,为了管理持久化类,Hibernate将其分为了三个状态: 瞬时态(Transient Object) 持久态(Persistent Object) 脱管态(Det ...
- Windows10下载mysql详解
mysql版本分为企业版(Enterprise)和社区版(Community),其中社区办是通过GPL协议授权的开源软件,可以免费使用,而企业版是需要收费的商业软件. mysql官网 https:// ...
- Liunx学习总结(八)--服务
什么是服务 服务是向外提供服务的进程,一般来说都会放在后台,既然要持续不断的提供外界随时发来的服务请求,服务进程就需要常驻在内存中,且不应该和终端有关,否则终端退出服务程序就退出了.另外,要能够接待外 ...
- stringbuffer与stringbuilder区别分析
它们到底都有什么区别呢! 三者都是用来对字符串进行操作,String通常用来定义一个变量,而StringBuilder StringBuffer则通常用来对字符串进行拼接等操作.但其实String同样 ...
- Egret白鹭开发微信小游戏(使用皮肤搭建UI,代码调用组件功能)
(1)新建皮肤,并命名如下 (2)根据实际情况自定义皮肤,例如: (3)修改名字为如下: (4)运行游戏会自动生成以下代码: (5)在default.thm.json中添加如下代码:(具体路径名字根据 ...
- UnityScript基础
基本格式 1 cc.Class({ 2 extends: cc.Component, 3 4 properties: { 5 }, 6 7 // use this for initialization ...
- Python——常用模块(time/datetime, random, os, shutil, json/pickcle, collections, hashlib/hmac, contextlib)
1.time/datetime 这两个模块是与时间相关的模块,Python中通常用三种方式表示时间: #时间戳(timestamp):表示的是从1970年1月1日00:00:00开始按秒计算的偏移量. ...
- 01-python3基础-基本数据类型
Python3 基本数据类型 标准数据类型 Python3 中有六个标准的数据类型: Number(数字) String(字符串) List(列表) Tuple(元组) Set(集合) Diction ...
- 互联网从此没有 BAT
根据 Wind 数据截止2019年8月30日,中国十大互联网上市公司排名中,百度排名第 6 位市值 365 亿美元,阿里巴巴排名第一市值高达 4499 亿美元,腾讯排名第二市值 3951 亿美元. 1 ...
- 牛客暑假多校第五场 D inv
题意:给你一个n, 接来下给你一个 [1,n] 中偶数的排列, 还有一个 [1, n] 中 奇数 按照递增的顺序排列, 现在求一个原数列, 使得偶数列排序 和 奇数列 都是原数列的一个子序列, 现在求 ...