Spring 学习笔记心得(九)AOP面向切面编程

首先我们应该想想为什么要使用aop面向切面编程?面向切面的底层实现是什么?小编在这里举个例子吧
小编首先给出Spring全家桶,方便大家下载使用---->Spring全家桶

1.自定义代理对象代理类以及实现类

1.1 定义接口(ArithmeticCacluetator)

public interface ArithmeticCacluetator {
/*  
   定义加减乘除四个方法
*/
    public void add(int i , int j);
    public void sub(int i , int j);
    public void mul(int i , int j);
    public void div(int i , int j);
}

1.2 创建实现类(ArithmeticCacluetatorImpl),并且这里在每一个方法都输出了操作日志

public class ArithmeticCacluetatorImpl implements ArithmeticCacluetator {
    @Override
    public void add(int i, int j) {
        System.out.println(" i : "+ i + " + "  + " j : " + j + " = " + (i + j));
    }

    @Override
    public void sub(int i, int j) {
        System.out.println(" i : "+ i + " - "  + " j : " + j + " = " + (i - j));
    }

    @Override
    public void mul(int i, int j) {
        System.out.println(" i : "+ i + " * "  + " j : " + j + " = " + (i * j));
    }

    @Override
    public void div(int i, int j) {
        System.out.println(" i : " + i +  " / "  + " j : " + j + " = " + (i / j));
    }
}

1.3 创建测试类:

 ArithmeticCacluetator cacluetator = new ArithmeticCacluetatorImpl();
        cacluetator.add(1,3);
        cacluetator.sub(1,3);
        cacluetator.mul(1,3);
        cacluetator.div(1,3);

输出结果:
i : 1 + j : 3 = 4
i : 1 - j : 3 = -2
i : 1 * j : 3 = 3
i : 1 / j : 3 = 0

不难发现,这样一一输出会十分的麻烦,每个方法里面都要去写一个输出的日志,这样还会导致代码实现类的可读性很差,所以我们应当弃用这种写法,而使用代理类来帮助我们实现


1.4 创建代理类(这里默认大家有动态代理基础哈)

public class ArithmeticCacluetatirProxy {

    public static void main(String[] args) {

        //真实对象
        ArithmeticCacluetatorImpl arithmeticCacluetator = new ArithmeticCacluetatorImpl();
        //代理对象
        ArithmeticCacluetator arithmeticCacluetator1_proxy = (ArithmeticCacluetator) Proxy.newProxyInstance(arithmeticCacluetator.getClass().getClassLoader(), arithmeticCacluetator.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				//这里判断只是用来模拟其他的不同的方法名,以及要执行的对应的日志
                if(method.getName().equals("add")){
                    System.out.println("Welcome to "+method.getName()+" Arithmeticacluetator !!!");
                    Object obj = method.invoke(arithmeticCacluetator, args);
                    return obj;
                }else{
                    System.out.println("Welcome to AutoJugeMethod "+method.getName()+" Arithmeticacluetator !!!");
                    Object obj = method.invoke(arithmeticCacluetator, args);
                    return obj;
                }
            }
        });
        //这里是通过代理类去调用方法
        arithmeticCacluetator1_proxy.add(12,13);
        arithmeticCacluetator1_proxy.sub(12,13);
        arithmeticCacluetator1_proxy.mul(12,13);
        arithmeticCacluetator1_proxy.div(12,13);

    }

输出结果:
method : add
Welcome to add Arithmeticacluetator !!!
i : 12 + j : 13 = 25

method : sub
Welcome to AutoJugeMethod sub Arithmeticacluetator !!!
i : 12 - j : 13 = -1

method : mul
Welcome to AutoJugeMethod mul Arithmeticacluetator !!!
i : 12 * j : 13 = 156

method : div
Welcome to AutoJugeMethod div Arithmeticacluetator !!!
i : 12 / j : 13 = 0

于是我们通过动态代理的形式,简化了日志的输出,这种编程思想,就称之为面向切面编程(AOP)

