代理,指的是为一个目标对象提供一个代理对象, 并由代理对象控制对目标对象的引用. 使用代理对象, 是为了在不修改目标对象的基础上,
增强目标对象的业务逻辑。
动态代理
特点:字节流随用随创建,随用随加载。
作用:不修改源码的基础上对方法进行增强。
一、基于接口的动态代理
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:使用Proxy类中的newProxyInstance方法。
创建代理对象的要求:被代理类最多实现一个接口,如果没有则不能使用。
newProxyInstance方法的参数:
1、ClassLoader 类加载器,用于加载代理对象的字节码,和被代理对象使用相同的类加载器。写法固定。
2、Class[] 字节码数组 使得代理对象和被代理对象拥有相同的方法。
3、InvocationHandler 用于提供增强的代码,是让我们写如何代理。我们一般是写一个该接口的实现类,通常情况下是匿名内部类,但不是必须的。写法固定。
Proxy.newProxyInstance(被代理对象.getClass().getClassLoader(),
被代理对象.getClass().getInterfaces(),
new InvocationHandler(){...}
);
接口中仅有一个名为invoke的方法,执行被代理对象的任何接口方法都会经过该方法。方法中各个参数的含义如下:
proxy: 代理对象的引用
method: 当前执行的方法
args[]: 当前执行方法所需的参数
返回值:和被代理对象方法有相同的返回值
在该方法中提供增强的内容代码,如下图所示:
二、基于子类的动态代理
涉及的类:Enhancer
提供者:第三方cglib库
如何创建代理对象:使用Enhancer类中的create方法。
创建代理对象的要求:被代理类不能是最终类
方法的参数:
1、Class 字节码,用于指定被代理对象的字节码。
2、Callback 用于提供增强的代码,在里面写增强的过程。我们一般是写一个该接口的实现类,通常情况下是匿名内部类,但是不是必须的。
此接口的实现类都是谁用谁写。我们一般写的是该接口的子接口实现类:MethodInterceptor。
接口中仅有一个名为invoke的方法,执行被代理对象的任何接口方法都会经过该方法。方法中各个参数的含义如下:
proxy: 代理对象的引用
method: 当前执行的方法
args[]: 当前执行方法所需的参数
methodProxy:当前执行方法的代理对象
返回值:和被代理对象方法有相同的返回值
AOP的概念
全称:面向切面编程。将程序中重复的代码抽取出来,在需要执行的时候使用动态代理的技术,在不对源码进行修改的基础上对已有的方法进行增强。
连接点(joinpoint):被拦截到的点,这些点指的是方法,spring只支持方法类型的连接点。在业务层接口中,其中所有的方法都是连接点,连接业务和增强方法。帮助我们将增强事务控制的代码加到业务当中来,使得方法加上事务支持从而形成完整的业务逻辑。
切入点(pointout):被增强的方法。切入点是连接点的子集。
通知/增强(advice):拦截到连接点后要做的事情。动态代理中invoke方法具有拦截的功能,能拦截被代理对象中所执行的所有方法。拦截后要做的就是提供事务的支持,包括开启事务、执行操作、提交事务、返回结果。
通知的类型:前置通知、后置通知、异常通知、最终通知、环绕通知。
引介:一种特殊的通知,在不修改类代码的前提下,在运行期为类动态地添加一些方法或字段(field)。
目标对象(target):代理的目标对象。
织入(waving):原有的service无法实现事务的支持,于是我们用动态代理技术创建了一个新的对象,返回了一个代理对象。在返回代理对象时,从中加入了事务支持。加入支持的过程称为织入。
代理(proxy):织入后产生的对象。
切面(aspect):切入点和通知的结合。切入点指的是被增强过的方法,通知指的是提供了公共代码的方法。建立切入点方法和通知方法在执行调用的对应关系指的是切面。
哪些方法,在何时执行,需要进行配置,最终形成切面。
基于XML的AOP配置
1、新建新项目,在pom.xml文件当中新增加以下依赖:
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
2、新建bean.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: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.xsd">
</beans>
3、在上述文件当中配置ioc,当有一个类需要增强时,把类对象配置进来。
<bean id="标识" class="全限定类名"></bean>
4、进行spring中基于xml的aop配置。首先把通知类bean也交给spring来管理。
<bean id="标识" class="全限定类名"></bean>
5、使用aop:config标签表明开始aop的配置。
**使用aop:aspect标签表明配置切面。**其中id属性是给切面提供一个唯一标识,ref属性是指定通知类bean的id。
在aop:aspect标签的内部使用对应标签来配置通知的类型。同时建立通知方法和切入点方法之间的关联 如果让"logger"类中的方法“printlog"在切入点方法执行之前执行,则配置前置通知。aop:before表示前置通知,method属性用于指定Logger类中哪个方法是前置通知,pointcut属性用于指定切入点表达式,该表达式的含义是对业务层中哪些方法加强。
切入点表达式的写法:
关键字:execution
表达式: 访问修饰符 返回值 包名.包名…类名.方法名(参数列表)
<aop:config>
<aop:aspect id="给切面提供一个唯一标识" ref="Logger">
<aop:before method="printlog" pointcut="..."/>
</aop:aspect>
</aop:config>
增强的过程就是创建代理对象的过程。配置全过程如下图所示:
测试:
注:
切入点表达式具有省略写法:
1、全通配写法 :
每个方法都会被进行增强。
2、访问修饰符可以被省略。
3、返回值可以使用通配符表示任意返回值。void可以改为‘*’,此时任意返回值均可以用。
4、包名可以使用通配符表示任意包,但有几级包就要用几个‘.’。
5、可以使用‘…’表示当前的包及其子包。
6、类名和方法名都可以使用‘*’来实现通配。
7、参数列表可以直接写数据类型,基本类型直接写名称,引用类型写包名.类名的方式。可以使用通配符表示任何类型,但是必须有参数。可以使用两个点表示有无参数均可,参数可以是任意类型。
8、实际开发中,切入点表达式通常切到业务层实现类下所有方法。
常用通知类型
后置通知和异常通知永远只能执行一个。事务要么提交要么回滚,不可能两个同时发生。
环绕通知
问题:当我们配置好环绕通知后,切入点方法没有执行,而通知方法执行了。
分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
解决:spring框架为我们提供了一个接口:ProceedingJoinPoint。在该接口中有一个方法proceed(),该方法就相当于明确接入点方法。该接口可以作为环绕通知的方法参数。在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。