三.spring面向切面编程(AOP)

三.AOP

1.简介

Aspect Orientied Programming

面向切面编程

使得应用易于扩展

开闭原则

将应用中所需要使用的交叉业务逻辑提取出来封装成切面

由AOP容器在适当的时机

将这些切面织入到具体的业务逻辑中

2.目的

  • 解耦合
    • 将具体的核心业务逻辑与交叉业务逻辑相分离
  • 切面的复用
    • 一个切面被多次使用
  • 独立模块化
    • 例如原本交叉业务逻辑做的打印操作
    • 将打印操作统一更换为日志操作
    • 只要切面改了,所有的业务均跟着改变
    • 独立模块化的前提是切面的复用
  • 在不改变原有功能的基础上,增加新的功能

3.动态代理

AOP是基于动态代理来实现的

动态代理是根据目标类的类加载器、代理的接口、交叉业务逻辑

生成对应的目标类的代理类

public class LogInvocationHandler implements InvocationHandler {

    private Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 交叉业务逻辑
     * @param proxy     代理对象,完全没用
     * @param method    需要代理的目标方法
     * @param args      目标方法的参数列表
     * @return      目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+method.getName()+"方法");
        return method.invoke(target,args);
    }
}
public static void main(String[] args) {
    SomeService someService = (SomeService) Proxy.newProxyInstance(
            SomeServiceImpl.class.getClassLoader(),
            SomeServiceImpl.class.getInterfaces(),
            new LogInvocationHandler(new SomeServiceImpl())
    );
    someService.doSome();
    System.out.println("---------------------------");
    someService.doOther();

    System.out.println("----------------------------------------");

    OtherService otherService = (OtherService) Proxy.newProxyInstance(
            OtherServiceImpl.class.getClassLoader(),
            OtherServiceImpl.class.getInterfaces(),
            new LogInvocationHandler(new OtherServiceImpl())
    );
    otherService.doSome();
    System.out.println("----------------------");
    otherService.doOther();
}

4.AOP1.X

倾入性

4-1 POM依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>
4-2 通知类型
通知类型描述接口
前置通知在执行核心业务逻辑之前执行MethodBeforeAdvice
后置通知在执行核心业务逻辑之后执行AfterReturningAdvice
异常通知在执行核心业务逻辑出现异常后执行ThrowsAdvice
环绕通知包含以上三种MethodInterceptor
4-3 前置通知
public class LogAdvice implements MethodBeforeAdvice {
    /**
     * 前置通知
     * @param method        目标方法
     * @param args          目标方法的参数列表
     * @param target        目标类
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+target+"中的"+method.getName()+"方法");
    }
}
4-4 后置通知
public class WelcomeAdvice implements AfterReturningAdvice {

    /**
     * 后置通知
     * @param returnValue   目标方法的返回值,当方法没有返回值的时候,返回null
     * @param method        目标方法
     * @param args          目标方法参数列表
     * @param target        目标类
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target+"中的"+method.getName()+"方法执行完成,返回值为:"+returnValue);
    }
}
4-5 异常通知
public class ExceptionAdvice implements ThrowsAdvice {

    /**
     * 异常通知
     * ThrowsAdvice接口中提供了四个方法
     * 这四个方法存在方法重载
     * 我们只需要实现其中的任意一个即可
     * 因此,此处不存在提示,需要手动编写方法
     * @param method    目标方法
     * @param args      方法参数列表
     * @param target    目标类
     * @param ex        异常
     */
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex){
        System.out.println(target+"中的"+method.getName()+"方法执行出现了异常,异常为:"+ex);
    }
}
4-6 环绕通知
public class AroundAdvice implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取目标类
        Object targte = invocation.getThis();
        // 获取目标方法
        Method method = invocation.getMethod();
        // 获取目标方法的参数列表
        Object[] args = invocation.getArguments();
        Object returnValue = null;

        // 计算执行目标方法所耗费的时间
        try {
            System.out.println("环绕通知之前置通知");
            long begin = System.currentTimeMillis();
            returnValue = invocation.proceed();
            long end = System.currentTimeMillis();
            System.out.println("环绕通知之后置通知,执行方法共花费了"+(end-begin)+"毫秒");
        } catch (Throwable ex) {
            System.out.println("环绕通知之异常通知");
        }
        return returnValue;
    }
}
4-7 配置文件
<!-- 配置目标类 -->
<bean id="someServiceTarget" class="aop03.SomeServiceImpl"></bean>
<bean id="otherServiceTarget" class="aop03.OtherServiceImpl"></bean>

