面向切面编程的介绍和使用(Spring框架)

Spring框架整理第二发,AOP的介绍和使用。

AOP概述

AOP:aspect oriented programming:面向切面编程
OOP:Object Oriented programming:面向对象编程
AOP和OOP是两种编程思想,AOP是OOP的一种补充。
面向切面编程:是指在 程序运行期间将某段代码动态的切入到某个类的指定方法的 指定位置。这种编程思想就是面向切面编程。

需求:编写一个简单的计算器,具有加减乘除功能,同时需要在方法执行的时候记录日志。

日志实现

①动态代理
②Spring框架AOP实现

动态代理

package com.wlg.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import com.wlg.inf.Calculator;
public class CalculatorProxy {
    /**
     * @param calculator传入被代理的对象
     * @return 返回的是代理对象
     * 被代理对象执行方法时实际是代理对象在帮他执行这个方法
     */
    public static Calculator getProxy(final Calculator calculator){
        //被代理对象的类加载器,
        ClassLoader classLoader = calculator.getClass().getClassLoader();
        //Class<?>[] 被代理对象所实现的所有接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();

        //InvocationHandler实际去执行被代理对象的方法的处理器
        InvocationHandler h = new InvocationHandler() {
            /**
             * Object proxy:代理对象
             * Method method:代理的方法,即将要执行的方法
             * Object[] args:执行方法的时候要用的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                //获取方法名
                String name = method.getName();
                System.out.println("动态代理方法日志["+name+"方法开始执行]【参数:"+Arrays.asList(args));
                //声明一个引用来接收方法返回值
                Object invoke = null;
                try {
                    //利用反射执行方法
                    invoke = method.invoke(calculator, args);
                    System.out.println("动态代理方法日志["+name+"方法执行结束]【返回值:】"+invoke);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("动态代理方法日志["+name+"方法执行异常]【异常信息:】"+e.getCause().getMessage());
                }
                System.out.println("动态代理方法日志["+name+"方法执行完成了】");
                //返回被代理对象这个方法最终执行的结果
                return invoke;
            }
        };

        //返回代理对象
        Calculator newProxyInstance = (Calculator) Proxy.newProxyInstance(classLoader, interfaces, h);
        return newProxyInstance;
    }
}

从上例可以看出来,动态代理写日志比较麻烦。

AOP


SpringAOP介绍

Spring AOP defaults to using standard J2SE dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
Spring AOP的AOP代理默认使用标准的J2SE动态代理,这样就可以代理任何接口。

SpringAOP作用

将与业务逻辑不相关的模块,比如日志记录,权限验证....,抽象为一个切面,切面里面的方法可以在业务逻辑方法执行的过程中动态的切入进去,不需要修改任何的业务逻辑代码。
AOP为了解决系统(辅助)模块和业务逻辑的耦合度问题。

从图形的角度来介绍Spring AOP的相关概念
我们自制的计算器有加减乘除4个方法,对于Spring来说,我们可以加入切面的位置有4个,方法执行之前,方法正常完成,方法出现异常,方法最终结束
我们可以自定义自己的切面类,然后在指定位置加上注解,Spring框架自动帮我们在切入点处加入指定的方法。


AOP使用(设计一个具有日志功能的计算器)


配置AOP功能
①创建一个切面类,管理一些想要切入的方法,并通过注解设置切入点和切入方法
②开启基于注解的自动扫描,将需要切入方法的组件配置好(切面类,目标类)
③开启基于注解的AOP功能

基于注解的AOP配置


①导入相关jar包
    IoC容器的包
commons-logging-1.1.3.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
   AOP依赖的包
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
//增强版
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

②面向接口编程
package com.wlg.inf;

public interface CalculatorInf {
    //加
    public int sum(int a, int b);
    //减
    public int sub(int a, int b);
    //乘
    public int mul(int a, int b);
    //除
    public int div(int a, int b);
}

计算器的简单实现
package com.wlg.impl;

import org.springframework.stereotype.Component;

import com.wlg.inf.CalculatorInf;

@Component
public class Calculator implements CalculatorInf{

    @Override
    public int sum(int a, int b) {
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public int mul(int a, int b) {
        return a * b;
    }

    @Override
    public int div(int a, int b) {
        return a / b;
    }

}

切面类

使用切面来解决日志记录的问题,创建一个切面类。
日志切面类,创建通知方法
主要基于4个横切关注点,方法执行开始,方法最终执行完成,方法正常返回,方法出现异常,在这些位置加入日志并设置相应的通知方法

@Aspect :标注这是一个切面类
@Order(value = 1):设置切面切入的优先级,数值越小,优先级越高
在方法上加入相应的注解,Spring框架就会在相应的位置切入该通知方法
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法执行最终结束之后执行
@AfterReturning :返回通知,在方法返回结果之后执行(正常返回),若没有异常,会在@After之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行

与动态代理进行比较
try{
    @Before前置通知
    method.invoke();
    @AfterReturning 返回通知
}catch(e){
    @AfterThrowing:异常通知
} finally {
     @After:后置通知
}
    

切入点表达式

使用切入点表达式来告诉框架切入那些方法

切入点表达式形式:

切入点表达式:
语法:execution(方法的完整签名)
execution(访问控制符 返回值 全类名.方法名(参数类型表))
*:用来通配除过参数列表外的东西,访问控制符不用*通配,忽略访问控制符,就是表示所有的控制符都可以
..:表示所有类型,任意多个参数

代码
package com.wlg.aspect;

import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 使用Spring aop管理日志
 * @author LENOVO
 */
@Order(value = 1)
@Aspect
@Component
public class LogAspect {
    //将所有的execution抽取出来 注意.*之间不能有空格
    @Pointcut(value = "execution(* *.*(..))")
    public void myPoint() {
    };

    // 第一个*表示返回类型, 第二个*表示全类名
    @Before(value = "execution(public * * .*(int, int) )")
    public void logStart(JoinPoint joinPoint) {
        // 获取参数列表
        Object[] args = joinPoint.getArgs();
        // 获取方法签名
        Signature signature = joinPoint.getSignature();
        // 获取方法名
        String name = signature.getName();
        System.out.println("日志【" + name + "】" + "开始执行" + "参数列表:"
                + Arrays.asList(args));
    }

    @After(value = "myPoint()")
    public void logEnd(JoinPoint joint) {
        String name = joint.getSignature().getName();
        System.out.println("日志【" + name + "】最终执行完成");
    }

    @AfterReturning(value = "myPoint() )", returning = "rt")
    public void logReturn(JoinPoint joinPoint, Object rt) {
        String name = joinPoint.getSignature().getName();
        System.out.println("日志【" + name + "】方法正常返回" + "返回值:" + rt);
    }

    @AfterThrowing(value = "myPoint()", throwing = "e")
    public void logException(JoinPoint joinPoint, Exception e) {
        String name = joinPoint.getSignature().getName();
        System.out.println("日志【" + name + "】出现异常" + e);
    }
}

③在IoC容器中进行配置
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

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

    <!-- 根据注解将需要管理的bean自动扫描到ioc中,将切面类也加上相应的注解 -->
    <context:component-scan base-package="com.wlg"></context:component-scan>
    <!-- 开启基于注解的aop功能 -->
     <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

④测试
package com.wlg.test;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.wlg.inf.CalculatorInf;

public class AOPTest {

    ApplicationContext ioc = new ClassPathXmlApplicationContext(
            "applicationContext.xml");

    @Test
    public void test() {
        // Calculator bean = ioc.getBean(Calculator.class);
        CalculatorInf bean = ioc.getBean(CalculatorInf.class);
        System.out.println(bean.sum(1, 2));
        System.out.println("---------------");
        System.out.println(bean.div(1, 0));
    }

}

注意:通过实现类类型不能获取到bean,因为IoC容器中保存的是代理对象,不是被代理对象本身
若是被代理的类没有接口,在java中是无法创建代理的,但是可以用Spring添加动态代理,这时用类类型就可以获取到(但是注意:IoC中依旧保存的是代理类对象)

通知方法执行顺序
Before→After→AfterReturning
Before→After→AfterThrowing

常用注解和类介绍

@Pointcut
设置一个通用的切入点表达式,其他方法要使用只需要设置value="方法名"
//将所有的execution抽取出来 注意.*之间不能有空格
@Pointcut(value = "execution(* *.*(..))")
   public void myPoint() {
   };
//使用
 @After(value = "myPoint()")

JoinPoint(包含当前切入的方法的所有信息)
// 获取参数列表
Object[] args = joinPoint.getArgs();
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取方法名
String name = signature.getName();
    
接收返回值
在通知方法参数列表中任意写一个变量,然后声明用这个变量接受返回值(使用returning="res"指定)
 

使用rt来接收返回值

@AfterReturning(value = "myPoint() )",  returning = "rt")
    public void logReturn(JoinPoint joinPoint,  Object rt) {...}
   
 接收异常
在通知方法中任意写一个异常类型变量,然后声明用这个变量接受异常(使用throwing="res"指定)
如果接收异常类型的变量是一个具体的类型只有当这个异常发生后才能接受到

使用e来接收异常
 @AfterThrowing(value = "myPoint()", throwing = "e")
 public void logException(JoinPoint joinPoint, Exception e) {...}

环绕通知
环绕通知创建和创建动态代理类似(最强大的通知方法)
自己调用被代理者的方法,注意:这里调用proceed方法后的返回值要return出去

@Around(value="com.atguigu.aspect.LogAspect.myPoint()")
public Object vre(ProceedingJoinPoint proceedingJoinPoint) {
//ProceedingJoinPoint 中保存了连接点的详细信息
//获取参数
Object[] args = proceedingJoinPoint.getArgs();
Object proceed = null;
try {
//方法调用前(@Before)
System.out.println("proceed执行前");
proceed = proceedingJoinPoint.proceed(args);
} catch (Throwable e) {
//e.printStackTrace();这里需要将异常向上抛出,否则日志处检测不到
@AfterThrowing:异常通知
System.out.println("procees执行出现异常");
throw new RuntimeException(e);
} finally {
//@After:后置通知
System.out.println("process执行最终完成");
}
//方法执行后(正常返回)@AfterReturning 返回通知
System.out.println("proceed执行后");
return proceed;
}

多个切面类
当有多个切面的时候,先切入的后出去。如有一个日志切面和验证切面
执行顺序:日志切面→验证切面→执行方法→验证切面→日志切面

基于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:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 使用xml的方式配置aop功能 -->
    <!-- ①将需要添加切面(例如日志)的bean注册到IoC中 -->
    <bean id="calculator" class="com.wlg.cal.Calculator"></bean>
    <!-- ②将切面类注册到IoC中 -->
    <bean id="logAspect" class="com.wlg.aspect.LogAspect"></bean>
    <!-- ③配置切面类切入位置和切入点,需要使用AOP名称空间 -->
    <aop:config>
        <!-- 设置一个通用切入点表达式 -->
        <aop:pointcut expression="execution(* * .*(..))" id="mypoint"/>
        <aop:aspect id="lA" ref="logAspect">
            <aop:before method="calStart" pointcut-ref="mypoint"/>
            <aop:after method="calEnd" pointcut-ref="mypoint"/>
            <aop:after-returning method="calReturn" pointcut-ref="mypoint" returning="rt"/>
            <aop:after-throwing method="calException" pointcut-ref="mypoint" throwing="e"/>
        </aop:aspect>
        <!-- 当有多个切面类的时候,就多用几个aop:aspect标签 -->
    </aop:config>

</beans>

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值