Spring的简单AOP开发(六)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring的简单AOP开发(六)

一. AOP


AOP,是非常深奥的,学识太少,所以只能是简单AOP开发。 这里不涉及太深的东西。


AOP,即 面向切面编程,Aspect Oriented Programming 。以前面向对象是纵向编程,AOP相当于是横向编程。 与IOC 一起,构成了Spring 的核心思想。


常常用来进行,日志记录,性能统计,安全控制,事务处理,异常处理等。


图片内容引用于: https://www.cnblogs.com/hongwz/p/5764917.html


20190514193140356.png


下面,通过一个具体的实例来说明一下。


一个简单的例子: 在userDao 层实现时, 添加操作前的日志和操作后的日志。


二. AOP 之前的作法


二.一 UserDaoImpl 类中


@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao {
  @Override
  public int add() {
    System.out.println("添加用户的方法 userDao");
    return 1;
  }
}


这是一个简单的add() 添加方法。 需要在add() 方法里面记录操作前的时间和操作后的时间。 以前的作法是。


二.二 添加一个Logger 日志接口和实现类


package com.yjl.log;
/**
 @author:yuejl
 @date: 2019年4月30日 下午3:38:09
 @Description 类的相关描述
*/
public interface Logger {
  public void before();
  public void after();
}


实现类为:


package com.yjl.log.impl;
import java.util.Date;
import org.springframework.stereotype.Controller;
import com.yjl.log.Logger;
/**
 @author:yuejl
 @date: 2019年4月30日 下午3:39:22
 @Description 类的相关描述
*/
@Controller  //进行注解
public class LoggerImpl implements Logger{
  public void before(){
    System.out.println("---------添加之前----------"+new Date().toLocaleString());
  }
  public void after(){
    System.out.println("---------添加之后---------"+new Date().toLocaleString());
  }
}


二.三 UserDaoImpl 新改变


需要添加Logger 接口的引用,并调用其中的before() 和after() 方法。


@Repository(value="userDaoImpl")
public class UserDaoImpl implements UserDao {
  @Autowired
  private Logger logger;
  @Override
  public int add() {
    logger.before();
    System.out.println("添加用户的方法 userDao");
    logger.after();
    return 1;
  }
}


这样,在执行add() 方法的时候,就会执行 logger 中的before() 和after() 方法。


二.四 缺点


这样做,可以达到效果,但是却有问题。 一个类中有多个方法,而且每个项目中会存在多个类, 会造成很多类,很多方法中都要这么写,非常麻烦,更致命的是, 日志记录的方法 并不是每个类中方法所特有的东西, User 类中add() 方法,就是插入User 表数据, delete() 方法 就是删除User 表数据,与日志是没有关联的。


三. AOP 开发


三.一 引入jar 包


需要引入Spring 的aop jar包


20190515185714520.png


其中,还需要引入aop 的关联jar 包


2019051518580646.png


2019051518582314.png


三.二 xml 约束


在xml 头部引入相应的约束。


  xmlns:aop="http://www.springframework.org/schema/aop"



  http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd


三.三 bean 配置


<!-- 采用xml的方式。 仍然用前面几章的例子 -->
  <!--关于loger的bean-->
  <bean id="logger" class="com.yjl.log.impl.LoggerImpl"></bean>
  <!-- 关于dao的bean -->
  <bean id="userDao" class="com.yjl.dao.impl.UserDaoImpl"></bean>
  <!-- 关于service的dao -->
  <bean id="userService" class="com.yjl.service.impl.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
  </bean>
  <!-- 关于action的dao -->
  <bean id="userAction" class="com.yjl.web.action.UserAction" scope="prototype">
    <property name="userService" ref="userService"></property>
  </bean>


三.四 AOP 配置 (重点)


<!-- 设置在dao中切入logger -->
  <aop:config>
    <!-- 配置切入点 。expression 会单独解释一下-->
    <aop:pointcut expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))" id="pointCut1"/>
    <!-- 增强引用于的是哪个类 -->
    <aop:aspect ref="logger"> 
      <!--method 为logger Bean中的方法, pointcut-ref 为上面配置的切入点-->
      <aop:before method="before" pointcut-ref="pointCut1"/>
      <!-- 最终增强 -->
      <aop:after method="after" pointcut-ref="pointCut1"/>
    </aop:aspect>
  </aop:config>


三.五 LoggerImpl 实现类


public class LoggerImpl implements Logger{
  public void before(){
    System.out.println("---------前置增强----------"+new Date().toLocaleString());
  }
  @Override
  public void after() {
    System.out.println("---------最终增强---------"+new Date().toLocaleString());
  }
}


三.六 UserDaoImpl 实现类


@Override
  public int add() {
    System.out.println("添加用户的方法 userDao");
    return 1;
  }


三.七 启动服务器,运行


2019051518580646.png


会发现,在执行add() 方法时,会自动调用logger接口的before() 和after() 方法。 这就是AOP 的魅力。