2.AOP注解:

@Before 前置通知,在方法执行之前执行
@After 后置通知,在方法执行之后执行
@AfterRunning; 返回通知,在方法返回结果之后执行
@AfterThrowing 异常通知,在方法抛出异常之后
@Around 环绕通知,围绕着方法执行

注意:要在Spring中声明AspectJ切面,只需要在I0C容器中将切面声明为Bean实例.当在Spring I0C容器中初始化AspectJ切面之后,Spring I0C容器就会为那些与AspectJ切面相匹配的Bean创建代理.

2.1 搭建基本环境(要声明为IOC容器再声明切面才有效)

2.1.1 创建LogginAspect 类,首先声明为IOC容器(@Componet),再次声明为切面(@Aspect)创建beforeInfo方法。
1.添加注解@Before(在方法执行之前执行 ),所以我们应该是最先看到这个方法里面的内容输出。
2.表达式:execution([方法可见性] xx(具体类全路径).xx(方法) ) 标识在xx包下的xx方法(是否包含参数 如果是包含参数就写出对应的参数类型【int , int …】 , 如果是任意参数就传入 ‘*’[ . . ])

@Component
@Aspect
public class LogginAspect {
		//参数:
		//JoinPoint 切入点 		
    @Before("execution(* cn.itcast.spring.aop.Impl.Service.ArithmeticCacluetatorImpl.*(..))")
    public void beforeInfo(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();//获取方法名称
        Object[] args = joinpoint.getArgs();//获取传入的参数
        System.out.println("before... " + " name : " + name);
        System.out.println("args : " + Arrays.asList(args));
    }

2.1.2 创建spring(bean-aop.xml)配置文件中配置扫描,并且开启aop切面代理模式

 <context:component-scan base-package="cn.itcast.spring.aop.Impl.services"/>

    <aop:aspectj-autoproxy/>

2.1.3 编写测试类AspectTestDemo

            // 创建IOC
        ApplicationContext context = new ClassPathXmlApplicationContext("bean-aop.xml");
        ArithmeticCacluetator bean = context.getBean(ArithmeticCacluetator.class);

        System.out.println(bean.getClass().getName());  //这里获取bean的类型
        bean.add(121,131);

为了清楚测试结果,小编将实现类(ArithmeticCacluetatorImpl)中日志输出全部都删除了,现在测试的是add方法,所以小编在add方法中打印一句话

 @Override
    public void add(int i, int j) {
     System.out.println("我是add方法");
    }

    @Override
    public void sub(int i, int j) {
    }

    @Override
    public void mul(int i, int j) {
    }

