简介

AOP(Aspect Oriented Programming),即面向切面编程

这是对面向对象思想的一种补充。

面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能。

常见的使用场景有:

  • 日志
  • 事务
  • 数据库操作

这些操作中,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是 AOP 的强项。

在 AOP 中,有几个常见的概念

概念 说明
切点 要添加代码的地方,称作切点
通知(增强) 通知就是向切点动态添加的代码
切面 切点+通知
连接点 切点的定义

AOP的实现

AOP 实际上是基于 Java 动态代理来实现的。

Java 中的动态代理有两种实现方式:cglibjdk

动态代理

基于 JDK 的动态代理

定义一个计算器接口

public interface MyCalculator {

    void add(int a, int b);

}

定义计算机接口的实现

@Service
public class MyCalculatorImpl implements MyCalculator{
@Override
public void add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));;
}
}

定义代理类

public class CalculatorProxy {

    public static Object getInstance(final MyCalculatorImpl myCalculator) {
return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
/**
* @param proxy 代理对象
* @param method 代理的方法
* @param args 方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法开始执行");
Object invoke = method.invoke(myCalculator, args);
System.out.println("方法结束执行");
return invoke;
}
});
} }

Proxy.newProxyInstance 方法接收三个参数

  • 第一个是一个 classloader,即类加载器。
  • 第二个是代理多项实现的接口。
  • 第三个是代理对象方法的处理器,所有要额外添加的行为都在 invoke 方法中实现。

方法调用的 invoke 方法有两个参数

  • 第一个是执行方法的对象。
  • 第二个是方法的参数数组。

调用方法

MyCalculatorImpl myCalculatorImpl = new MyCalculatorImpl();
MyCalculator myCalculator = (MyCalculator) CalculatorProxy.getInstance(myCalculatorImpl);
myCalculator.add(1, 2);

打印结果

方法开始执行
1+2=3
方法结束执行

五种通知

Spring 中的 Aop 的通知类型有 5 种

  • 前置通知
  • 后置通知
  • 异常通知
  • 返回通知
  • 环绕通知

在项目中,引入 Spring 依赖(这次需要引入 Aop 相关的依赖)

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>

定义切点,这里介绍两种切点的定义方式

  • 使用自定义注解
  • 使用规则

其中,使用自定义注解标记切点,是侵入式的,所以这种方式在实际开发中不推荐,仅作为了解。

另一种使用规则来定义切点的方式,无侵入,一般推荐使用这种方式。

自定义注解

自定义一个注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}

在需要拦截的方法上,添加该注解

在 reduce 方法上添加了 @Action 注解,表示该方法将会被 Aop 拦截,而其他未添加该注解的方法则不受影响

@Service
public class MyCalculatorImpl implements MyCalculator{ @Override
public void add(int a, int b) {
System.out.println(a + "+" + b + "=" + (a + b));;
} @Override
@Action
public int reduce(int a, int b) {
int result = a - b;
System.out.println(a + "-" + b + "=" + result);
return result;
} }

接下来,定义增强(通知、Advice)

@Component
@Aspect //标注这是一个切面
public class LogAspect { /**
* @param joinPoint 包含了目标方法的关键信息
* @Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中,需要填入切点
*/
@Before(value = "@annotation(Action)")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法要执行了");
} /**
* 后置通知
* @param joinPoint 包含了目标方法的所有关键信息
* @After 表示这是一个后置通知,即在目标方法执行之后执行
*/
@After(value = "@annotation(Action)")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法执行结束了");
} /**
* @param joinPoint
* @param r 返回值
* @AfterReturning 表示这是一个返回通知,即有目标方法有返回值的时候才会触发
* 该注解中的 returning 属性表示目标方法返回值的变量名,这个需要和参数一一对应
* 注意:目标方法的返回值类型要和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为 void),则方法返回值参数可以为 Object
*/
@AfterReturning(value = "@annotation(Action)", returning = "r")
public void afterReturning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法返回了" + r);
} /**
* 异常通知
* @param joinPoint
* @param e 目标方法所抛出的异常
* 注意,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,只有这样,才会捕获
* 如果想拦截所有,参数类型声明为 Exception
*/
@AfterThrowing(value = "@annotation(Action)", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法抛出了异常:" + e.getMessage()); } /**
* 环绕通知
*
* 环绕通知是集大成者,可以用环绕通知实现上面的四个通知,这个方法的核心有点类似于在这里通过反射执行方法
* @param pjp
* @return 注意这里的返回值类型最好是 Object ,和拦截到的方法相匹配
*/
@Around(value = "@annotation(Action)")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
} }

