介绍

概念

面向切面编程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)
  1. 方法名必须是afterThrowing
  2. 参数列表中的最后一个参数必须存在,可以是Throwable或者Throwable的子类
  3. 方法列表的前三个参数要么都存在,要么一个都不存在

环绕通知

此通知是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的更多相关文章

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

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

  2. spring框架(2)— 面相切面编程AOP

    spring框架(2)— 面相切面编程AOP AOP(Aspect Oriented Programming),即面向切面编程. 可以说是OOP(Object Oriented Programming ...

  3. ASP.NET MVC 学习笔记之面向切面编程与过滤器

    AOP(面向切面)是一种架构思想,用于把公共的逻辑放到一个单独的地方,这样就不用每个地方都写重复的代码了.比如程序中发生异常,不用每个地方都try…catch 只要在Golbal的Applicatio ...

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

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

  5. 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 ...

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

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

  7. Spring框架学习笔记(1)

    Spring 框架学习笔记(1) 一.简介 Rod Johnson(spring之父) Spring是分层的Java SE/EE应用 full-stack(服务端的全栈)轻量级(跟EJB比)开源框架, ...

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

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

  9. Spring框架学习笔记(5)——Spring Boot创建与使用

    Spring Boot可以更为方便地搭建一个Web系统,之后服务器上部署也较为方便 创建Spring boot项目 1. 使用IDEA创建项目 2. 修改groupid和artifact 3. 一路n ...

随机推荐

  1. Java 8 为什么会引入lambda 表达式?

    Java 8 为什么会引入lambda ? 在Java8出现之前,如果你想传递一段代码到另一个方法里是很不方便的.你几乎不可能将代码块到处传递,因为Java是一个面向对象的语言,因此你要构建一个属于某 ...

  2. SpringCould-------使用Hystrix 实现断路器进行服务容错保护

    消费: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.or ...

  3. win7 部署tomcat

    1,下载 jdk:http://www.oracle.com/technetwork/java/javase/downloads/jdk-7u3-download-1501626.html 2,下载t ...

  4. Linux相关安装文档

    一.JDK环境安装 1.查看linux上是否存在已安装好的JDK (1)java -version openjdk version "1.8.0_181"OpenJDK Runti ...

  5. 基于CAS分析对ABA问题的一点思考

    基于CAS分析对ABA问题的一点思考 什么是CAS? 背景 synchronized加锁消耗太大 volatile只保证可见性,不保证原子性 基础 用CPU提供的特殊指令,可以: 自动更新共享数据; ...

  6. Fire Balls 04——砖塔的创建,动态上升以及旋转

    版权申明: 本文原创首发于以下网站: 博客园『优梦创客』的空间:https://www.cnblogs.com/raymondking123 优梦创客的官方博客:https://91make.top ...

  7. QFramework 使用指南 2020(三):脚本生成(1)基本使用

    在上一篇,我们对 QFramework 的两个主要版本提供了介绍,并且写下了第一个 QFramework 脚本. 在这一篇,我们学习 QFramework 中几乎每个项目都要用到并且从中受益的功能:自 ...

  8. 【spring-boot 源码解析】spring-boot 依赖管理

    关键词:spring-boot 依赖管理.spring-boot-dependencies.spring-boot-parent 问题 maven 工程,依赖管理是非常基本又非常重要的功能,现在的工程 ...

  9. 面试必备:Java线程池解析

    前言 掌握线程池是后端程序员的基本要求,相信大家求职面试过程中,几乎都会被问到有关于线程池的问题.我在网上搜集了几道经典的线程池面试题,并以此为切入点,谈谈我对线程池的理解.如果有哪里理解不正确,非常 ...

  10. HTML(二)属性,标题,段落,文本格式化

    HTML属性 HTML属性 HTML 元素可以设置属性 属性可以在元素中添加附加信息 属性一般描述于开始标签 属性总是以名称/值对的形式出现,比如:name="value" 常用属 ...