四. AOP的术语解释及expression 表达式


四.一 术语解释


在AOP 中有一些常用的概念,如 切点,连接点,切面等。


20190515191441324.png


很难懂,老蝴蝶给翻译翻译。


Joinpoint 连接点, 就是可以被增强的点。 如在UserDaoImpl 中 实现了常见的 add(), delete(), update(), get(), getAll() 等方法, 这些方法都可以像add() 一样被增强,被日志记录, 则这些方法都是 连接点。


Pointcut 切入点, 在实际开发中,只需要记录 add() ,delete(), update() 方法的日志即可,不需要记录 get() 和getAll() 的日志, 而且程序中也是要这么处理的, 那么 add,delete,update 就是切入点,即实际被切入的点。 而get() ,getAll() 不是。


Advice 增强, 由切入点 找到了这个方法,如add() 方法, 想要对这个方法进行什么处理操作呢, 如执行这个方法之前,做什么操作,(Before 前置通知), 执行这个方法后面做什么操作,(After-return 后置通知), 在目标方法执行前和执行后执行 (around 环绕通知), 出现异常的时候,做什么操作 (after-throwing异常通知), 最后执行完这个方法后,无论是否出现异常,都会做什么操作(after, 最终通知) . advice 就是切面要完成的功能,可以有多个。


四.二 expression 表达式


expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(…))


[方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)


可以拆成六部分.


1.execution () 表达式主体

2.*  返回值类型

3.包名 哪个包下

4.类名 如果是任意包下的任意类为 *

5.方法名 如add,delete 任意方法为 *

6.参数名 () 如果是任意的参数, 那么就是两个点 . .


expression 表达式,可以准确的找到是哪一个方法,包括重载的方法。


例子:


  1. UserDaoImpl 类的 add 方法


expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))


  1. UserDaoImpl 类的所有方法


expression="execution(* com.yjl.dao.impl.UserDaoImpl.*(..))


  1. dao.impl 包下的所有类add 方法


expression="execution(* com.yjl.dao.impl.*.add(..))


  1. dao.impl 包下的所有类的所有方法


expression="execution(* com.yjl.dao.impl.*. *(..))


  1. com.yjl 包下的所有的包下的所有类的所有方法


expression="execution(* com.yjl.*.*.*(..))


  1. 所有包下的所有方法


expression="execution(* *.*.*(..))


  1. 所有公共的方法


expression="execution(public * *.*.*(..))


一般expression 表达式书写时,范围都尽量小,不能太大,一般为dao包下的所有类的所有方法。 即只记录与数据库有关的操作 (操作日志的时候,安全的时候不记录dao包。)


五. 所有通知集合


五.一 Logger 接口


package com.yjl.log;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 @author:yuejl
 @date: 2019年4月30日 下午3:38:09
 @Description 类的相关描述
*/
public interface Logger {
  public void before();
  public void afterReturn();
  public int around();
  public int aroundT(ProceedingJoinPoint p);
  public void throwExecption();
  public void after();
}


五.二 LoggerImpl 实现


package com.yjl.log.impl;
import java.util.Date;
import org.aspectj.lang.ProceedingJoinPoint;
import com.yjl.log.Logger;
/**
 @author:yuejl
 @date: 2019年4月30日 下午3:39:22
 @Description 类的相关描述
*/
public class LoggerImpl implements Logger{
  public void before(){
    System.out.println("---------前置增强----------"+new Date().toLocaleString());
  }
  public void afterReturn(){
    System.out.println("---------后置增强---------"+new Date().toLocaleString());
  }
  public int around() {
    // 在方法内部执行,所以返回值要与增强的方法的返回值一样。
    System.out.println("---------我是一个普通环绕增强---------");
    return 0;
  }
  @Override
  public void throwExecption() {
    System.out.println("---------我是一个异常增强---------");
  }
  @Override
  public int aroundT(ProceedingJoinPoint p) {
    System.out.println("环之前");
    int result=-1;
    try {
      result=(int) p.proceed(); //为dao中add() 方法的返回结果。
    } catch (Throwable e) {
      e.printStackTrace();
    }
    System.out.println("环之后");
    // return 0 时, service 中得到的结果为0
    return result;
  }
  @Override
  public void after() {
    System.out.println("---------最终增强---------"+new Date().toLocaleString());
  }
}


五.三 UserDaoImpl 中的add() 方法


@Override
  public int add() {
    System.out.println("添加用户的方法 userDao");
    return 1;
  }


五.四 xml 配置


<!-- 设置在dao中切入logger -->
  <aop:config>
    <!--不要忘记添加 aopalliance 和aspectjweaver-->
    <!-- 配置切入点 . 可以有多种形式,分别说出是什么意思。-->
    <aop:pointcut expression="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))" id="pointCut1"/>
    <!-- 把增强用到方法上 -->
    <aop:aspect ref="logger"> 
      <aop:before method="before" pointcut-ref="pointCut1"/>
      <!-- 最终增强 -->
      <aop:after method="after" pointcut-ref="pointCut1"/>
      <!-- 后置增强 -->
      <aop:after-returning method="afterReturn" pointcut-ref="pointCut1"/>
      <aop:around method="around" pointcut-ref="pointCut1"/>
      <aop:around method="aroundT" pointcut-ref="pointCut1"/>
      <!-- 异常增强 -->
      <aop:after-throwing method="throwExecption" pointcut-ref="pointCut1"/>
    </aop:aspect>
  </aop:config>


