Javaspring 14-18课 spring AOP

spring的两个核心思想就是LOC和AOP,前面已经总结了LOC,这一部分则是关于AOP的知识。

AOP概述

AOP翻译过来叫做面向切面编程,与面向对象编程类似,是一种编程思想。AOP 的全称是“Aspect Oriented Programming”,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。

查了很多资料,如何理解这个面向切面是一个比较难解释的问题。做一个可能不太合理的假设,首先我们将类的执行看做是一条直线,而许多个类就对应许多条直线,这些类都需要同一个功能,按照传统的编程思想,我们的做法是在每个类中都添加这样一个方法,而面向切面就是将这些统一的方法提取出来,不需要在每个类中执行,而是在类执行到一定关键点的时候转而执行这个共同的方法,于是模型就可以理解成有一个切面,所有的直线都穿过了这个切面并且仅有一个交点,即每个类的执行过程在一定的时间点被切面横切,这个切面就是公共部分的代码。
在AOP的知识中常使用下面的概念:
在这里插入图片描述
这里可以结合课本上的图示来理解:
在这里插入图片描述
程序流程用一个直线来表示,那么切面就是一个横向的切割,这个切面实际上就是一段程序代码,这段代码将会被植入到程序流程中。连接点与切入点是两个容易混淆的点,个人是这样理解的,连接点是程序流程中的一些阶段点,可以看做一些关键的时间点,而切入点则是需要插入切面的点,也就是说连接点不一定都是切入点,反过切入点也不一定都是连接点。
在这里插入图片描述
通知则可以看做是某个切入点被横切后所采取的处理逻辑,在切入点处拦截程序之后,通过通知来执行切面。

这么多的概念之中,一定要记住,切面就是要插入的代码,切入点就是要插入代码的位置。剩下的概念在实际使用时多使用就能理解了。

Spring JDK动态代理

AOP的实现有很多种方式,这一节主要是利用java.lang.reflect.Proxy来实现,下面结合实际的例子来演示JDK的动态代理。

首先在MyEclipse中创建一个名称为springDemo03的Web项目,将Spring支持和依赖的JAR包复制到Web项目的WEB-INF/lib目录中,并发布到类路径下。在项目的src目录下创建一个名为com.mengma.dao的包,在该包下创建一个CustomerDao接口,代码如下:

package com.mengma.dao;
public interface CustomerDao {
    
    
    public void add(); // 添加
    public void update(); // 修改
    public void delete(); // 删除
    public void find(); // 查询
}

之后在同一路径下编写这个接口的实现类,代码如下:

package com.mengma.dao;
public class CustomerDaoImpl implements CustomerDao {
    
    
    @Override
    public void add() {
    
    
        System.out.println("添加客户...");
    }
    @Override
    public void update() {
    
    
        System.out.println("修改客户...");
    }
    @Override
    public void delete() {
    
    
        System.out.println("删除客户...");
    }
    @Override
    public void find() {
    
    
        System.out.println("修改客户...");
    }
}

现在我们编写好了程序的执行的过程,即上面图中的那条直线就搞定了,但是还需要编写切面代码,即要插入的代码,在src目录下,创建一个名为com.mengma.jdk的包,在该包下创建一个切面类MyAspect,编辑后如下:

package com.mengma.jdk;
public class MyAspect {
    
    
    public void myBefore() {
    
    
        System.out.println("方法执行之前");
    }
    public void myAfter() {
    
    
        System.out.println("方法执行之后");
    }
}

现在切面和程序执行都准备好了,需要的是将这两个合并在一起,这里采用java.lang.reflect.Proxy实现JDK动态代理,在com.mengma.jdk包下创建一个名为MyBeanFactory的类,代码如下:

