AOP核心概念
横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
切面(aspect
):类是对物体特征的抽象,切面就是对横切关注点的抽象
连接点(joinpoint
):被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
切入点(pointcut
):对连接点进行拦截的定义
通知(advice
):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
目标对象:代理的目标对象
织入(weave
):将切面应用到目标对象并导致代理对象创建的过程
引入(introduction
):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
Spring对AOP的支持
Spring中AOP代理由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。Spring创建代理的规则为:
1、默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了
2、当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB
JDK动态代理
首先直接调用就可以了,为什么还需要代理。采用代理模式可以有效的将具体的实现与调用方进行解耦,通过面向接口进行编码完全将具体的实现隐藏在内部。
静态代理
先看看什么是静态代理。由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
首先创建一个接口和一个实现类。JDK代理都是基于接口的
public interface ISub {
public void hello(String name);
}
public class SubImpl implements ISub {
@Override
public void hello(String name) {
System.out.println("My name is "+name);
}
}
然后写个代理类,也是实现了ISub
public class SubProxy implements ISub{
ISub sub = new SubImpl();
@Override
public void hello(String name) {
System.out.println("====之前====");
sub.hello(name);
System.out.println("====之后====");
}
}
最后写客户端。这个Client只于Proxy交互,不需要去了解ISub和SubImpl里面的东西了。实现了解耦。
public class Client{
public static void main(String[] args) {
SubProxy subProxy = new SubProxy();
subProxy.hello("lijia");
}
}
那么静态代理有什么缺点呢。
- 代理对象的一个接口只服务一种类型的对象,如果要代理的方法很多,那么必须要为每一种方法都进行代理,静态代理在程序规模稍大就无法胜任了。
- 如果接口增加一个方法,那么所有实现类和代理类都要增加这个方法。增加系统维护复杂度。
动态代理
动态代理的思维模式与之前的一般模式是一样的,也是面向接口进行编码,创建代理类将具体类隐藏解耦,不同之处在于代理类的创建时机不同,动态代理需要在运行时因需实时创建。
接口和实现类还是上面的。
主要是代理类。代理类实现了InvocationHandler。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicSubProxy implements InvocationHandler {
private Object object;
public DynamicSubProxy(Object o){
this.object = o;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====之前====");
method.invoke(object,args);
System.out.println("====之后====");
return null;
}
}
测试类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class DynamicTest {
public static void main(String[] args) {
ISub sub = new SubImpl();
InvocationHandler ih = new DynamicSubProxy(sub);
ISub o = (ISub) Proxy.newProxyInstance(ISub.class.getClassLoader(),new Class[]{ISub.class},ih);
o.hello("lijia");
}
}
总结:
- 首先JDK的动态代理是基本接口实现的。
因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。面向接口编程,利用java的多态特性,实现程序代码的解耦 - 创建代理类的过程。
静态代理和动态代理都需要创建代理类。但是创建的方式不一样。
静态代理创建的代理类需要知道对哪个接口,哪个实现类来创建代理。而动态代理只需要实现一个固定的接口InvocationHandler
。 - 主要通过反射在运行期间创建。而静态代理之前就需要创建好。
CGLIB代理
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLIB代理
还是用上面的实现类,没有接口了
public class SubImpl1{
public void hello(String name) {
System.out.println("My name is "+name);
}
}
cglib代理类
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
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接口方法
@Override
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 CglibTest {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
SubImpl1 sub = (SubImpl1) proxy.getProxy(SubImpl1.class);
sub.hello("lijia");
}
}
总结:
CGLib原理是对指定的目标生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。
AOP代码分析
首先还是通过一个例子来看看。
创建一个切面类
package aop.aspects;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MyAspect1 {
public MyAspect1()
{
System.out.println("MyAspect1 created ");
}
@Before("execution(* spring.services.*.*(..))")
public void traceBusiness() {
System.out.println( "in spring advice ");
}
@Before("execution(* spring.services.*.*(..))")
public void traceBusiness(JoinPoint jp) {
System.out.println("joinpoint [ "+jp.getKind() + " ,declaringTypeName "
+ jp.getSignature().getDeclaringTypeName()+"\r\n this "+jp.getThis().getClass().getName() + "\r\n,target:" + jp.getTarget() + " , method "
+ jp.getSignature().getName() + ",args:" + Arrays.toString(jp.getArgs()));
}
@After("execution(* spring.services.*.*(..))")
public void afterBusiness() {
System.out.println( "after aop");
}
@Around("execution(* spring.services.*.*(..))")
public void traceBusiness(ProceedingJoinPoint jp) throws Throwable {
System.out.println("before enter method "+jp.getSignature().getName());
jp.proceed(jp.getArgs());
System.out.println("after enter method "+jp.getSignature().getName());
}
}
创建接口:
package spring.services;
public interface MyDemoServiceIntf {
public void doBusinessBBB(boolean suc);
}
创建MyDemoServiceIntf 实现类
package spring.services;
import org.springframework.stereotype.Component;
@Component("myTestService2")
public class MyTestService2 {
public MyTestService2()
{
System.out.println("MyTestService2 craeated "+this);
}
public void doBusinessAAA(String[] args,boolean suc)
{
}
public void doBusinessBBB(boolean suc)
{
System.out.println("=====进入了BBBBB");
doBusinessCCC();
}
public void doBusinessCCC()
{
System.out.println("=====CCCCCC");
}
}
配置类
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("spring.services,aop.aspects")
@EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true)
//@EnableAspectJAutoProxy(proxyTargetClass=true)
@Configurable
public class MySpringConfig {
public MySpringConfig()
{
System.out.println("MySpringConfig create d ");
}
}
测试类
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.services.MyTestService2;
public class Test4 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MySpringConfig.class);
MyTestService2 mybean = (MyTestService2) ctx.getBean("myTestService2");
mybean.doBusinessBBB(true);
ctx.close();
}
}
运行结果:
看结果顺序,
首先是几个构造函数初始化。
然后是进入
@Around
的System.out.println("before enter method "+jp.getSignature().getName());再走了
@Before
,走完了之后执行业务代码,
最后
@After
,然后走
@Around
的System.out.println("after enter method "+jp.getSignature().getName());
注解
-
arg()
限制连接点匹配参数为指定类型的执行方法; -
@args()
限制连接点匹配参数由指定注解标注的执行方法; -
execution()
用于匹配是连接点的执行方法; -
this()
限制连接点匹配AOP代理的bean引用为指定类型的类 -
target
限制连接点匹配目标对象为指定类型的类 -
@target()
限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解 -
within()
限制连接点匹配指定的类型 -
@within()
限制连接点匹配特定注解所标注的类型 -
@annotation
限定匹配带有指定注解的连接点
总结
Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。