在配置类中,开启包扫描和自动代理

@Configuration
@ComponentScan
@EnableAspectJAutoProxy // 开启自动代理
public class JavaConfig {
}

开启调用

@Resource
private MyCalculator myCalculator; @Test
public void testAutoProxy() {
myCalculator.add(1, 2);
myCalculator.reduce(5, 2);
}

打印结果

1+2=3
reduce方法要执行了
5-2=3
reduce方法返回了3
reduce方法执行结束了

LogAspect 切面中,切点的定义是不够灵活的,切点直接写在注解里边,如果要修改切点,每个方法的注解都要修改。

我们可以将切点统一定义,然后统一调用。

@Pointcut("@annotation(Action)")
public void pointCut() {
} @Before("pointCut()")
public void before(JoinPoint joinPoint) {
}

不过,使用注解是侵入式的,还可以改为非侵入式的。

下面这种方式是更为通用的拦截方式

/**
* 可以统一定义切点
* 第一个 * 表示要拦截的目标方法返回值任意(也可以明确指定返回值类型)
* 第二个 * 表示包中的任意类(也可以明确指定类)
* 第三个 * 表示类中的任意方法
* 最后面的两个点表示方法参数任意,个数任意,类型任意
*/
@Pointcut("execution(* cn.sail.training.spring.aop.*.*(..))")
public void pointCut() { }

XML 配置 AOP

定义切面,不再需要注解

public class LogAspectXml {

    public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法要执行了");
} public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法执行结束了");
} public void afterReturning(JoinPoint joinPoint, Integer r) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法返回了" + r);
} public void afterThrowing(JoinPoint joinPoint, Exception e) {
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
System.out.println(methodName + "方法抛出了异常:" + e.getMessage()); } public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//这个相当于 method.invoke 方法,我们可以在这个方法的前后分别添加日志,就相当于是前置/后置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
} }

在 XML 中配置 AOP

<bean class="cn.sail.aop.LogAspectXml" id="logAspectXml"/>
<aop:config>
<aop:pointcut id="pointCut1" expression="execution(* cn.sail.aop.*.*(..))"/>
<aop:aspect ref="logAspectXml">
<aop:before method="before" pointcut-ref="pointCut1"/>
<aop:after method="after" pointcut-ref="pointCut1"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointCut1" returning="r"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointCut1" throwing="e"/>
<aop:around method="around" pointcut-ref="pointCut1"/>
</aop:aspect>
</aop:config>

JdbcTemplate

JdbcTemplate 是 Spring 利用 AOP 思想封装的 JDBC 操作工具。

添加依赖

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

准备数据库

create table grade
(
id int auto_increment comment 'ID'
primary key,
name varchar(30) null comment '名称'
);

准备实体类

public class Grade implements Serializable {

    private static final long serialVersionUID = 5882495741692079263L;

    private int id;

    private String name;

    public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} @Override
public String toString() {
return "Grade{" +
"id=" + id +
", name='" + name + '\'' +
'}';
} public Grade() {
} public Grade(int id, String name) {
this.id = id;
this.name = name;
}
}

Java 配置

@Configuration
public class JdbcConfig { @Bean
DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test");
driverManagerDataSource.setUsername("username");
driverManagerDataSource.setPassword("password");
return driverManagerDataSource;
} @Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
} }

调用

public class TestDemo {

    private JdbcTemplate jdbcTemplate;

    @Before
public void before() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JdbcConfig.class);
jdbcTemplate = ctx.getBean(JdbcTemplate.class);
} @Test
public void insert() {
jdbcTemplate.update("insert into grade (name) values (?);", "高一");
} @Test
public void update() {
jdbcTemplate.update("update grade set name = ? where id = ?", "大五", 5);
} @Test
public void delete() {
jdbcTemplate.update("delete from grade where id = ?", 5);
} @Test
public void select() {
Grade grade = jdbcTemplate.queryForObject("select * from grade where id = ?", new BeanPropertyRowMapper<>(Grade.class), 1);
System.out.println(grade);
}

在查询时,如果使用了 BeanPropertyRowMapper,要求查出来的字段必须和 Bean 的属性名一一对应。

如果不一样,则不要使用 BeanPropertyRowMapper,此时需要自定义 RowMapper 或者给查询的字段取别名。