五.五 重启服务器


20190515195229867.png


没有异常增强和 环增强

将其去掉, around 只剩下一个aroundT


<!--<aop:around method="around" pointcut-ref="pointCut1"/>-->


20190515195515863.png


出现了环绕增强,没有出现异常增强。 异常增强,只有在方法出现异常时,才出现。

将add() 方法改成:


@Override
  public int add() {
    System.out.println("添加用户的方法 userDao");
    int i=10/0;
    return 1;
  }


20190515195835725.png


2019051519585468.png


可以看出,先执行 前置,然后是环绕,如果有异常,就执行 异常通知,然后是后置,最后是最终通知。


六 注解开发


六.一 开启AOP 注解的自动代码


<!-- aop注解的方式 -->
  <aop:aspectj-autoproxy></aop:aspectj-autoproxy>


六.二 在LoggerImpl类中添加注解@Aspect


@Component
@Scope("prototype")
@Aspect
public class LoggerImpl implements Logger{


是org.aspectj.lang.annotation.Aspect 包下的注解。


六.三 添加在方法上配置注解


  1. 可以直接在后面跟 expression 语句。


@Before("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  public void before(){
    System.out.println("---------前置增强----------"+new Date().toLocaleString());
  }
  @AfterReturning("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  public void afterReturn(){
    System.out.println("---------后置增强---------"+new Date().toLocaleString());
  }
  //@Around("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  public int around() {
    // 在方法内部执行,所以返回值要与增强的方法的返回值一样。
    System.out.println("---------我是一个普通环绕增强---------");
    return 0;
  }
  @AfterThrowing("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  @Override
  public void throwExecption() {
    System.out.println("---------我是一个异常增强---------");
  }
  @Around("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  @Override
  public int aroundT(ProceedingJoinPoint p) {
    System.out.println("环之前");
    int result=-1;
    try {
      result=(int) p.proceed();
    } catch (Throwable e) {
      // TODO 自动生成的 catch 块
      e.printStackTrace();
    }
    System.out.println("环之后");
    // return 0 时, service 中得到的结果为0
    return result;
  }
  @After("execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  @Override
  public void after() {
    System.out.println("---------最终增强---------"+new Date().toLocaleString());
  }


  1. 如果配置的语句都一样的话, 可以这样写


@Pointcut(value="execution(* com.yjl.dao.impl.UserDaoImpl.add(..))")
  public void pointCut(){
  }
  @Before("LoggerImpl.pointCut()")
  public void before(){
    System.out.println("---------前置增强----------"+new Date().toLocaleString());
  }
  @AfterReturning("LoggerImpl.pointCut()")
  public void afterReturn(){
    System.out.println("---------后置增强---------"+new Date().toLocaleString());
  }


运行之后是:


20190515201346470.png


谢谢!!!

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
26天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
38 4
|
3天前
|
XML Java 数据安全/隐私保护
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
Spring Aop该如何使用
|
23天前
|
存储 缓存 Java
Spring高手之路23——AOP触发机制与代理逻辑的执行
本篇文章深入解析了Spring AOP代理的触发机制和执行流程,从源码角度详细讲解了Bean如何被AOP代理,包括代理对象的创建、配置与执行逻辑,帮助读者全面掌握Spring AOP的核心技术。
30 3
Spring高手之路23——AOP触发机制与代理逻辑的执行
|
8天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
20 1
|
20天前
|
XML Java 数据格式
提升效率!Spring Boot 开发中的常见失误轻松规避
本文深入探讨了在 Spring Boot 开发中常见的失误,包括不当使用注解、不良异常处理、低效日志记录等,提供了有效的规避策略,帮助开发者提升代码质量和系统性能,构建更健壮、高效的应用程序。
|
4天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
9 0
|
1月前
|
Java 编译器 Spring
Spring AOP 和 AspectJ 的区别
Spring AOP和AspectJ AOP都是面向切面编程(AOP)的实现,但它们在实现方式、灵活性、依赖性、性能和使用场景等方面存在显著区别。‌
54 2
|
1月前
|
Java 数据库连接 Spring
【2021Spring编程实战笔记】Spring开发分享~(下)
【2021Spring编程实战笔记】Spring开发分享~(下)
25 1
|
30天前
|
开发框架 Java API
「SpringBrick快速入门指南」:一款基于Spring Boot的高级插件化开发框架
「SpringBrick快速入门指南」:一款基于Spring Boot的高级插件化开发框架
46 0
|
30天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
45 0
下一篇
无影云桌面