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

    Python官网:https://www.python.org/ 下载指定版本的软件包: [root@General data]# yum clean all  &&  yum rep ...

  2. git 常规业务场景 使用

    一般每个开发者都会有个自己的分支,有个test分支,合并代码用,两条分支相互备份,就算merge的时候被覆盖,也不用担心 建立自己的分支 // 创建本地分支, git checkout -b dev_ ...

  3. react native 8081 端口号被占

    McAfee Agent 占用8081 端口号 公司电脑被标装,McAfee Agent关不掉 sudo lsof -n -i4TCP:8081 sudo launchctl list | grep ...

  4. 池化层的back proporgation 原理

    转载:https://www.jianshu.com/p/6928203bf75b

  5. Spark应用场景以及与hadoop的比较

    一.大数据的四大特征: a.海量的数据规模(volume) b.快速的数据流转和动态的数据体系(velocity) c.多样的数据类型(variety) d.巨大的数据价值(value) 二.Spar ...

  6. DES加解密工具类

    这两天在跟友商对接接口,在对外暴露接口的时候,因为友商不需要登录即可访问对于系统来说存在安全隐患,所以需要友商在调用接口的时候需要将数据加密,系统解密验证后才执行业务.所有的加密方式并不是万能的,只是 ...

  7. Excel VBA 在保留原单元格数据的情况下,将计算的百分比加在后面

    算的是红框占绿框的百分比 难点在保留原数据的情况下,把百分比加在后面.通过公式我是不会,但程序实现也不难. 先在Excel中的开发工具中打开visual basic,或者用宏也可以 导入代码文件,代码 ...

  8. Javaweb之国际化

    Javaweb之国际化 一.前言 软件的本地化:一个软件在某个国家或地区使用时,采用该国家或地区的语言,数字,货币,日期等习惯. 软件的国际化:软件开发时,让它能支持多个国家和地区的本地化应用.使得应 ...

  9. Spring框架之事务管理

    Spring框架之事务管理 一.事务的作用 将若干的数据库操作作为一个整体控制,一起成功或一起失败. 原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 一致性:指事务前后 ...

  10. Setup Factory 9 简单打包

    由于项目资源太大,使用VS自带打包工具无法实现需求,所以Setup Factory 9进行打包生成多个文件的方案,下面记录使用方法: 一:这里点击下载:下载,提取码:tt7a 二:下载完安装需要注册码 ...