😉😉 学习交流群:
✅✅1:这是孙哥suns给大家的福利!
✨✨2:我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料
🥭🥭3:QQ群:583783824 📚📚 工作微信:BigTreeJava 拉你进微信群,免费领取!
🍎🍎4:本文章内容出自上述:Spring应用课程!💞💞
💞💞5:以上内容,进群免费领取呦~ 💞💞💞💞
一:AOP编程
Aop编程全称为:Aspect Oriented Programming面向切面编程,什么叫做面向切面编程?oop面向对象编程,pop面向过程编程。
1:面向对象编程含义
以对象为基本单位的程序开发,通过对象间的彼此协同,相互调用来达到开发目的。
2:面向过程编程
以过程为基本单位的层程序开发,以过程之间的相互协同,相互调用来达到开发的目的,这里的古城值得就是方法或者说函数
3:面向切面编程
以切面为基本单位的层程序开发,以切面之间的相互协同,相互调用来达到开发的目的。如果我们能找到切面,并且以切面进行开发,就是以切面进行开发
切面:切入点+额外功能。他们两个被组装后形成切面。这是spring动态代理的开发当中是面向切面编程的基础。spring的动态代理开发其实就是面向切面编程,这两个是完全等价的,Aop编程的背后的技术就是动态代理技术。
4: 什么是Aop编程
Aop编程的本质就是Spring动态代理的开发,通过代理为原始类增加额外的功能,好处就是利于原始类的维护,有了Aop编程之后就可以取代oop这个说法是不正确的,她是从oop上发展来的,不存在取代的关系,他仅仅是oop编程的一种有益的补充。
5:Aop的开发步骤
准备原始对象—准备原始功能-切入点—组装成切面(额外功能+切入点)
这里的开发步骤和spring的动态代理技术是没有任何区别的。
切面:切入点+额外功能
为什么spring的切入点+额外功能叫做切面呢?
形成了一个切面,所以叫切面。
二:AOP底层实现原理
AOP底层实现原理也就是动态代理的底层实现原理,搞明白这个事需要知道几个核心问题
1:核心问题提出
Aop如何帮我们创建这个动态代理类,这个动态代理类是依赖于一门动态字节码技术,动态字节码技术到底是如何通过我们的编码将JVM中的动态代理类创建出来的呢?
Spring在做动态代理或者Aop编程的时候,有一个非常有意思的事,他是怎么把原始对象的id值,拿到的是动态代理对象的id值的呢?
以上的问题就等效与:Spring是如何创建和加工动态代理对象的
2:如何创建动态代理类
Spring创建动态代理类有两种方式,第一种方式是jdk动态代理,第二种是Cglib动态代理
1:JDK动态代理
这里研究的是动态代理对象的创建,而并不是动态代理类的创建,真正的动态代理类的创建是Proxy.newInstance(....)这个方法执行的时候,在jvm内存中创建的动态代理类,上边这个方法获取的是动态代理对象。
Spring的动态代理或者说Spring的Aop的代理对象的创建和使用,他的本质也是使用的是JDK动态代理或者cglib动态代理,他的写法已经在是Spring当中固定好了,所以,原始对象必须得是交由Spring进行创建,另外本质的Spring向外暴露的动态代理的实现方式还是对JDK底层动态代理实现或者cglib动态代理实现的封装。
创建动态代理的三要素:原始对象、额外功能、动态代理和原始类实现相同的接口。
这个动态代理在这一组参数当中,invocationHandler这是一个接口,这个接口对应了我们的额外功能,在这个接口当中有一个invoke方法,实现了这个接口之后呢,这个接口当中有三个方法,方法中有三个参数,如下,执行任何一个方法(原始方法)都需要三个要素,对象、方法、参数列表,不论是普通调用,或者使用反射,都是这么个情况,所以第二个参数是方法对象,第三个参数是方法的参数数组,还需要第一步创建的原始对象,这样就可以执行原始方法了,这个invoke方法的返回值代表的是原始方法的返回值,这里的这个接口当中的invoke方法是原生jdk提供的动态代理的实现方式,spring对我们暴露的实现方式呢有那个MethodInceptor这个接口,这个接口当中的invoke方法中有一个参数对象,可以执行proceed()方法其实这个就是对原生jdk的一个封装,原生的肯定复杂一点,封装之后的肯定更好用。
代理对象要和原始对象实现相同的接口,为什么我们动态代理对象要和原始对象实现相同的接口呢,因为都耦合与一个接口才能迷惑调用者呀
我们创建的是jdk的动态代理,也需要实现和目标类相同的接口,这个条件不是我们来实现,而是proxy.newProxyInstance这个方法类帮我们实现,这里的传入的第二个参数就是干这个用的。提供这个参数是使用,是采用UserService.class对象,就可以使用userService.getClass().getInterfaces();这个形式获取就可以了。
//创建原始对象UserService userService = new UserServiceImpl();//创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy//这个方法的返回值就是这个类的动态代理对象。//这个方法需要三个参数,第一个参数
到这里我们实现动态代理的的三个要素就准备好了,原始对象我们自己创建的
额外功能由于我们采用的事JDK形式的动态代理,所以我们使用的接口。将额外功能书写在这接口实现类中的invoke方法当中,进行响应的处理,实现同样的接口,就是获取原始对象实现的接口传递给这个方法,然而在这个Proxy.newProxyInstatnce()这个方法当中还有一个来加载器对象这样的一个参数,这样的一个参数究竟作用是什么呢,这就是
类加载器的作用:(单纯谈类加载器的作用)
1:通过类加载器把对应类的字节码文件加载到虚拟机当中,
2:通过类加载器创建类的class对象,进而创建这个类的实例。
Class对象记录着这个类最完整的信息。
我们书写的java文件是java的源码文件,java文件经过编译之后的文件称为字节码文件,也就是.class文件,程序的运行是class文件加载到jvm内存当中之后被执行的结果,那么将class字节码文件加载到虚拟机当中是谁干的呢? 是类加载器,字节码进入到虚拟机当中之后,类加载器帮我们加载class文件到jvm内存之后,还会帮助我们创建这个类的Class对象,这里边有这个类最全的信息,进而可以创建这个类的实例。这个类加载器在我们的创建对象的过程当中是尤为重要的,类加载器这么重要,如何获取类加载器呢?
虚拟机会为每一个class分配对应的类加载器,有了这个类加载器之后呢,我们就可以创建这个类的字节码对象了,在动态代理的创建中为什么需要类加载器呢?动态代理也是在虚拟机当中获得动态代理类,进而帮我们创建动态代理对象,这个动态代理类没有源文件,也买有字节码文件,动态代理类他是怎么获取这个类的字节码来创建对象的呢?动态代理是通过动态字节码技术来创建字节码,直接就将字节码写到了虚拟机当中,没有一个加载字节码的过程,这个动态字节码技术在我们的虚拟机当中是怎么实现的?我们是怎么对他进行编程的呢?他暴露给我们程序员的就是Proxy.newProxyInstance()这个方法,这个方法中就会使用这个动态字节技术,作为这个动态字节码技术,而且我们在这个方法中已经传入了所有的创建动态代理类所需要的原材料,那么使用这个动态字节码技术呢,直接将字节码写到了虚拟机当中,所以呢类加载的第一个作用就没有用武之地了,这里的类加载器起的作用个呢是床架你这个动态代理类的Class对象,也就是发挥他的第二个技术,省略了一个加载的过程,有了这个字节码对象我们就能创建这个类的实例了(所有的创建对象都是如此),到了这里呢,就不太好搞了,之前的普通类加载的时候呢是虚拟机根据字节码文件(.class文件)进行分配,现在呢,动态代理类是动态字节码,并没有这个Class文件,没有这个class文件,虚拟机就不会为他分配类加载器,也就没有办法生成Class对象,此时在动态代理创建的过程当中,需要类加载器创建代理类的Class对象,可是动态代理类没哟对应的Class问文件,虚拟机也就不会为他分配类加载器,但是我们又需要,怎么办?借一个,借用的是源文件的类加载对象,来过来为动态代理类创建Class对象的,进而创建这个类的实例,这也是这里为什么需要一个类加载器参数的原因。借用谁的?比方说UserService,或者UserServiceImpl
package com.spring;import com.spring.proxy.User;
import com.spring.proxy.UserService;
import com.spring.proxy.UserServiceImpl;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @Auther: DaShu* @Date: 2021/6/23 19:23* @Description:*/
public class JdkProxyTest {public static void main(String[] args) {//创建原始对象UserService userService = new UserServiceImpl();//创建动态代理基于jdk,创建动态代理基于jdk的方式,需要调用jdk为我们提供的一个工具proxy//这个方法的返回值就是这个类的动态代理对象。//这个方法需要三个参数,第一个参数//为了简单采用内部类的方式来实现invocationhANDLERInvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("-----------这里是代理对象中的额外功能-------------");//目标方法执行Object invoke = method.invoke(userService, args);System.out.println("-----------这里是代理对象中的额外功能-------------");return invoke;}};UserService proxyUserService = (UserService) Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(),userService.getClass().getInterfaces(),invocationHandler);proxyUserService.login("崔磊磊", "12345");proxyUserService.register(new User());//-----------这里是代理对象中的额外功能-------------//UserServiceImpl.login//-----------这里是代理对象中的额外功能-------------//-----------这里是代理对象中的额外功能-------------//UserServiceImpl.register//-----------这里是代理对象中的额外功能-------------}
}
我们说这个了类的类加载器是借的,我们借的谁的都行。
注意:jdk8.0之前呢 内部类访问外部类类的变量需要外部类的成员边量是final修饰的,jdk8.0之后呢会自动加上这个final,
2:CGLIB动态代理
cglib动态代理也是spring底层默认支持的一种动态代理创建方式,他跟jdk动态代理的区别在哪里呢?
jdk动态代理中:原始对象需要实现一个接口,比方说UserServiceImpl 实现 UserService接口,代理对象需要和原始兑现实现相同的接口,为什么会有一个这样的要求呢?第一保证代理类和原始类方法一致,第二点保证代理类当中提供对应方法的新的实现,就可以加入额外功能了,也可以调用原始方法,所以说我们的代理类,这样代理的诉求就实现了。这就是jdk的动态代理。
作为cglib来讲创建动态代理类有什么特点呢?原始类没有实现任何接口,没有实现任何接口的这样的代理类创建代理对象的时候,反正我们的初衷就是我们的代理类和原始类中必须有相同的方法,新的实现里边包括这个额外功能和原始功能的调用,新的代理类可以使用什么来开发呢?cglib要求他所创建的这个代理类要去继承这个原始类,这样来保证你有的这个方法我也有,变相也保证了代理类和原始类有这个共同方法的要求,然后就有机会添加新的实现,调用原始方法super.就可以了,这样的实现的方式不同,但是最终的结果是一致的。cglib采用的事这个父子继承的关系,不管原始类有没有实现接口。
cgilib实现动态代理的的步骤,1:准备原始对象UserService原始对象的userService不需要实现任何的接口,里边准备两个原始方法,第二部,创建代理对象,创建代理对象需要cgilib对应的jar包,这个jar包spring已经默认帮我们引入进来了,这样我们就可以直接使用,cgilib的jar包中最为关键的类是一个Enhancer的接口,作为这个类来讲,我们调用create方法进行创建代理对象,这里边需要几个参数,他所需要的参数和jdk的参数是一模一样的,只不过这里边的接口类型指定的值父类的class对象,还有一个父类的原始功能,Enhancer.setClassLoader()Enhance.setSupperClass,Enhance.setCallBack(MethodInterceptor)这个接口是cgilib中的接口跟当时的spring不是一个接口,
创建这个cglib的Methodinterceptor的接口的时候,采用一个内部类的形式,创建他的子类的实例对象,子类对象需要重写他的intereceptor方法,
package com.spring.cglib;import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @Auther: DaShu* @Date: 2021/6/24 19:41* @Description:*/
public class TestCglib {public static void main(String[] args) {UserService userService = new UserService();//通过cgilib方式进行创建,Enhancer enhancer = new Enhancer();enhancer.setClassLoader(TestCglib.class.getClassLoader());enhancer.setSuperclass(userService.getClass());MethodInterceptor interceptor = new MethodInterceptor() {@Overridepublic Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("----------cgilib日志额外功能。----------");//等同于invocationHandler中的invoke方法,Object invoke = method.invoke(userService, args);return invoke;}};enhancer.setCallback(interceptor);UserService proxyUserService = (UserService)enhancer.create();proxyUserService.login();proxyUserService.register();//----------cgilib日志额外功能。----------//UserService.login//----------cgilib日志额外功能。----------//UserService.register}
}///**
// * @Auther: DaShu
// * @Date: 2021/6/24 19:39
// * @Description: 这个代表原始类。
// */
//public class UserService {
// public void login(){
// System.out.println("UserService.login");
// }
// public void register(){
// System.out.println("UserService.register");
// }
//}
JDK动态代理,Proxy.newProxyInstance()通过接口原始对象的代理类,cgilib动态代理依赖于Eahancer这个类,通过继承原始类创建原始类的代理类,在spring的后续过程中,如果在面试过程中可以把这些方法中的对应的参数说清楚,这是一个大大的加分项。