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

    关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...

  2. 目标检测YOLO进化史之yolov1

    yolov3在目标检测领域可以算得上是state-of-art级别的了,在实时性和准确性上都有很好的保证.yolo也不是一开始就达到了这么好的效果,本身也是经历了不断地演进的. yolov1 测试图片 ...

  3. Django之ORM-model模型关系

    模型类关系 1)一对多关系例:图书类-英雄类 models.ForeignKey() 定义在多的类中. 2)多对多关系例:新闻类-新闻类型类 体育新闻 国际新闻models.ManyToManyFie ...

  4. vector function trmplate

    /* vector function template programmer:qpz */ #include <iostream> #include <vector> #def ...

  5. OSG与Shader的结合使用

    目录 1. 概述 2. 固定管线着色 3. 纹理着色 4. 参考 1. 概述 以往在OpenGL中学习渲染管线的时候,是依次按照申请数据.传送缓冲区.顶点着色器.片元着色器这几个步骤编程的.OSG是O ...

  6. 使用jQuery.extend创建一个简单的选项卡插件

    选项卡样式如图,请忽略丑陋的样式,样式可以随意更改 主要是基于jquery的extend扩展出的一个简单的选项卡插件,注意:这里封装的类使用的是es6中的class,所以不兼容ie8等低版本浏览器呦! ...

  7. JAVA解除tomcat 对浏览器特别字符 | () {} [] 的限制

    1.打开tomcat本机地址打开conf文件夹(一定要关闭Tomcat启动在修改) 2.记事本打开或者编辑软件打开(我这里推荐的编辑软件是以下如图) 打开进去加入这两行代码 3打开server.xml ...

  8. 你真的了解MyBatis中${}和#{}的区别吗?

    动态sql是mybatis的主要特性之一.在mapper中定义的参数传到xml中之后,在查询之前mybatis会对其进行动态解析. mybatis提供了两种支持动态sql的语法:#{} 和 ${}. ...

  9. HDU - 4305 - Lightning 生成树计数 + 叉积判断三点共线

    HDU - 4305 题意: 比较裸的一道生成树计数问题,构造Krichhoof矩阵,求解行列式即可.但是这道题还有一个限制,就是给定的坐标中,两点连线中不能有其他的点,否则这两点就不能连接.枚举点, ...

  10. CF - 652 D Nested Segments

    题目传送门 题解: 可以将所有线段按照左端点优先小,其次右端点优先大进行排序. 然后对于第 i 条线段来说, 那么第 i+1 ---- n 的线段左端点都一定在第i条线段的右边, 接下来就需要知道 i ...