@Test
public void select1() {
Grade grade = jdbcTemplate.queryForObject("select * from grade where id = ?", new RowMapper<Grade>() {
@Override
public Grade mapRow(ResultSet resultSet, int i) throws SQLException {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
return new Grade(id, name);
}
}, 2);
System.out.println(grade);
}

如果要查询多条记录,方式如下

@Test
public void select2() {
List<Grade> gradeList = jdbcTemplate.query("select * from grade", new BeanPropertyRowMapper<>(Grade.class));
System.out.println(gradeList);
}

以上配置,也可以通过 XML 文件来实现。

通过 XML 文件实现只是提供 JdbcTemplate 实例,剩下的代码还是 Java 代码,就是 JdbcConfig 被 XML 文件代替而已。

<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource2">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="Asailing648735"/>
</bean> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource2"/>
</bean>

事务

Spring 中的事务主要是利用 AOP 思想,简化事务的配置,可以通过 Java 配置也可以通过 XML 配置。

建表

CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

配置JdbcTemplate

<!--配置数据源-->
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource2">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="Asailing648735"/>
</bean> <!--配置数据访问模板-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource2"/>
</bean>

开启自动化扫描

<context:component-scan base-package="cn.sail.transactional"/>

编写持久层

@Repository
public class UserDao { @Resource
private JdbcTemplate jdbcTemplate; public void addMoney(String username, Integer money) {
jdbcTemplate.update("update account set money = money + ? where username = ?", money, username);
} public void reduceMoney(String username, Integer money) {
jdbcTemplate.update("update account set money = money - ? where username = ?", money, username);
} }

编写服务层

@Service
public class UserService { @Resource
private UserDao userDao; public void updateMoney() {
userDao.addMoney("zhangsan", 100);
int i = 1 / 0;
userDao.reduceMoney("lisi", 100);
} }

这里的int i = 1 / 0;是为了让方法报错才写的。

XML配置

配置事务管理器

<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="dataSourceTransactionManager">
<property name="dataSource" ref="dataSource2"/>
</bean>

配置事务要处理的方法

<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="add*"/>
<tx:method name="delete*"/>
<tx:method name="update*"/>
<tx:method name="get*"/>
</tx:attributes>
</tx:advice>

一旦配置了方法名称规则之后,service 中的方法一定要按照这里的名称规则来,否则事务配置不会生效。

配置 Aop

<aop:config>
<aop:pointcut id="pointCut2" expression="execution(* cn.sail.transactional.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut2"/>
</aop:config>

测试

public class TestDemo {

    private JdbcTemplate jdbcTemplate;

    private UserService userService;

    @Before
public void before() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = ctx.getBean(JdbcTemplate.class);
userService = ctx.getBean(UserService.class);
} @Test
public void testTransactional() {
userService.updateMoney();
} }

由于UserService中的updateMoney方法会报错,addMoney的执行结果会被回滚,表的数据保持不变,即实现了事务的效果。

Java 配置

在 XML 中添加如下配置

<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

这行代码可以代替如下代码

<tx:advice transaction-manager="dataSourceTransactionManager" id="txAdvice">
<tx:attributes>
<tx:method name="add*"/>
<tx:method name="delete*"/>
<tx:method name="update*"/>
<tx:method name="get*"/>
</tx:attributes>
</tx:advice> <aop:config>
<aop:pointcut id="pointCut2" expression="execution(* cn.sail.transactional.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut2"/>
</aop:config>

添加 @Transactional 注解

此注解表示该方法开启事务。

这个注解也可以放在类上,表示这个类中的所有方法都开启事务。

@Service
public class UserService { @Resource
private UserDao userDao; @Transactional
public void updateMoney1() {
userDao.addMoney("zhangsan", 100);
int i = 1 / 0;
userDao.reduceMoney("lisi", 100);
} }

测试

@Test
public void testTransactional() {
userService.updateMoney1();
}

执行方法,会得到与 XML 配置一样的结果。

