Spring——面向切面编程(AOP)详解
声明:本博客仅仅是一个初学者的学习记录、心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流、一起学习、一起进步的朋友可以加我微信Liu__66666666
这是简单学习一遍之后的记录,后期还会修改。
一、问题引入
在日常写项目的时候,肯定少不了要打印日志。例如,要向数据库中insert一个用户,我想在插入前输出一下相关信息,怎么实现呢?最基本的做法是:在insert方法中写日志输出语句。这样写完全能实现功能,但是会不会显得很冗余?耦合度是不是很高?编程的准则是“高内聚,低耦合”,低耦合的意思就是类与类之间的依赖关系尽量少、关联程度尽量小。
而如果在上述情景中使用面向切面编程(AOP),就可以不在insert方法中写日志输出语句却能实现日志输出功能。当然,AOP不止如此。
二、概念引入
1.AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方 式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个 热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑 的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高 了开发的效率。
2.几个基本概念
- 切入点:所有要操作的方法定义,要求业务层方法风格统一
- 分离点:将不可分割的组件单独提取出去定义为单独的操作功能
- 横切关注点:将所有与开发无关的程序组成类单独提取后组织运行
- 织入:将所有切入点、关注点的代码组成在一张完整的程序结构中
3.通知(Advice)
AOP是通过通知来实现功能的,有如下五种:
前置通知(BeforeAdvice)
后置通知(AfterAdvice)
后置返回通知(AfterReturningAdvice)
后置异常通知(AfterThrowingAdvice)
环绕通知(AroundAdvice)
三、Pointcut与Execution表达式
pointcut使用execution表达式表示要被切入的方法(即定义切入点)。
execution表达式,功能类似于正则表达式,都是用来匹配筛选,只不过正则表达式用来筛选字符串,而execution表达式用来筛选要被切入的方法。
execution表达式的格式为:
execution(<注解>? <修饰符>? <返回值类型> <方法名模式>(<参数模式>) <异常>?)) <and args()>?)
例:execution(@Deprecated public Void aop.MyAspect.hello(int,String) throws Exception))')
package aop;
public class AspectDemo {
@Deprecated
public void hello(int i,String s) throws Exception{
}
}
其实不难发现,这个表达式和我们声明的方法的各个部分一一对应
注解:(可省略)例如上面代码中的
@Deprecated
,就是筛选带有该注解的方法修饰符(可省略)
- public
- protected
- private
当然一般用通配符 *
返回值类型
写各种返回值,一般用通配符 *
方法名模式
包名部分:在上例中,AspectDemo是位于aop包中的,所以可以通过包名.包名.类名的格式来定位到某个类,例如aop.AspectDemo 中aop. 就是包名部分;
当然也可以用通配符
- *:匹配任何数量字符,例如service.*.UserService 表示的是service的直接子包
- ..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包,例如service..代表着匹配service及其包含的所有包;而在方法参数模式中匹配任何数量参数。
- +:匹配指定类型的子类型;仅能作为后缀放在类型模式后边,例如java.lang.Number+ 表示的是lang包下Numer的子类
类名部分:在上例中aop.AspectDemo中aop.是包名部分,AspectDemo就是类名部分,可以用通配符来表示,*用的比较多
参数模式
- 写法1:直接按照方法的参数列表写具体类型,上例的方法中参数列表(int i,String s),就可以直接在表达式中写(int,String)
- 写法2:使用通配符:
- “()”表示方法没有任何参数;
- “(..)”表示匹配接受任意个参数的方法
- “(..,java.lang.String)”表示匹配接受java.lang.String类型的参数结束,且其前边可以接受有任意个参数的方法
- “(java.lang.String,..)” 表示匹配接受java.lang.String类型的参数开始,且其后边可以接受任意个参数的方法
- “(*,java.lang.String)” 表示匹配接受java.lang.String类型的参数结束,且其前边接受有一个任意类型参数的方法;
异常模式(可省略)
throws Exception1,Exception2.。。。
传入参数(可省略)
and args(arg-name),一般用于AfterAdvice和Around通知
四、前期准备
- 创建项目,导入相关jar包,参考Spring——IOC,此外还需导入aop和aspectj的jar包
- 创建applicationContext.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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy/>
</beans>
注意:新增了
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/beans/spring-aop.xsd
<aop:aspectj-autoproxy/>
不加这个可能会报错,可坑了
- 创建UserService这个类,内部有insert方法用来注册用户
public class UserService {
public void insert(){
System.out.println("UserService正在注册用户……");
}
}
- 创建MyAspect类
public class MyAspect {}
五、基于XML配置的AOP
1.BeforeAdvice
(1)在MyAspect类中创建方法beforeAdvice
public void beforeAdvice(){
System.out.println("【AOP】Before Advice正在执行……");
}
(2)在applicationContext.xml中配置
关于pointcut和execution表达式见下文
<!--首先要引入myAspect这个bean,备用-->
<bean id="myAspect" class="aop.MyAspect"/>
<bean id="userService" class="aop.UserService"/>
<aop:config>
<!--配置切面,一个aop:aspect标签对应一个Aspect类-->
<aop:aspect id="beforeAdvice" ref="myAspect">
<!--配置通知 method对应MyAspect类中定义的方法,pointcut是切入点表达式用于筛选需要被 切入的方法-->
<aop:before method="beforeAdvice" pointcut="execution(* aop..*.*(..)))"/>
</aop:aspect>
</aop:config>
(3)编写测试类
public class UserServiceTest {
public static void main(String[] args) {
ApplicationContext context=
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.insert();
}
}
(4)输出结果
【AOP】Before Advice正在执行……
UserService正在注册用户……
可以发现,BeforeAdvice就已经实现了
2.AfterAdvice(相当于异常里面的finally语句)
(1)UserService类同上
(2)在MyAspect中创建方法afterAdvice
public void afterAdvice(){
System.out.println("【AOP】after Advice…… 不管怎样我都会执行");
}
(3)修改applicationContext.xml
<aop:config>
<aop:aspect id="beforeAdvice" ref="myAspect">
<aop:after method="afterAdvice"
pointcut="execution(* aop.*.insert(..)))" />
</aop:aspect>
</aop:config>
(4)编写测试类(同上)
(5)输出结果
UserService正在注册用户……
【AOP】after Advice…… 不管怎样我都会执行
3.AfterReturningAdvice
(1)修改UserService的insert方法,使其有返回值
public class UserService {
public int insert(){
System.out.println("UserService正在注册用户……");
return 1;
}
}
(2)在MyAspect中新增afterReturningAdvice方法
public void afterReturningAdvice(int result) {
System.out.println("【AOP】after advice……返回值为"+result);
}
(3)在applicationContext.xml中配置
<aop:config>
<aop:aspect id="beforeAdvice" ref="myAspect">
<aop:after-returning method="afterReturningAdvice"
pointcut="execution(* aop.*.insert(..)))" returning="result"/>
</aop:aspect>
</aop:config>
注意:这里这个returning="result"与MyAspect类中对应方法的参数名必须保持一致,本例中都为result
(4)编写测试类(代码同1.)
(5)输出结果
UserService正在注册用户……
【AOP】after advice……返回值为1
4.AfterThrowingAdvice
(1)修改UserService使其抛异常
public int insert() throws Exception {
try {
System.out.println("UserService开始注册用户……");
int i=1/0;
}catch (Exception e){
throw new Exception("insert方法遇到异常……");
}
return 1;
}
(2)在MyAspect中新增方法 afterThrowingAdvice
//这里传入的这个Exception就是捕获到的异常对象
public void afterThrowingAdvice(Exception e){
System.out.println("【AOP】得到异常信息:"+e.getMessage());
}
(3)修改applicationContext.xml
<aop:config>
<aop:aspect id="beforeAdvice" ref="myAspect">
<aop:after-throwing method="afterThrowingAdvice"
pointcut="execution(* aop.*.insert(..)))" throwing="e"/>
</aop:aspect>
</aop:config>
注意:这里的throwing="e”就是跑出的异常对象的名字,要与MyAspect中afterThrowingAdvice方法中传入的参数Exception e的名字保持一致。
(4)编写测试类
public static void main(String[] args) throws Exception {
ApplicationContext context=
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.insert();
}
(5)输出结果
UserService开始注册用户……
【AOP】得到异常信息insert方法遇到异常……
Exception in thread "main" java.lang.Exception: insert方法遇到异常……
at aop.UserService.insert(UserService.java:12)
at aop.UserService$$FastClassBySpringCGLIB$$7e3b8e5e.invoke(<generated>)
...
5.AroundAdvice
(1)修改UserService中的insert方法
public int insert(int arg) throws Exception {
try {
int i = 1 / 0;
} catch (Exception e) {
throw new Exception("insert方法遇到异常……");
}
return 1;
}
(2)在MyAspect中添加方法AroundAdvice
//这里这个ProceedingJointPoint可以理解为切入点对象,可以通过它获取切入点(被切入的方法)的参数、返回值、抛出的异常,并且可以通过pjp.proceed(args);为该切入点设置参数
public int aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println("【AOP】before Advice,获取到insert方法传入的参数为:"+args[0]);
Object result;
try {
result=pjp.proceed(args);//这里是我们手动执行切入点,并传入参数
System.out.println("【AOP】after Returning Advice,返回值为:"+result);
}catch (Exception e){
//这里捕获的就是切入点运行时抛出的异常
System.out.println("【AOP】after Throwing Advice,错误信息为:"+e.getMessage());
}
System.out.println("【AOP】after advice……不管异常不异常我都执行");
//这个就跟着这样写吧。。如果不写返回值的话会报 null return value does not match...
return 1;
}
(3)修改applicationContext.xml文件
<aop:config>
<aop:aspect id="beforeAdvice" ref="myAspect">
<aop:around method="aroundAdvice"
pointcut="execution(* aop.*.insert(..)))" />
</aop:aspect>
</aop:config>
(4)编写测试类
public static void main(String[] args) throws Exception {
ApplicationContext context=
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.insert(2);
}
(5)输出结果
【AOP】before Advice,获取到insert方法传入的参数为:2
【AOP】after Throwing Advice,错误信息为:insert方法遇到异常……
【AOP】after advice……不管异常不异常我都执行
六、基于注解配置的AOP
先把项目状态恢复到 “四、前期准备”的状态,然后在applicationContext.xml中添加下面的语句开启注解和包扫描。
<context:annotation-config/>
<context:component-scan base-package="aop"/>
注意,这个base-package可以配置多个包,以半角(英文)逗号隔开,例如“aop,mvc,dao,service”,当然,为了省事,可以直接配一个顶级包,他会自动遍历扫描所有的子包及子包的子包等等。
然后为MyAspect类和UserService类加上注解
@Component
@Aspect
public class MyAspect {}
@Service
public class UserService {}
1.BeforeAdvice
(1)在MyAspect类中创建beforeAdvice方法,并写好注解
@Before(value = "execution(* aop..*.*(..)))")
public void beforeAdvice(){
System.out.println("【AOP】Before Advice正在执行……");
}
不需要配任何bean,是不是很爽
(2)编写测试类
public static void main(String[] args) {
ApplicationContext context=
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.insert();
}
(3)输出结果
【AOP】Before Advice正在执行……
UserService正在注册用户……
2.AfterService(我就不写测试了)
(1)在MyAspect类中创建afterAdvice方法,并写好注解
@After(value = "execution(* aop..*.*(..)))")
public void afterAdvice(){
System.out.println("【AOP】after Advice正在执行……");
}
3.AfterReturningAdvice
(1)修改UserService中的insert方法
public int insert(){
System.out.println("UserService正在注册用户……");
return 1;
}
(2)在MyAspect类中创建afterReturningAdvice方法,并写好注解
@AfterReturning(value = "execution(* aop..*.*(..))&& args(result))")
public void afterReturningAdvice(int result){
System.out.println("【AOP】after Returning Advice正在执行……返回值为:"+result);
}
(3)不写测试了
4.AfterThrowingAdvice
(1)修改UserService中的insert方法
public int insert() throws Exception {
try {
int i=1/0;
}catch (Exception e){
throw new Exception("【UserService】的insert遇到了错误……");
}
return 1;
}
(2)在MyAspect类中创建afterThrowingAdvice方法,并写好注解
@AfterThrowing(value = "execution(* aop..*.*(..)))",throwing = "e")
public void afterThrowingAdvice(Exception e){
System.out.println("【AOP】after Throwing Advice正在执行……错误信息为:"+e.getMessage());
}
(3)不测试了。。
5.AroundAdvice
(1)把MyAspect中之前写的方法注释掉,不然会影响观察结果
(2)修改insert方法
public int insert(int arg) throws Exception {
try {
int i=1/0;
}catch (Exception e){
throw new Exception("【UserService】的insert遇到了错误……");
}
return 1;
}
(3)在MyAspect类中创建aroundAdvice方法,并写好注解
@Around(value = "execution(* aop..*.*(..)))")
public int aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println("【AOP】before Advice,获取到insert方法传入的参数为:"+args[0]);
Object result;
try {
result=pjp.proceed(args);
System.out.println("【AOP】after Returning Advice,返回值为:"+result);
}catch (Exception e){
System.out.println("【AOP】after Throwing Advice,错误信息为:"+e.getMessage());
}
System.out.println("【AOP】after advice……不管异常不异常我都执行");
return 1;
}
Spring——面向切面编程(AOP)详解的更多相关文章
- Spring面向切面编程(AOP)
1 spring容器中bean特性 Spring容器的javabean对象默认是单例的. 通过在xml文件中,配置可以使用某些对象为多列. Spring容器中的javabean对象默认是立即加载(立即 ...
- Spring面向切面编程(AOP,Aspect Oriented Programming)
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...
- Spring面向切面编程(AOP)方式二
使用注解进行实现:减少xml文件的配置. 1 建立切面类 不需要实现任何特定接口,按照需要自己定义通知. package org.guangsoft.utils; import java.util.D ...
- Spring面向切面编程AOP(around)实战
spring aop的环绕通知around功能强大,我们这里就不细说,直接上代码,看着注释就能明白 需要的可以点击下载源码 1.如果使用注解的方式则需要先创建个注解类 package com.mb.a ...
- Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)
在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...
- Spring学习手札(二)面向切面编程AOP
AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...
- Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)
一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...
- Spring框架学习笔记(2)——面向切面编程AOP
介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...
- Spring之控制反转——IoC、面向切面编程——AOP
控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...
- Spring 面向切面编程(AOP)
Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...
随机推荐
- C# ModBus 读取数据
简单介绍: 项目上需要与多家公司做接口对接.我们提供接口的有,其他公司提供的接口也有.所有的接口全部对接完了,遇到一个非常棘手的问题,需要获取甲方船厂设备上的状态,就给了一个文档,文档上写了IP.端口 ...
- 【数据结构】9.java源码关于HashTable
1.hashtable的内部结构 基础存储数据的hash桶由Entry结构的数组存放而entry数据结构,有hash,key和value,还有一个指向下一个节点的引用next对象 这里就和hashma ...
- Scala 系列(二)—— 基本数据类型和运算符
一.数据类型 1.1 类型支持 Scala 拥有下表所示的数据类型,其中 Byte.Short.Int.Long 和 Char 类型统称为整数类型,整数类型加上 Float 和 Double 统称为数 ...
- 装饰器修复技术@wraps
@wrap修复技术 首先我先说一下wrap的效果 如果没使用@wraps,当A调用了装饰器B的话,即使A.name,返回的会是装饰器B的函数名称,而不是A的函数名称如果使用了@wraps,当A调用了装 ...
- CocosBuilder 学习笔记(1) CCBReader 解析.ccbi文件流程
1. 简介 CocosBuilder是免费开源的Cocos2d UI编辑器. .ccb文件是CCB项目的原始文件. .ccbi文件是CCB项目发布后的生成的二进制文件.CCBReader可以快速通过该 ...
- abp(net core)+easyui+efcore实现仓储管理系统——菜单 (十六)
系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+easyui+efcore实现仓储管理系统——解决方案介绍(二) ...
- Java跨平台实现原理
跨平台:一次编译,处处运行. 实现原理:Java源代码经过编译,生成字节码文件,交由Java虚拟机来执行,不同得系统有不同得JVM,借助JVM实现跨平台.
- Linux配置使用SSH Key登录并禁用root密码登录
Linux系统大多数都支持OpenSSH,生成公钥.私钥的最好用ssh-keygen命令,如果用putty自带的PUTTYGEN.EXE生成会不兼容OpenSSH,从而会导致登录时出现server r ...
- python查找时,不支持compound class
1.python使用如下代码查找页面上的元素时, browser.findElement_by_class_name("widget-content nopadding") 报错: ...
- CF 462 C. A Twisty Movement 分段想 线段树 或 dp
题意 有一个只包含1和2的序列,试翻转一个区间,使得结果中非连续非递减数列最长. 思路 一. 作出1的前缀计数和为cnt1,2的后缀计数和为cnt2, 由于要找出[1,1,1][2,2,2][1,1, ...