<!-- 配置通知类 -->
<bean id="logAdvice" class="aop03.LogAdvice"></bean>
<bean id="welcomeAdvice" class="aop03.WelcomeAdvice"></bean>
<bean id="exceptionAdvice" class="aop03.ExceptionAdvice"></bean>
<bean id="aroundAdvice" class="aop03.AroundAdvice"></bean>

<!--
    配置代理类
    通过Spring提供的FactoryBean帮我们将交叉业务逻辑织入到核心业务逻辑中
    使用的是动态代理的机制
 -->
<bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 配置目标类,通过目标类可以获取目标类的类加载器 -->
    <property name="target" ref="someServiceTarget"></property>
    <!--
        配置实现的接口列表
        其值是一个Class数组
        由于此处的值只有一个,可以使用简单值进行装配
        其值是接口的包名.类名
     -->
    <property name="proxyInterfaces" value="aop03.SomeService"></property>
    <!--
        配置交叉业务逻辑
        在此处即为通知类
        其值是一个可变长字符串
        字符串的值对应的是通知类的id值
    -->
    <property name="interceptorNames">
        <list>
            <!--<value>logAdvice</value>-->
            <!--<value>welcomeAdvice</value>-->
            <!--<value>exceptionAdvice</value>-->
            <value>aroundAdvice</value>
        </list>
    </property>
</bean>


<bean id="otherService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="otherServiceTarget"></property>
    <property name="proxyInterfaces" value="aop03.OtherService"></property>
    <property name="interceptorNames">
        <list>
            <value>logAdvice</value>
            <value>welcomeAdvice</value>
        </list>
    </property>
</bean>
4-8 通知的优先级

在AOP1.X中,通知的优先级是根据配置的位置而定的

谁的配置在前,谁的优先级高

优先级高的前置通知执行的时机更前

优先级高的后置通知执行的时机更后

5.AOP2.X

5-1 特征
  • 非倾入性
  • 完全基于配置
  • 引入了新的命名空间
  • 引入了切点表达式
5-2 POM依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

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

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.4</version>
</dependency>
5-3 通知类型
  • 前置通知
    • 在执行核心业务逻辑之前执行
  • 后置通知
    • 正常返回通知
      • 当方法正常执行结束,没有遇到异常的时候执行
    • 异常通知
      • 当方法运行出现异常的时候执行
    • 后置通知
      • 不管方法是否遇到异常均会执行
  • 环绕通知
    • 包含以上所有
5-4 无参通知
public class LogAdvice {

    public void a(){
        System.out.println("前置通知");
    }

    public void b(){
        System.out.println("正常返回通知");
    }

    public void c(){
        System.out.println("异常通知");
    }

    public void d(){
        System.out.println("后置通知");
    }

}
<!-- 定义目标类 -->
<bean id="someService" class="aop04.SomeServiceImpl"></bean>
<bean id="otherService" class="aop04.OtherServiceImpl"></bean>

<!-- 定义通知 -->
<bean id="logAdvice" class="aop04.LogAdvice"></bean>

<!--
    引入了新的命名空间AOP,用于定义AOP的相关配置
    引入了切点表达式,用于表示当前切面应用于哪些业务逻辑
-->
<aop:config>
    <!--
        定义切入点
        表示需要使用对应交叉业务逻辑的核心业务逻辑有些哪些
        简单来讲,即哪些类中的哪些方法需要使用交叉业务逻辑
        id属性:当前切入点的唯一性标识符
        expression属性:指定切点表达式
    -->
    <!-- 匹配SomeServiceImpl中的所有方法 -->
    <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
    <!--
        定义切面,即通知
        ref属性:定义当前通知所使用的通知类是谁
            其属性值对应的是bean的id属性值
        before子标签:配置前置通知
        after-returning子标签:正常返回通知
        after-throwing子标签:异常通知
        after:后置通知
        method属性:当前通知所对应方法是bean中哪一个
        pointcut-ref属性:当前切面所使用的切入点是谁
    -->
    <aop:aspect ref="logAdvice">
        <aop:before method="a" pointcut-ref="pc1"/>
        <aop:after-returning method="b" pointcut-ref="pc1"/>
        <aop:after-throwing method="c" pointcut-ref="pc1"/>
        <aop:after method="d" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>