Spring 03 切面编程的更多相关文章

  1. 快速高效掌握企业级项目中的Spring面向切面编程应用,外带讲面试技巧

    Spring面向切面编程(AOP)是企业级应用的基石,可以这样说,如果大家要升级到高级程序员,这部分的知识必不可少. 这里我们将结合一些具体的案例来讲述这部分的知识,并且还将给出AOP部分的一些常见面 ...

  2. Spring AOP 切面编程记录日志和接口执行时间

    最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特 ...

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

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

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

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

  5. Spring AOP 切面编程的方法

    spring aop的使用分为两种,一种是使用注解来实现,一种是使用配置文件来实现. 先来简单的介绍一下这两种方法的实现,接下来详细的介绍各处的知识点便于查阅.目录如下: 1.基于注解实现spring ...

  6. 再学习之Spring(面向切面编程)

    一.概念 1.理论 把横切关注点和业务逻辑相分离是面向切面编程所要解决的问题.如果要重用通用功能的话,最常见的面向对象技术是继承(inheritance)或 组成(delegation).但是,如果在 ...

  7. spring aop 切面编程中获取具体方法的方法

    spring 切面编程中获取具体方法的方法 工作中,使用环绕通知,用来捕获异常,然后通过获取方法的返回值,返回不同的数据给到调用方. 由于方法的返回值不同,我们处理异常时,也需要返回不同的格式. 这时 ...

  8. Spring AOP 切面编程实战Demo项目

    为什么会有此项目?在某日,我看博客时,看到了讲面向切面编程的内容,之前也知道spring是面向切面编程的,只是自己没有写过相关的代码,于是决定自己写一个test.但是url拦截器从外部看,和AOP有相 ...

  9. Spring面向切面编程

    在使用面向切面编程时,我们可以在一个地方定义通用的共鞥,但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无需修改受影响的类.横切关注点可以被模块化为特殊的类,这些类被称为切面.这样的优点是 ...

随机推荐

  1. 一文带你搞懂 SSR

    欲语还休,欲语还休,却道天凉好个秋 ---- <丑奴儿·书博山道中壁>辛弃疾 什么是 SSR ShadowsocksR?阴阳师?FGO? Server-side rendering (SS ...

  2. 2021.05.04【NOIP提高B组】模拟 总结

    T1 题目大意, \(S_{i,j}=\sum_{k=i}^j a_k\) ,求 \(ans=\min\{ S_{i,j}\mod P|S_{i,j}\mod P\ge K \}\) 其中 \(i\l ...

  3. B 树的简单认识

    理解 B 树的概念 B 树是一种自平衡的查找树,能够保持数据有序.这种数据结构能够让查找数据.顺序访问.插入数据及删除数据的动作,都能在对数时间内完成. 同一般的二叉查找树不同,B 树是一棵多路平衡查 ...

  4. R数据分析:临床预测模型中校准曲线和DCA曲线的意义与做法

    之前给大家写过一个临床预测模型:R数据分析:跟随top期刊手把手教你做一个临床预测模型,里面其实都是比较基础的模型判别能力discrimination的一些指标,那么今天就再进一步,给大家分享一些和临 ...

  5. iNeuOS工业互联网操作系统,增加搜索应用、多数据源绑定、视图背景设置颜色、多级别文件夹、组合及拆分图元

    目       录 1.      概述... 2 2.      搜索应用... 2 3.      多数据源绑定... 3 4.      视图背景设置颜色... 4 5.      多级别文件夹 ...

  6. sublime_text 3安装Emmet时出现PyV8警告

    使用Emmet是需要在PyV8依赖下才可以的.1. 下面是下载网址:PyV8下载地址 下载自己系统版本的压缩包,然后解压,自己创建一个名为PyV8文件夹.将解压后的文件放入该文件夹里. 打开首选项里的 ...

  7. SAP Web Dynpro-集成消息

    您可以使用消息管理器将消息集成到消息日志中. 您可以使用Web Dynpro代码向导打开消息管理器. 您可以从工具栏中打开Web Dynpro代码向导. 当您的ABAP工作台处于更改模式或编辑视图或控 ...

  8. 使用 Cheat Engine 修改 Kingdom Rush 中的金钱、生命、星

    最新博客链接 最近想学习一下 CE,刚好看见游戏库里装了 Kingdom Rush 就拿它来研究吧.这里写的东西,需要一些 Cheat Engine 的基础,可以看看教程. 这里主要是看写的注释,来理 ...

  9. NC23046 华华教月月做数学

    NC23046 华华教月月做数学 题目 题目描述 找到了心仪的小姐姐月月后,华华很高兴的和她聊着天.然而月月的作业很多,不能继续陪华华聊天了.华华为了尽快和月月继续聊天,就提出帮她做一部分作业. 月月 ...

  10. Nacos 的安装与服务的注册

    Nacos 的安装与服务的注册 我们都知道naocs是一个注册中心,那么注册中心是什么呢? 什么是注册中心? 它类似与一个中介角色(不收费的良心中介), 在微服务中起纽带的作用,它提供了服务和服务地址 ...