package com.mengma.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
public class MyBeanFactory {
    
    
    public static CustomerDao getBean() {
    
    
        // 准备目标类
        final CustomerDao customerDao = new CustomerDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        return (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                new Class[] {
    
     CustomerDao.class }, new InvocationHandler() {
    
    
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
    
    
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
    }
}

这段代码是其中最重要的,就是利用切面和程序流程,制作一个代理类,所谓代理类,可以看作是买房的时候的房屋中介,你不和房主直接沟通,一切都是经由房屋中介来交流,这里也是一个道理,代理类将切面整合入原有的代码,再交给程序去使用,从而实现了AOP,参考这里的代码,这段代码是仿照了loC中的beanfactory,实际上不一定要这么写,再看详细的代码,通过编写方法getBean来实现代理,在这个方法中创建一个目标类,即要插入切面的对象,之后再实例化一个切面对象,之后利用Proxy来整合。
这段代码有很多种写法,首先明确的是返回的结果一定是一个目标类的对象,这一点是毋庸置疑的,而我们要做的就是将切面类对象整合进去,利用的就是Proxy的newProxyInstance()方法,这个方法的第一个参数是当前类的类加载器,本人也不知道什么是类加载器,所以直接按照代码的写法:当前类名.class.getClassLoader(),第二参数是所创建实例的实现类的接口,即代理类需要实现的所有接口的类名,由于所有类名都需要写进去,所以使用了一个数组的形式,第三个参数就是需要增强的方法,在这个方法中我们要编写整合的顺序,按照这个格式写即可。

编写好这个类之后通过junit编写测试类,测试代码和运行结果如下:

扫描二维码关注公众号,回复: 12651015 查看本文章
package com.mengma.jdk;
import org.junit.Test;
import com.mengma.dao.CustomerDao;
public class JDKProxyTest {
    
    
    @Test
    public void test() {
    
    
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        CustomerDao customerDao = MyBeanFactory.getBean();
        // 执行方法
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

在这里插入图片描述
可见增强之前并没有“方法执行之前/后”的输出,现在增强后补上了,实际上就是我们的切面对象的代码整合进了目标对象中。

除此之外,代理类的编写也可以采用下面这种写法:

package com.mengma.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
public class MyBeanFactory {
    
    
    public static CustomerDao getBean() {
    
    
        // 准备目标类
        final CustomerDao customerDao = new CustomerDaoImpl();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 使用代理类,进行增强
        CustomerDao proxySvice = (CustomerDao) Proxy.newProxyInstance(
                MyBeanFactory.class.getClassLoader(),
                 customerDao.getClass().getInterfaces() , new InvocationHandler() {
    
    
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
    
    
                        myAspect.myBefore(); // 前增强
                        Object obj = method.invoke(customerDao, args);
                        myAspect.myAfter(); // 后增强
                        return obj;
                    }
                });
        return proxySvice;
    }
}

Proxy.newProxyInstance方法中的第二个参数换成目标类对象名.getClass().getInterfaces(),这样就不用写成数组的形式了,此外只要最后返回的是一个目标类对象就可以,所以我们也完全可以先获得对象再返回,效果上是一样的。

Spring CGLlB动态代理

CGLIB动态代理是Spring的另一种代理方式,JDK动态代理必须要实现一个或多个接口,如果不希望实现接口,则可以使用CGLIB代理。
两种代理的区别在于JDK动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而CGLIB动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,如果目标对象没有实现了接口,则必须采用CGLIB库。
总而言之,两种动态代理的区别就在于是对类做代理还是对接口做代理。

下面结合实例来演示,在com.mengma.dao包下创建目标类GoodsDao,在类中定义增、删、改、查方法,并在每个方法编写输出语句,由于CGLIB是对类做代理,所以不需要实现接口,直接用类即可,代码如下所示:

package com.mengma.dao;
public class GoodsDao {
    
    
    public void add() {
    
    
        System.out.println("添加商品...");
    }
    public void update() {
    
    
        System.out.println("修改商品...");
    }
    public void delete() {
    
    
        System.out.println("删除商品...");
    }
    public void find() {
    
    
        System.out.println("修改商品...");
    }
}

之后创建代理类,在src目录下创建一个名为com.mengma.cglib的包,该包下创建类MyBeanFactory,如下所示:

package com.mengma.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
public class MyBeanFactory {
    
    
    public static GoodsDao getBean() {
    
    
        // 准备目标类
        final GoodsDao goodsDao = new GoodsDao();
        // 创建切面类实例
        final MyAspect myAspect = new MyAspect();
        // 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
        Enhancer enhancer = new Enhancer();
        // 确定需要增强的类
        enhancer.setSuperclass(goodsDao.getClass());
        // 添加回调函数
        enhancer.setCallback(new MethodInterceptor() {
    
    
            // intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
            @Override
            public Object intercept(Object proxy, Method method, Object[] args,
                    MethodProxy methodProxy) throws Throwable {
    
    
                myAspect.myBefore(); // 前增强
                Object obj = method.invoke(goodsDao, args); // 目标方法执行
                myAspect.myAfter(); // 后增强
                return obj;
            }
        });
        // 创建代理类
        GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
        return goodsDaoProxy;
    }
}

最后在com.mengma.cglib包下创建测试类CGLIBProxyTest,编辑后如下所示:

package com.mengma.cglib;
import org.junit.Test;
import com.mengma.dao.GoodsDao;
public class CGLIBProxyTest {
    
    
    @Test
    public void test() {
    
    
        // 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
        GoodsDao goodsDao = MyBeanFactory.getBean();
        // 执行方法
        goodsDao.add();
        goodsDao.update();
        goodsDao.delete();
        goodsDao.find();
    }
}

最终运行结果如下:
在这里插入图片描述
两种方式的区别主要在于创建代理类,CGLIB采用的是enhancer。

Spring通知类型及使用ProxyFactoryBean创建AOP代理

前面的两节捯饬了半天,实在是搞不清楚这两节和现在这一节是什么关系,JDK动态代理和CGLIB代理在实际使用时貌似不需要由用户来实现,而是利用ProxyFactoryBean通过实现接口的方式来创建AOP代理,所以上面两节难不成讲的是实现的具体细节?个人已经蒙逼,后面边学边用吧。

所谓通知,就是要插入进切入点的代码,切入点和通知一起构成了切面,SpringAOP为通知(Advice)提供了org.aopalliance.aop.Advice接口。
Spring 通知按照在目标类方法的连接点位置,可以分为以下五种类型:
在这里插入图片描述
需要采用哪一种方式,就编写切面方法来实现对应的接口。

Spring创建一个AOP代理的基本方法是使用org.springframework.aop.framework.ProxyFactoryBean,这个类对应的切入点和通知提供了完整的控制能力,并可以生成指定的内容。

ProxyFactoryBean类中的常用可配置属性如下表所示:
在这里插入图片描述
这些属性要在xml文件里面进行修改和配置。

下面结合一个环绕通知的实例来演示一下创建AOP代理,首先,创建AOP代理需要在原来spring的jar包的基础上导入新的两个包,一个在原来spring的大合集里面有,直接搜索导入即可,另一个需要现下载,直接百度搜索包的名字就有下载方式。
spring-aop-3.2.13.RELEASE.jar:是 Spring 为 AOP 提供的实现,在 Spring 的包中已经提供。
com.springsource.org.aopalliance-1.0.0.jar:是 AOP 提供的规范。
将这两个包下载下来放在lib文件夹下即可。

在src目录下创建一个名为com.mengma.factorybean的包,在该包下创建切面类MyAspect,由于这里演示的是环绕通知,所以查前面的表格可以得到我们需要继承MethodInterceptor接口,实现接口中的invoke()方法,而invoke()方法用于确定目标方法mi,并告诉Spring要在目标方法前后执行哪些方法,这里为了简单只输出了一段代码,可以根据需要换作其他的操作,具体代码如下:

package com.mengma.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor {
    
    
    public Object invoke(MethodInvocation mi) throws Throwable {
    
    
        System.out.println("方法执行之前");
        // 执行目标方法
        Object obj = mi.proceed();
        System.out.println("方法执行之后");
        return obj;
    }
}

之后修改配置文件,在com.mengma.factorybean包下创建配置文件applicationContext.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--目标类 -->
    <bean id="customerDao" class="com.mengma.dao.CustomerDaoImpl" />
    <!-- 通知 advice -->
    <bean id="myAspect" class="com.mengma.factorybean.MyAspect" />
    <!--生成代理对象 -->
    <bean id="customerDaoProxy"
     class="org.springframework.aop.framework.ProxyFactoryBean">
     <!--代理实现的接口 -->
        <property name="proxyInterfaces" value="com.mengma.dao.CustomerDao" />
        <!--代理的目标对象 -->
        <property name="target" ref="customerDao" />
        <!--用通知增强目标 -->
        <property name="interceptorNames" value="myAspect" />
        <!-- 如何生成代理,true:使用cglib; false :使用jdk动态代理 -->
        <property name="proxyTargetClass" value="true" />
    </bean>
</beans>

根据代码可以看出,首先我们在容器里面实例化了一个目标类对象,又实例化了一个通知对象,用这个通知去增强目标对象,增强后的仍然是一个实例化的bean,通过实现spring里面的jar包的接口,来完成代理的配置。

最后在com.mengma.factorybean包下创建一个名为FactoryBeanTest的测试类,代码如下:

package com.mengma.factorybean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class FactoryBeanTest {
    
    
    @Test
    public void test() {
    
    
        String xmlPath = "com/mengma/factorybean/applicationContext.xml";
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
                xmlPath);
        CustomerDao customerDao = (CustomerDao) applicationContext
                .getBean("customerDaoProxy");
        customerDao.add();
        customerDao.update();
        customerDao.delete();
        customerDao.find();
    }
}

这里可以看出,我们从spring容器里面取出来的是增强后的bean,而这个增强的过程是由spring实现的,编程人员并没有进行操作,只是实现了通知类和一些配置而已,这样就大大节省了时间。直接获得增强后的bean,使用其中的方法,会在关键的切入点使用通知中的方法,效果如下图:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43849505/article/details/114264231