SpringAOP重温

163 阅读4分钟
Aop是什么

与OOP对比,面向切面,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的住业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。

aop的应用场景:

  • 记录日志
  • 权限验证
  • 事务管理
  • 业务处理加锁
  • ...
springAop的底层技术
等级 JDK动态代理(基于接口) CGLIB代理(基于继承)
编译时期的织入还是运行时期的织入? 运行时期织入 运行时期织入
初始化时期织入还是获取对象时期织入? 初始化时期织入 初始化时期织入
springAop和AspectJ的关系

springAop和AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是自己的。

spring AOP提供两种编程风格
@AspectJ support         ------------>利用aspectj的注解
Schema-based AOP support ----------->xml aop:config 命名空间
JDK还是CGLIB

SpringAop默认使用了jdk动态代理,简单的SpringAop实现如下:

@Configuration
@ComponentScan("com.study")
@EnableAspectJAutoProxy(proxyTargetClass = false)
public class AppConfig {
}

其中,@EnableAspectJAutoProxy开启对AspectJ自动代理的支持,proxyTargetClass默认的值就是false,代表使用jdk动态代理完成SpringAop。

@Aspect
@Component
public class Myaspectj {

	@Pointcut("@annotation(com.study.anno.MyAnno)")
	private void anyOldTransfer() {
	}

	@Before("anyOldTransfer()")
	public void advice() {
		System.out.println("Myaspectj");
	}

}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAnno {
}

由于jdk动态代理基于接口来完成,提供如下接口:

public interface Dao {
	void query();
}

先自己实现接口,将daoImpl交给spring容器管理

@Repository("daoImpl")
public class DaoImpl implements Dao {

	@MyAnno
	public void query() {
		System.out.println("DaoImpl");
	}

}
public class Test {

	public static void main(String[] args) {

		AnnotationConfigApplicationContext ann = new AnnotationConfigApplicationContext(AppConfig.class);
		//Dao dao = ann.getBean(DaoImpl.class);
		Dao dao = (Dao) ann.getBean("daoImpl");
		dao.query();

		System.out.println("==================");
		System.out.println(dao instanceof DaoImpl);
		System.out.println(dao instanceof Dao);
		System.out.println(dao instanceof Proxy);
	}
}

运行程序后发现输出如下:

Myaspectj
DaoImpl
==================
false
true
true

说明SpringAop正常完成。从输出就结果可以看到,由于使用的是jdk动态代理完成的SpringAop,所以我们一开始交给spring容器的daoImpl,再通过daoImpl拿出来的bean已经不属于DaoImpl这个bean了。由于是基于接口的,所以肯定是属于Dao的,但是为什么也属于Proxy呢?其实这也是为什么jdk的动态代理要通过接口实现而不能通过继承实现的原因。

查看jdk实现动态代理的关键代码

在Proxy这个类当中首先实例化一个对象ProxyClassFactory,然后在get方法中调用了apply方法,完成对代理类的创建

其中最重要的两个方法

generateProxyClass通过反射收集字段和属性然后生成字节
defineClass0 jvm内部完成对上述字节的load

现在,于是使用ProxyGenerator生成要代理的接口(Dao)的字节码

byte[] proxyDaos = ProxyGenerator.generateProxyClass("ProxyDao", new Class[]{Dao.class});
		File file = new File("d:\\ProxyDao.class");
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(file);
			fileOutputStream.write(proxyDaos);
			fileOutputStream.flush();
			fileOutputStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

然后反编译ProxyDao.class

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.study.dao.Dao;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxyDao extends Proxy implements Dao {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public ProxyDao(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void query() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.study.dao.Dao").getMethod("query");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到,jdk动态代理不但实现了我们自己的Dao接口,还继承了Proxy类,这就说明了为什么上面输出的结果里,为什么dao instanceof DaoImpl的输出结果为false,dao instanceof Proxy的输出结果为true。也说明了为什么jdk动态代理为什么要基于接口,而不能基于继承。

@Configuration
@ComponentScan("com.study")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

将proxyTargetClass修改为true 运行输出如下:

@Configuration
@ComponentScan("com.study")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}

cglib

cglib使用asm操作字节码完成对代理类的创建 主要通过集成目标对象,然后完成重写,再操作字节码