5-5 切点表达式
  • within
    • 匹配某个类中的所有方法
    • 语法:within(包名.类名)
  • execution
    • 匹配你想要的一切
    • 可以是类,可以是方法
    • 语法:execution(返回值类型 包名.类名.方法名(参数列表))
    • 支持通配符用法
      • *
        • 用法一:匹配0或者多个字符
        • 用法二:匹配一个单词
      • ..
        • 表示匹配0或者多个参数
  • 支持连接条件
    • 连接条件可以使用within,也可以使用execution
    • and:且的意思,多个条件必须同时满足
    • or:或的意思,多个条件只要满足任意一个即可
    • not:非的意思,匹配不满足条件的方法
      • 使用not前面必须有空格
<!-- 匹配SomeServiceImpl中的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参无返回值的方法 -->
<aop:pointcut id="pc1" expression="execution(void aop04.SomeServiceImpl.doSome())"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(java.lang.String aop04.SomeServiceImpl.doSome(java.lang.String))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(*))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<aop:pointcut id="pc2" expression="execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*())"></aop:pointcut>
<!-- 匹配SomeServiceImpl中除了doSome以外的所有方法 -->
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) and not execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有方法以及OtherServiceImpl中除了doOther以外的方法 -->
<aop:pointcut id="pc1" expression=" not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl) or within(aop04.OtherServiceImpl) and not execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配aop04包下所有除了doSome以外的方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..)) and not execution(* aop04.*.doSome(..))"></aop:pointcut>
5-6 有参通知

在AOP2.X中,如何获取与目标方法相关的信息

此时需要使用有参通知来实现

使用有参通知的时候,其参数必须是:JoinPoint

public class LogAdvice {

    public void before(JoinPoint jp){
        // 获取目标类
        Object target = jp.getThis();
        // 获取目标方法
        Signature signature = jp.getSignature();
        // 获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行");
    }

    public void afterReturning(JoinPoint jp, Object returnValue){
        System.out.println("正常返回通知:"+jp.getSignature().getName()+"方法执行完成,方法返回值为:"+returnValue);
    }

    public void afterThrowing(JoinPoint jp, Exception e){
        System.out.println("异常通知:"+jp.getSignature().getName()+"方法执行出现了异常,异常为:"+e);
    }

    public void after(JoinPoint jp){
        System.out.println("后置通知:"+jp.getSignature().getName()+"方法执行结束");
    }

}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>

<bean id="logAdvice" class="aop05.LogAdvice"></bean>

<aop:config>
    <aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>

    <aop:aspect ref="logAdvice">
        <aop:before method="before" pointcut-ref="pc1"/>
        <!--
            returning属性:指定当前方法中的哪一个参数作为目标方法的返回值
        -->
        <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
        <!--
            throwing属性:指定当前方法中的哪一个参数作为接收目标方法异常的参数
        -->
        <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
        <aop:after method="after" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>
5-7 环绕通知

环绕通知对方法存在三个要求

  • 方法必须有返回值
    • 返回值类型必须是Object
    • 表示的就是目标方法的返回值
  • 方法必须有参数
    • 参数为:proceedingJoinPoint
    • 该参数是JoinPoint的子类
    • 可以获取与目标方法相关的信息
    • 可以用于执行目标方法
  • 方法必须抛出Throwable
public class AroundAdvice {

    public Object around(ProceedingJoinPoint jp) throws Throwable{

        Object returnValue = null;
        System.out.println("环绕通知之前置通知");
        long begin = System.currentTimeMillis();

        try {
            returnValue = jp.proceed();
            long end = System.currentTimeMillis();
            System.out.println("环绕通知之正常返回通知,执行方法共花费了"+(end-begin)+"毫秒");
        } catch (Throwable throwable) {
            System.out.println("环绕通知之异常通知");
        } finally {
            System.out.println("环绕通知之后置通知");
        }
        return returnValue;
    }
}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>
<bean id="aroundAdvice" class="aop05.AroundAdvice"></bean>

<aop:config>
    <aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>

    <aop:aspect ref="aroundAdvice">
        <aop:around method="around" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>
5-8 优先级

默认情况下,AOP2.X的优先级与AOP1.X一致

根据配置的位置而定

谁的配置在前,谁的优先级高

优先级高的前置通知执行时机更前

优先级高的后置通知执行时机更后

在AOP2.X可以手动配置优先级

在切面中进行配置,在aop:aspect标签中通过order属性定义优先级

值越小,优先级越高

<aop:aspect ref="logAdvice" order="2">
    <aop:before method="before" pointcut-ref="pc1"/>
    <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
    <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
    <aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>

<aop:aspect ref="aroundAdvice" order="1">
    <aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值