前言

Object object = new Object();

世间万物的本质都可看作类的对象,面向对象(OOP)的模式让程序易维护、易复用、易扩展,而面向切面(AOP)则是面向对象的补充,让对象的功能更加强大

对比前面的日志框架技术二者非常相似,他的特点就是在不影响业务的前提下将程序的运行情况输出到控制台,总体来看是起一个辅助的作用,所谓的AOP亦是如此——是在不改原有代码的前提下对其进行增强

一.OOP&AOP

OOP将组件视为对象,AOP将对象的切面视为“对象”

OOP&AOP让程序通过极其简单的方式变得更加全面、强大

AOP(Aspect Oriented Programming)面向切面编程、OOP(Object Oriented Programming)面向对象编程

OOP是一种编程思想,AOP也是一种编程思想,编程思想主要的内容就是指导程序员该如何编写程序,两者都是不同的编程范式各有特色

二.AOP核心

通过以下一个计算程序运行时间的功能,引出AOP相关概念

@Repository
public class AImpl implements A {
    public void save() {
        //记录程序当前执行执行(开始时间)
        Long startTime = System.currentTimeMillis();
        //业务执行万次
        for (int i = 0;i<10000;i  ) {
            System.out.println("START ...");
        }
        //记录程序当前执行时间(结束时间)
        Long endTime = System.currentTimeMillis();
        //计算时间差
        Long totalTime = endTime-startTime;
        //输出信息
        System.out.println("执行万次程序消耗时间:"   totalTime   "ms");
    }
    public void m1(){ System.out.println(" m1 ..."); }
    public void m2(){ System.out.println(" m2 ..."); }
}

(1)save,m1m2方法,这些方法我们给起了一个名字叫连接点

(2)对于需要增强的方法我们给起了一个名字叫切入点

(3)将功能抽取到一个方法中,换句话说就是存放共性功能的方法,我们给起了个名字叫通知

(4)通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,那么对于通知和切入点之间的关系描述,我们给起了个名字叫切面

(5)通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类

三.第一个AOP案例

1.环境准备

  • 创建一个Maven项目
  • pom.xml添加Spring依赖spring-context
  • 添加A和AImpl类
public interface A {
    public void save();
    public void m1();
}
@Repository
public class AImpl implements A {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }
    public void m1(){
        System.out.println("book dao m1 ...");
    }
}

创建Spring的配置类

@Configuration
@ComponentScan("yu7daily")
public class Config {
}

编写Show运行类

public class Show {
    public static void main(String[] args) {
        ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
        A A = ctx.getBean(A.class);
        A.save();
    }
}

2.AOP实现步骤

1.@EnableAspectJAutoProxy 开启注解格式AOP功能

2.@Aspect设置当前类为AOP切面类

3.@Pointcut 设置切入点方法

4.@Before设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

**1.添加依赖

pom.xml

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

因为spring-context中已经导入了spring-aop,所以不需要再单独导入spring-aop.

导入AspectJ的jar包,AspectJ是AOP思想的一个具体实现,Spring有自己的AOP实现,但是相比于AspectJ来说比较麻烦,所以我们直接采用Spring整合ApsectJ的方式进行AOP开发。

2.定义接口与实现类:环境准备的时候,AImpl已经准备好,不需要做任何修改

3.定义通知类和通知

通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印

