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操作字节码完成对代理类的创建 主要通过集成目标对象,然后完成重写,再操作字节码