应用场景
适用于那些具有横切逻辑的应用场合,如性能检测、访问控制、事务管理及日志记录
主要解决问题
通过横向抽取机制将这类无法通过纵向继承提醒进行抽象的重复性代码(比如:性能统计、事务管理)抽取到一个独立的模块中,但如何将这些独立的逻辑融合到业务逻辑中以完成和原来一样的业务流程,是问题的关键,这正是AOP要解决的主要问题。
术语
连接点 joinpoint
特定点是程序执行的某个特定位置,如类开始初始前、类初始后,类的某个方法调用前后。一个类或一段程序拥有具有边界性质的特定点,这些代码中的特定点成为“连接点”。Spring仅支持方法的连接点。
连接点有两个信息确定:一是用方法表示的程序执行点,二是用相对位置表示的方位。
切点 pointcut
AOP通过“切点”定位特定的连接点,在Spring中,切点通过org.spring.framework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件,Spring AOP的规则解析引擎负责解析切点所设定的查询条件,找到对应的连接点。
增强 Advice
增强是植入目标类连接点上的一段程序代码,必须嵌入类的某连接点上,并完成一段附加的执行逻辑。
结合执行点的方位信息和切点信息,可以找到特定的连接。
目标对象 Target
增强逻辑的植入目标类。
引介 Introduction
引介是一种特殊的增强,它为类添加一些属性和方法。即使一个业务类原本没有实现某个接口,通过AOP的引介功能,也可以动态的为该业务类添加接口的实现逻辑,让业务类称为这个接口的实现类。
织入 Weaving
织入是将增强添加到目标类的具体连接点的过程。根据不同的实现技术,AOP有3种织入方式:
编译器织入,这要求使用特殊的Java编译器;
类装载期织入,这要求使用特殊的类装载器;
动态代理织入,在运行期为目标类添加增强生成子类的方式。
Spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入。
代理 Proxy
一个类被AOP织入增强后,就产生了一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理既可能是和原类具有相同接口的类,也可能就是原类的子类,所以可以采用和调用原类相同的方式调用代理类。
切面 Aspect
切面由切点和增强(引介)组成,它既包括横切逻辑的定义,也包括连接点的定义。
AOP的工作重心在于如何将增强应用于目标对象的连接点上,主要包括:如何通过切点和增强定位到连接点上;如何在增强中编写切面的代码。
代理机制
Spring AOP使用了两种代理机制:基于JDK的动态代理,基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。
JDK动态代理
Jdk动态代理主要设计java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑编织在一起。
JDK动态代理所用到的代理类在程序调用到代理类对象时才由JVM真正创建,JVM根据传进来的 业务实现类对象 以及 方法名 ,动态地创建了一个代理类的class文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。
我们需要做的,只需指定代理类的预处理、调用后操作即可。
动态代理步骤
:
1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2.创建被代理的类以及接口
3.通过Proxy的静态方法
newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理
4.通过代理调用方法
1、需要动态代理的接口:
package jiankunking;
/**
* 需要动态代理的接口
*/
public interface Subject
{
* 你好
*
* @param name
* @return
*/
public String SayHello(String name);
/**
* 再见
*
* @return
*/
public String SayGoodBye();
}
2、需要代理的实际对
package jiankunking;
/**
* 实际对象
*/
public class RealSubject implements Subject
{
/**
* 你好
*
* @param name
* @return
*/
public String SayHello(String name)
{
return "hello " + name;
}
/**
* 再见
*
* @return
public String SayGoodBye()
{
return " good bye ";
}
}
3、调用处理器实现类(有木有感觉这里就是传说中的AOP啊)
package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 调用处理器实现类
* 每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象
*/
public class InvocationHandlerImpl implements InvocationHandler
{
/**
* 这个就是我们要代理的真实对象
*/
private Object subject;
/**
* 构造方法,给我们要代理的真实对象赋初值
*
* @param subject
*/
public InvocationHandlerImpl(Object subject)
{
this.subject = subject;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用。
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*
* @param proxy 代理类实例
* @param method 被调用的方法对象
* @param args 调用参数
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("在调用之前,我要干点啥呢?");
System.out.println("Method:" + method);
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
Object returnValue = method.invoke(subject, args);
System.out.println("在调用之后,我要干点啥呢?");
return returnValue;
}
}
4、测试
package jiankunking;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 动态代理演示
*/
public class DynamicProxyDemonstration
{
public static void main(String[] args)
{
//代理的真实对象
Subject realSubject = new RealSubject();
/**
* InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发
* 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用.
* 即:要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法
*/
InvocationHandler handler = new InvocationHandlerImpl(realSubject);
}
}
// System.out.println(goodbye);
// String goodbye = subject.SayGoodBye();
System.out.println(hello);
String hello = subject.SayHello("jiankunking");
System.out.println("动态代理对象的类型:"+subject.getClass().getName());
Subject subject = (Subject) Proxy.newProxyInstance(loader, interfaces, handler);
*/
* 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
/**
Class[] interfaces = realSubject.getClass().getInterfaces();
ClassLoader loader = realSubject.getClass().getClassLoader();
ClassLoader loader = realSubject.getClass().getClassLoader();
JDK动态代理的代理对象在创建时,需要使用业务实现类所实现的接口作为参数(因为在后面代理方法时需要根据接口内的方法名进行调用)。如果业务实现类是没有实现接口而是直接定义业务方法的话,就无法使用JDK动态代理了。并且,如果业务实现类中新增了接口中没有的方法,这些方法是无法被代理的(因为无法被调用)。
CGLib动态代理
CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺序织入横切逻辑。
简单的实现举例:
这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。
public
class
SayHello {
public
void
say(){
System.out.println(
"hello everyone"
);
}
}
该类实现了创建子类的方法与代理的方法。getProxy(SuperClass.class)方法通过入参即父类的字节码,通过扩展父类的class来创建代理对象。intercept()方法拦截所有目标类方法的调用,obj表示目标类的实例,method为目标类方法的反射对象,args为方法的动态入参,proxy为代理类实例。proxy.invokeSuper(obj, args)通过代理类调用父类中的方法。
public
class
CglibProxy
implements
MethodInterceptor{
private
Enhancer enhancer =
new
Enhancer();
public
Object getProxy(Class clazz){
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(
this
);
//通过字节码技术动态创建子类实例
return
enhancer.create();
}
//实现MethodInterceptor接口方法
public
Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy)
throws
Throwable {
System.out.println(
"前置代理"
);
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println(
"后置代理"
);
return
result;
}
}
具体实现类:
public
class
DoCGLib {
public
static
void
main(String[] args) {
CglibProxy proxy =
new
CglibProxy();
//通过生成子类的方式创建代理类
SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.
class
);
proxyImp.say();
}
}
代理知识小结
3个需要改进的地方:
目标类的所有方法都添加了横切逻辑,而这并不是我们期望的,我们只期望对业务类中的、
某些特定方法添加横切逻辑;
通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类方法的开始和结束织入代码;
手工编写代理实例的创建过程,在为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用;
Spring AOP主要工作:
通过Pointcut(切点)指定在那些类的那些方法上织入横切逻辑;
通过Advice(增强)描述横切逻辑和方法的具体织入点;
通过Advisor(切面)将pointcut和Advice组装起来;
有了Advisor信息,Spring就可以利用JDK或CGLib动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。