public class Test {
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

类名和方法名没有要求,可以任意。

4.定义切入点

AImpl中有两个方法,分别是save和m1,我们要增强的是m1方法,该如何定义呢?

public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

说明:

切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。

execution及后面编写的内容

5.制作切面

切面是用来描述通知和切入点之间的关系,如何进行关系的绑定?

public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Before("po1()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置

说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行,除此之前还有其他四种类型

6.将通知类配给容器并标识其为切面类

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Before("po1()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

7.开启注解格式AOP功能

@Configuration
@ComponentScan("yu7daily")
@EnableAspectJAutoProxy
public class Config {
}

8.运行程序

public class Show {
    public static void main(String[] args) {
        ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
        A A = ctx.getBean(A.class);
        A.m1();
    }
}

看到在执行m1方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功!!!

四.切入点表达式

前面的案例中,有涉及到如下内容:

对于AOP中切入点表达式,我们总共会学习三个内容,分别是语法格式、通配符和书写技巧。

1.语法格式

首先我们先要明确两个概念:

切入点:要进行增强的方法

切入点表达式:要进行增强的方法的描述方式

描述方式一:执行yu7daily.dao包下的A接口中的无参数m1方法

execution(void yu7daily.dao.A.m1())

描述方式二:执行yu7daily.dao.impl包下的AImpl类中的无参数m1方法

execution(void yu7daily.dao.impl.AImpl.m1())

因为调用接口方法的时候最终运行的还是其实现类的方法,所以上面两种描述方式都是可以的。

对于切入点表达式的语法为:

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)

execution(public User yu7daily.service.UserService.findById(int))

切入点表达式就是要找到需要增强的方法,所以它就是对一个具体方法的描述,但是方法的定义会有很多,所以如果每一个方法对应一个切入点表达式,极其复杂可以通过以下方式进行简化

2.通配符

使用通配符描述切入点,主要的目的就是简化之前的配置

*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

execution(public * yu7daily.*.UserService.find*(*))

匹配yu7daily包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution(public User com..UserService.findById(..))

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

:专用于匹配子类类型

execution(* *..*Service .*(..))

使用切入点表达式来分析下:

execution(void yu7daily.dao.A.m1())
匹配接口,能匹配到
execution(void yu7daily.dao.impl.AImpl.m1())
匹配实现类,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1())
返回值任意,能匹配到
execution(* yu7daily.dao.impl.AImpl.m1(*))
返回值任意,但是m1方法必须要有一个参数,无法匹配,要想匹配需要在m1接口和实现类添加参数
execution(void com.*.*.*.*.m1())
返回值为void,com包下的任意包三层包下的任意类的m1方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.m1())
返回值为void,com包下的任意两层包下的任意类的m1方法,匹配到的是接口,能匹配
execution(void *..m1())
返回值为void,方法名是m1的任意包下的任意类,能匹配
execution(* *..*(..))
匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..u*(..))
匹配项目中任意包任意类下只要以u开头的方法,m1方法能满足,能匹配
execution(* *..*e(..))
匹配项目中任意包任意类下只要以e结尾的方法,m1和save方法能满足,能匹配
execution(void com..*())
返回值为void,com包下的任意包任意类任意方法,能匹配,*代表的是方法
execution(* yu7daily.*.*Service.find*(..))
将项目中所有业务层方法的以find开头的方法匹配
execution(* yu7daily.*.*Service.save*(..))
将项目中所有业务层方法的以save开头的方法匹配

五.AOP通知类型

它所代表的含义是将通知添加到切入点方法执行的前面。

除了这个注解外,还有没有其他的注解,换个问题就是除了可以在前面加,能不能在其他的地方加?