    @Override
    public void div(int i, int j) {
    }

输出结果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法

不难发现,我们在方法中输出的这句话是最后面才输出的,所以@Before注解是在方法执行前调用的

2.2 @After后置通知注解配置使用

@After("execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetator.*(..))")
    public void afterMethod(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        System.out.println("after... " + name);
    }

然后再次跑一次测试类:输出结果如下

com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add

2.3 @AfterReturning返回值通知注解配置使用。
注意:
1.只有当没有发生异常时,才会执行此方法。2.添加returning = value 参数 用来接受返回值。3.在方法中声明Object value 返回值

/**
     只有当成功了才会
      @param joinpoint
      @param result
     */
    @AfterReturning(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",returning = "result")
    public void returnMethod(JoinPoint joinpoint,Object result) {
        String name = joinpoint.getSignature().getName();
        System.out.println("return... " + name + " : " + result);
    }

调用测试方法:

输出结果:
com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : null

因为小编的add方法是void类型,所以这里没有返回值,返回的是null。这里小编临时性的将add方法的返回值改为int类型,再次查看输出结果如下:

@Override
    public int add(int i, int j) {
        System.out.println("我是add方法");
        return i+j;
    }

com.sun.proxy.$Proxy19
before… name : add
args : [121, 131]
我是add方法
after… add
return… add : 252

2.4 @AfterThrowing异常通知注解配置使用。
注意:1.只会在指定的异常出现时才会触发。2.定义throwing = value 设置异常参数名称。3.在方法中声明 (异常类型 value)这里小编声明的是空指针异常

 /**
     * 指定什么异常  就在什么异常发生时执行
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
    throwing = "ex")
    public void throwException(JoinPoint joinPoint , NullPointerException ex){
        System.out.println("ERROR ... " + ex);
    }

2.4.1 修改实现类中的除法方法,自定义一个数学异常,来测试异常声明

 @Override
    public void div(int i, int j) {
        int c = i / j;
    }

2.4.2 调用div方法,传入参数【100,0】

输出结果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
Exception in thread “main” java.lang.ArithmeticException: / by zero

2.4.3 修改throwException方法中的异常声明为Exception

@AfterThrowing(value = "execution(* cn.itcast.spring.aop.Impl.services.Service.ArithmeticCacluetatorImpl.*(..))",
    throwing = "ex")
    public void throwException(JoinPoint joinPoint , Exception ex){
        System.out.println("ERROR ... " + ex);
    }

输出结果:
com.sun.proxy.$Proxy19
before… name : div
args : [100, 0]
after… div
ERROR … java.lang.ArithmeticException: / by zero

2.4.4 @Pointcut注解的使用。1.同类目录下可直接引用注解。2.异类目录下需要写全路径。3.好处:因为小编刚才演示的时候每一次都要写一次表达式,并且表达式都是一模一样的,这样就写了很多重复的代码,做着同样的事,避免麻烦,我们可以使用@Pointcut注解来声明表达式,可以在需要的地方直接引用即可

@Pointcut("execution(* cn.itcast.spring.aop.Impl.Order.Service.ArithmeticCacluetator.*(..))")
    public void declareExecutionExpression(){};
    
    @Before("declareExecutionExpression()")
    public void sayMehtod(){
        System.out.println(" VildationAspect aop ... ");
    }

2.4.5 异类下的的引用:

@Before("cn.itcast.spring.aop.Impl.Order.VildationAspect.declareExecutionExpression()")
    public void beforeMethod(){
        System.out.println("before Method ... ");
    }

2.5 @Around环绕通知注解使用(基于动态代理 , 小编这里单独将环绕通知提取出来了,不会受到之前配置过的四个通知的影响)。注意:1.切入点应该声明为ProceedingJoinPoint。2.声明返回值为Object类型。


    /**
      可以获取  String method;
     @param jdp
      @return
     */
    @Around("execution(* cn.itcast.spring.aop.Impl.Around.Services.ArithmeticCacluetatorImpl.*(..))")
    public Object aroundMethod(ProceedingJoinPoint jdp){
       Object result = null;
        String name = jdp.getSignature().getName();
        try {
            //前置通知
            System.out.println("Around Before ... getArgs ...  " + Arrays.asList(jdp.getArgs()));
            result = jdp.proceed();  //method.invoke(); 代理对象调用
            //后置通知
            System.out.println("Around After .... " + name);
            //返回通知
            System.out.println("Around AfterReturing  ... " + result);
        }catch (Throwable e){
            //出现异常,这里不选择手动抛出,用控制台打印
            System.out.println("The Method : " + name +"Error Appentend ...  " + e);
        }
        return result;
    }

2.5.1 调用实现类中的div方法

 bean.div(10,0);

输出结果:
Around Before … getArgs … [10, 0]
The Method : divError Appentend … java.lang.ArithmeticException: / by zero

这里出现了异常,仅仅只执行了前置通知,后面的通知都没有被执行,就直接走进catch中去了

2.5.2 在接口中添加public String show(String name)方法,实现类实现

  @Override
    public String show(String name) {
        System.out.println(name+" comming...");
        return name;
    }

2.5.3 执行show(“Audi”)方法

测试结果如下:
Around Before … getArgs … [Audi]
Audi comming…
Around After … show
Around AfterReturing … Audi

3.AOP 使用XML形式实现:

3.1 因为是在XML中声明使用,所以我们便不需要再添加注解。创建spring配置文件使用(bean-application-xml.xml)这里需要用到aop,context标签,将这段复制到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:p="http://www.springframework.org/schema/p"
       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/aop
       http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.2.xsd">
       </beans>

3.2 声明所有使用到的Bean

   <!-- 配置使用到的bean -->
<bean id="logginAspect" class="cn.itcast.spring.aop_xml.LogginAspect"/>
<bean id="vildationAspect" class="cn.itcast.spring.aop_xml.VildationAspect"/>
<bean id="arithmeticCacluetator" class="cn.itcast.spring.aop_xml.Service.ArithmeticCacluetatorImpl"></bean>
<bean id="logginAround" class="cn.itcast.spring.aop.Impl.Around.LogginAround"></bean>

3.3 切面类LogginAspect

public class LogginAspect {


    public void beforeInfo(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        Object[] args = joinpoint.getArgs();
        System.out.println("before... " + " name : " + name);
        System.out.println("args : " + Arrays.asList(args));
    }

    public void afterMethod(JoinPoint joinpoint) {
        String name = joinpoint.getSignature().getName();
        System.out.println("after... " + name);
    }

    public void returnMethod(JoinPoint joinpoint,Object result) {
        String name = joinpoint.getSignature().getName();
        System.out.println("return... " + name + " : " + result);
    }


    public void throwException(JoinPoint joinPoint , NullPointerException ex){
        System.out.println("ERROR ... " + ex);
    }

}

3.4 VildationAspect类(设置了Order属性,方法是最先被执行的)

public void sayMehtod(){
        System.out.println(" VildationAspect aop ... ");
    }

3.5 配置程序一致:

<aop:config>
        <!--
          配置的基本逻辑:
                1.配置pointcut 公共指向
                2.配置Aspect 注解 ref  - 涉及到的类
                3.配置 [ 前置 , 后置 , 返回值 , 抛出异常 , 环绕通知]
        -->
        <aop:pointcut id="declearExpression" expression="execution(* cn.itcast.spring.aop_xml.Service.ArithmeticCacluetator.*(..))"></aop:pointcut>
       <!-- 切面作用在哪个类上 相当于注解@Aspect  -->
        <aop:aspect ref="logginAspect">
           <!--  基本配置  -->
           <aop:before method="beforeInfo" pointcut-ref="declearExpression"/>
            <aop:after method="afterMethod" pointcut-ref="declearExpression"/>
        	<!-- 这里注意参数一定要对应 -->
            <aop:after-returning method="returnMethod" pointcut-ref="declearExpression" returning="result"/>
            <aop:after-throwing method="throwException" pointcut-ref="declearExpression" throwing="ex"/>
          
        </aop:aspect>
        
        <!--  配置优先级别 -->
    <aop:aspect ref="vildationAspect" order="1">
        <aop:before method="sayMehtod" pointcut-ref="declearExpression"/>
    </aop:aspect>
        <aop:aspect ref="logginAround">
            <aop:around method="aroundMethod" pointcut-ref="declearExpression"></aop:around>
        </aop:aspect>

    </aop:config>

3.6 测试类:

ArithmeticCacluetator bean = (ArithmeticCacluetator) context.getBean("arithmeticCacluetator");
        System.out.println(bean);
        int result = bean.add(12, 3);//返回值为int类型
        System.out.println("result : " + result);

输出结果:
VildationAspect aop … 设置了Order属性,所以sayMethod方法最先被执行
Around Before … getArgs … [12, 3] //前置通知
Around After … add // 后置通知
Around AfterReturing … 15 //返回值通知
result : 15 //自己打印的返回值

好了,这里就是AOP面向切面编程的内容了,虽然有点抽象,但是相信通过不断地学习,可以不断地去了解它

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章