声明:本博客仅仅是一个初学者的学习记录、心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流、一起学习、一起进步的朋友可以加我微信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通知

四、前期准备

  1. 创建项目,导入相关jar包,参考Spring——IOC,此外还需导入aop和aspectj的jar包
  2. 创建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/> 不加这个可能会报错,可坑了

  1. 创建UserService这个类,内部有insert方法用来注册用户
public class UserService {
public void insert(){
System.out.println("UserService正在注册用户……");
}
}
  1. 创建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)详解的更多相关文章

  1. Spring面向切面编程(AOP)

    1 spring容器中bean特性 Spring容器的javabean对象默认是单例的. 通过在xml文件中,配置可以使用某些对象为多列. Spring容器中的javabean对象默认是立即加载(立即 ...

  2. Spring面向切面编程(AOP,Aspect Oriented Programming)

    AOP为Aspect Oriented Programming的缩写,意为:面向切面编程(也叫面向方面),可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. ...

  3. Spring面向切面编程(AOP)方式二

    使用注解进行实现:减少xml文件的配置. 1 建立切面类 不需要实现任何特定接口,按照需要自己定义通知. package org.guangsoft.utils; import java.util.D ...

  4. Spring面向切面编程AOP(around)实战

    spring aop的环绕通知around功能强大,我们这里就不细说,直接上代码,看着注释就能明白 需要的可以点击下载源码 1.如果使用注解的方式则需要先创建个注解类 package com.mb.a ...

  5. Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)

    在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...

  6. Spring学习手札(二)面向切面编程AOP

    AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...

  7. Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)

    一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...

  8. Spring框架学习笔记(2)——面向切面编程AOP

    介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...

  9. Spring之控制反转——IoC、面向切面编程——AOP

      控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...

  10. Spring 面向切面编程(AOP)

    Spring 系列教程 Spring 框架介绍 Spring 框架模块 Spring开发环境搭建(Eclipse) 创建一个简单的Spring应用 Spring 控制反转容器(Inversion of ...

随机推荐

  1. 一文看懂ConstraintLayout的用法

    ConstraintLayout 相对于 RelativeLayout来说性能更好,布局上也更加灵活.在最新的Google Android开发文档中是推荐使用 ConstraintLayout的,下面 ...

  2. Spring Context 你真的懂了吗

    今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识. 1. context 是什么 我们经常在编程中见到 contex ...

  3. egret之消除游戏开发

    1.地图 (1)地图形状不同,尺寸不变 (2)背景图变化 2.步数 (1)不同关卡步数不同 (2)步数为01,游戏失败 3.道具 4.消除 (1)>=3可消除 (2)不可消除时,自动打乱 5.数 ...

  4. unity游戏开发_对象池

    现在假设游戏中我们需要实现一个这样功能,按下鼠标左键,发射一颗子弹,3秒之后消失.在这个功能中,我们发射了上百上千发子弹,就需要实例化生成上百上千次.这时候,我们就需要使用对象池这个概念,每次实例化生 ...

  5. Oracle在VMware虚拟机安装的配置

    我是在VMware虚拟机上安装的Oracle , 我只说说我踩过的几个坑吧. VMware的虚拟网络编辑器 仅主机模式相当于在你的主机和虚拟机之间建立了一个局域网,里面只有你的主机和虚拟机 可以通过D ...

  6. Jmeter 之测试片段、Include Controller、模块控制器应用

    一.测试片段是什么? 应用在控制器上的一个特殊线程组,与线程组处于同一层级,必须与Include Controller或模块控制器一起使用才被执行. 二.应用场景 1.当jmeter脚本非常复杂的时候 ...

  7. JDK8的JVM内存结构,元空间替代永久代成为方法区及常量池的变化

    JVM的知识这里总结的很详细:https://github.com/doocs/jvm/blob/master/README.md,因此在本博客也不会再对其中的东西重复总结了. 现在很多文章关于JVM ...

  8. 消费者、生产者Java代码示例,wait

    箱子中的苹果代表资源,现在有消费者从箱子中拿走苹果,生产者往箱子中放苹果.代码如下: 资源--箱子中的苹果: public class Box { int size; int num; public ...

  9. netcore 中的动态代理与RPC实现(微服务专题)

    一.关于RPC的调用 1. 调用者(客户端Client)以本地调用的方式发起调用: 2. Client stub(客户端存根)收到调用后,负责将被调用的方法名.参数等打包编码成特定格式的能进行网络传输 ...

  10. P2766 最长不下降子序列问题 网络流

    link:https://www.luogu.org/problemnew/show/P2766 题意 给定正整数序列x1,...,xn . (1)计算其最长不下降子序列的长度s. (2)计算从给定的 ...