(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容

(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容

(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,如果方法执行抛出异常,返回后通知将不会被添加

(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,只有方法抛出异常后才会被添加

(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后,这也是比较常用的方式,它可以实现其他四种通知类型的功能

环境准备

1.pom.xml添加Spring依赖spring-context、aspectjweaver

2.添加A和AImpl类

public interface A {
    public void m1();
    public int m2();
}
@Repository
public class AImpl implements A {
    public void m1(){
        System.out.println(" m1 ...");
    }
    public int m2() {
        System.out.println(" m2 is running ...");
        return 1;
    }
}

创建Spring的配置类

@Configuration
@ComponentScan("yu7daily")
@EnableAspectJAutoProxy
public class Config {
}

创建通知类

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    public void around(){
        System.out.println("around before advice ...");
        System.out.println("around after advice ...");
    }
}

编写Show运行类

public class Show {
    public static void main(String[] args) {
        ShowlicationContext ctx = new AnnotationConfigShowlicationContext(Config.class);
        A A = ctx.getBean(A.class);
        A.m1();
    }
}

环绕通知

(1)原始方法有返回值的处理

修改Test,对A中的m2方法添加环绕通知,

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Pointcut("execution(int yu7daily.dao.A.m2())")
    private void po2(){}
    @Around("po2()")
    public void aroundM2(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
    }
}

修改Show类,调用m2方法

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Pointcut("execution(int yu7daily.dao.A.m2())")
    private void po2(){}
    @Around("po2()")
    public Object aroundM2(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice ...");
        //表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }
}

说明:

返回的是Object而不是int的主要原因是Object类型更通用随时可以转型

在环绕通知中是可以对原始方法返回值就行修改的

1.返回后通知

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Pointcut("execution(int yu7daily.dao.A.m2())")
    private void po2(){}
    @AfterReturning("po2()")
    public void afterReturning() {
        System.out.println("afterReturning advice ...");
    }
}

注意:返回后通知是需要在原始方法m2正常执行后才会被执行,如果m2()方法执行的过程中出现了异常,那么返回后通知是不会被执行。后置通知则是不管原始方法有没有抛出异常都会被执行

2.异常后通知

@Component
@Aspect
public class Test {
    @Pointcut("execution(void yu7daily.dao.A.m1())")
    private void po1(){}
    @Pointcut("execution(int yu7daily.dao.A.m2())")
    private void po2(){}
    @AfterReturning("po2()")
    public void afterThrowing() {
        System.out.println("afterThrowing advice ...");
    }
}

环绕通知注意事项

1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知

2. 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行

3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型

4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object

5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

到此这篇关于Spring超详细讲解面向对象到面向切面的文章就介绍到这了,更多相关Spring面向对象内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Spring超详细讲解面向对象到面向切面的更多相关文章

  1. Swift面向对象的类型

    1、类2、结构体3、枚举在swift语言中通过类和结构体实现面向对象,在Swift语言中,枚举也具有面向对象的特性示例和对象在面向对象中,将类创建对象的过程称为实例化,因此将对象称为实例化,但是在swift中,枚举和结构体不能称为对象,因为结构体和枚举并不是彻底的面向对象类型,而是只包含了一些面向对象的特定,例如,在Swift中继承只发生在类上,结构体和枚举不能继承

  2. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  3. Spring JdbcTemplate执行数据库操作详解

    JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法使对数据库的操作更加方便、友好,效率也不错,这篇文章主要介绍了Spring JdbcTemplate执行数据库操作,需要的朋友可以参考下

  4. Spring Batch批处理框架操作指南

    Spring Batch 是 Spring 提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作,这篇文章主要介绍了Spring Batch批处理框架操作指南,需要的朋友可以参考下

  5. Spring详细讲解@Autowired注解

    @Autowired注解可以用在类属性,构造函数,setter方法和函数参数上,该注解可以准确地控制bean在何处如何自动装配的过程。在默认情况下,该注解是类型驱动的注入

  6. 使用Spring AOP实现用户操作日志功能

    这篇文章主要介绍了使用Spring AOP实现了用户操作日志功能,功能实现需要一张记录日志的log表,结合示例代码给大家讲解的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  7. Spring Security认证器实现过程详解

    一些权限框架一般都包含认证器和决策器,前者处理登陆验证,后者处理访问资源的控制,这篇文章主要介绍了Spring Security认证器实现过程,需要的朋友可以参考下

  8. spring学习JdbcTemplate数据库事务管理

    这篇文章主要为大家介绍了spring学习JdbcTemplate数据库事务管理,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  9. Spring Boot 集成Redisson实现分布式锁详细案例

    这篇文章主要介绍了Spring Boot 集成Redisson实现分布式锁详细案例,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下

  10. Spring Security实现接口放通的方法详解

    在用Spring Security项目开发中,有时候需要放通某一个接口时,我们需要在配置中把接口地址配置上,这样做有时候显得麻烦。本文将通过一个注解的方式快速实现接口放通,感兴趣的可以了解一下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部