AOP的使用、通知类型介绍和AOP原理

先从AOP前置通知类型为例,写一个简单的demo,从入门级了解AOP的使用。 
IDE环境:Intellj IDEA 
步骤: 
1先建立一个Spring项目。项目结构如下: 
 
已经知道AOP四要素 
方面组件————-LogUtil类(添加日志功能) 
目标组件————-EntityDao类(进行数据库操作的功能) 
EntityDao类代码:(因为主要学习AOP,所以数据库的操作方法简单写一下就行)

package com.hnust.service;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;

@Service
public class EntityDao {

    public void save(){
        System.out.println("保存操作");
    }

    public void update(){
        System.out.println("更新操作");
    }

    public void delete(){
        System.out.println("删除操作");
    }

    public static void main(String[] args){
        //获取Spring容器
        ApplicationContext atc = new ClassPathXmlApplicationContext("spring-config.xml");
        //通过Spring容器创建EntityDao对象
        EntityDao ed =  atc.getBean(EntityDao.class);
        System.out.println("目标组件"+ed.getClass().getName());
        ed.save();
        ed.update();
        ed.delete();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Spring配置文件spring-config.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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.hnust"></context:component-scan>
</beans>
1
2
3
4
5
6
7
8
9
这里涉及IOC注解注入和组件扫描的知识,先不讲解(因为我也还没学0.0),知道就是添加了这个注解,通过组件扫描,就可以将对象添加进入Spring容器中,交由Spring管理,这是Spring容器利用IOC创建对象的一种方式就行。 
我们可以运行代码,得到一下结果 

现在已经模拟完成数据库的增删改操作啦。 
为了增加数据库的安全性,我们现在对数据库每一个操作之前添加日志文件,也就是在目标组件中添加方面组件模块,而且目标组件中的方法有格式要求,方法必须是共有的,无返回值,至于带不带参数,取决于通知类型,我们先使用前置通知类型进行测试,不需要带参数。

LogUtil类代码:

package com.hnust.aspect;

import org.springframework.stereotype.Component;

@Component
public class LogUtil {

    public void log1(){
        System.out.println("**数据库操作日志已记录**");
    }
}
1
2
3
4
5
6
7
8
9
10
11
有了目标组件和方面组件,AOP四要素还缺两个,就是通知和切入点,这两个都在Spring配置文件中配置就行。 
在Spring配置文件spring-config.xml文件添加AOP配置

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.hnust"></context:component-scan>
    <!--AOP配置-->
    <aop:config>
        <!--声明方面组件-->
        <aop:aspect ref="logUtil">
            <aop:before method="log1"
                        pointcut="within(com.hnust.service.*)"/>
        </aop:aspect>
    </aop:config>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我们学习一下配置规则。 
 
配置之后,再运行目标组件,正常情况结果是这样 
 
可以看到,AOP的配置使得目标组件成功调用方面组件的功能。 
需要注意的是,Intellij IDEA中创建Spring项目时,包没有导全,我们在使用AOP功能时,需要导入aspectjweaver-1.8.10.jar,如果使用maven构建项目,那么在maven配置文件中添加该包的依赖,如果没有使用maven,那么就下载这个包,添加进项目的lib文件中就行(坑呀,这个问题花了大半天没有解决,最后还是仔细看错误提示才找到原因,提醒各位出现BUG时习惯先看错误输出,不要无脑去看源程序)。 
AOP的入门使用大概就是上面讲解的了,我们知道AOP使得程序更加灵活,之间就讲过,AOP可以使得方面组件可以植入目标组件的任意位置,也就是说目标组件哪些方法可以调用方面组件,什么时机可以调用方面组件,所以,我们可以通过修改AOP配置中的切入点和通知去改变。

下面介绍AOP通知类型:

前置通知:调用目标组件前,调用方面组件(before)
后置通知:调用目标组件后,调用方面组件(after-returning)
最终通知:调用目标组件后,在finally里调用方面组件(after)
异常通知:目标组件发生异常时,调用方面组件。(after-throwing)
环绕通知:调用目标组件前、后,分别调用一次方面组件。(around) 
AOP就是按照这五种通知类型进行分类的。 
用代码可以更清晰的明白各通知之间的位置,我们关注点不在各通知之间的相对顺序,只要关注某个通知类型和关键业务代码的顺序就行了。
try{
    try{
        //环绕通知-前置
        //前置通知
        ed.save();
        //环绕通知-后置
    }(Exception e){
        throw e;
    }finally{
        //最终通知
    }
    //后置通知
}catch(Exception e){
    //异常通知
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我们利用上面的Demo来测试一下这五种类型的通知吧,了解不同类型通知的差异。 
前面Demo使用的是前置通知,后置通知和最终通知是一类,因为方面组件方法是无参的。只需要在配置文件中改变通知类型就可以。 
将before关键字依次改为after-returning、after,分别是后置通知和最终通知,运行结果一样,如下图: 
 
那么后置通知和最终通知区别是什么呢?学过异常处理机制就很容易理解,如果目标组件中业务核心代码发生出错,那么后置通知不会执行,但是最终通知会执行,因为最终通知位于finally模块中。

那么异常通知类型呢,我们需要在方面组件的方法中加入异常处理的参数。 
增加一个处理异常的方法log2

package com.hnust.aspect;

import org.springframework.stereotype.Component;

@Component
public class LogUtil {

    public void log1(){
        System.out.println("**数据库操作日志已记录**");
    }

    public void log2(Exception e){
        System.out.println("调用异常通知日志");
        //得到异常数组并输出
        String error = e.getStackTrace()[0].toString();
        System.out.println(error);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在配置文件中配置异常通知类型

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.hnust"></context:component-scan>
    <!--AOP配置-->
    <aop:config>
        <!--声明方面组件-->
        <aop:aspect ref="logUtil">
            <!--前置、后置、最终通知-->
            <aop:after-returning method="log1"
                        pointcut="within(com.hnust.service.*)"/>
            <!--异常通知-->
            <aop:after-throwing method="log2" throwing="e"
                                 pointcut="within(com.hnust.service.*)"/>
        </aop:aspect>
    </aop:config>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
此时运行目标组件,发现异常通知并没有输出,因为目标组件中没有方法出现异常,所以方面组件中的异常方法不会触发,我们可以在目标组件某个方法中加入异常代码,可以看到输出异常信息 


那么这个异常机制是怎么实现的呢?我们需要先了解一下AOP原理(感觉有点乱了,随意吧): 
AOP实现原理使用的是动态代理的技术,可以在程序运行阶段,根据业务的要求在内存中创建代理类,Spring采用2中方式实现动态代理: 
1:CGLIB工具包 
动态创建目标的子类作为代理类,所有的类都可以用它来实现代理 
2:JDK Proxy API 
动态创建接口的实现类作为代理类,带有接口的类可以由它实现代理 
可以知道后者有一定要求。 
那么Spring如何使用动态代理技术实现AOP这种功能呢? 
一张图可以明白 


我们可以通过以上的实验结论验证 
当没有使用AOP配置时:


使用了AOP配置后:

 
在目标组件中有这样一行代码

System.out.println("目标组件"+ed.getClass().getName());
1
这是输出目标组件的类名,调用AOP之后目标组件的类名便是代理类的类名,代理类是继承EntityDao的一个子类。 
根据AOP的通知类型可以使得调用方面组件和super.save()方法顺序可变。

知道了AOP的动态代理技术,了解AOP的异常通知类型也就容易了。 
在代理类中处理异常的机制是这样

try{
    super.save();
}catch(Exception e){
    logUtil.log2(e);
    }
1
2
3
4
5
当目标组件发生异常时,Sping 会将异常传给方面组件,然后在方面组件方法内部处理异常和输出异常日志。

最后介绍环绕通知的写法 
增加一个处理环绕通知的方法log3

package com.hnust.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Component
public class LogUtil {

    public void log1(){
        System.out.println("**数据库操作日志已记录**");
    }

    public void log2(Exception e){
        System.out.println("调用异常通知日志");
        //得到异常数组并输出
        String error = e.getStackTrace()[0].toString();
        System.out.println(error);
    }
    public Object log3(ProceedingJoinPoint pj)throws Throwable{
        System.out.println("调用log3()记录日志-环绕通知前置");
        Object obj=pj.proceed();
        System.out.println("调用log3()记录日志-环绕通知后置");
        return  obj;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在配置文件中配置环绕通知类型

<?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:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:component-scan base-package="com.hnust"></context:component-scan>
    <!--AOP配置-->
    <aop:config>
        <!--声明方面组件-->
        <aop:aspect ref="logUtil">
            <!--前置、后置、最终通知-->
            <aop:after-returning method="log1"
                        pointcut="within(com.hnust.service.*)"/>

            <!--异常通知-->
            <aop:after-throwing method="log2" throwing="e"
                                pointcut="within(com.hnust.service.*)"/>

            <!--环绕通知-->
            <aop:around method="log3"
                                pointcut="within(com.hnust.service.*)"/>
        </aop:aspect>
    </aop:config>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
运行后: 
 
环绕通知的原理: 
代理对象在复写目标组件方法中做了两件事: 
1.目标组件方法new了一个ProcessdingJoinPoint()对象,同时初始化目标组件对象,放到ProcessdingJoinPoint()对象中。 
2.logUtil.log3(p)。

LogUtil类中log3方法怎么处理的呢? 
前置内容 
p.proceed()—-执行目标方法 
后置内容
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值