Spring面向对象到面向切面实例分析

本篇内容主要讲解“Spring面向对象到面向切面实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Spring面向对象到面向切面实例分析”吧!

一.OOP&AOP

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

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

Spring面向对象到面向切面实例分析  spring 第1张

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)通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类

Spring面向对象到面向切面实例分析  spring 第2张

三.第一个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))

Spring面向对象到面向切面实例分析  spring 第3张

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

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面向对象到面向切面实例分析”有了更深的了解,不妨来实际操作一番吧!这里是蜗牛博客网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:niceseo99@gmail.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

评论

有免费节点资源,我们会通知你!加入纸飞机订阅群

×
天气预报查看日历分享网页手机扫码留言评论电报频道链接