我记得我在上个月的时候就有说我要学习ssm的源码分析,经历了设计模式和并发的一些基础知识的复习,终于来到了spring,SpringMVC,mybatis的源码分析这个专题,希望能够在年前把这三个框架的源码过一遍吧。我会尽量做到一天一更的。让自己的养成一个习惯,加油。
在学习源码之前,我要复习一下spring的一些基础知识,最近写项目遇到好几次spring的bean注入异常类似的bug(我已经很长时间没有写后端代码了,现在在公司主要写的是react)
1.what is IOC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)
2.为什么要使用spring的IOC(这个我在面试的时候有被问到过)
在日常程序开发过程当中,我们推荐面向抽象编程,面向抽象编程会产生类的依赖,当然如果你够强大可以自己写一个管理的容器,但是既然spring以及实现了,并且spring如此优秀,我们仅仅需要学习spring框架便可。
当我们有了一个管理对象的容器之后,类的产生过程也交给了容器,至于我们自己的app则可以不需要去关系这些对象的产生了。
(面试官和我说,我最想听到的就是spring帮我们维护了类之间的依赖关系)
3.spring实现IOC的思路和方法
spring实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系,前提是对象之间的依赖关系必须在类中定义好,比如A.class中有一个B.class的属性,那么我们可以理解为A依赖了B。既然我们在类中已经定义了他们之间的依赖关系那么为什么还需要在配置文件中去描述和定义呢?spring实现IOC的思路大致可以拆分成3点
- 应用程序中提供类,提供依赖关系(属性或者构造方法)
- 把需要交给容器管理的对象通过配置信息告诉容器(xml、annotation,javaconfig)
- 把各个类之间的依赖关系通过配置信息告诉容器
配置这些信息的方法有三种分别是xml,annotation和javaconfig
维护的过程称为自动注入,自动注入的方法有两种构造方法和setter
自动注入的值可以是对象,数组,map,list和常量比如字符串整形等
解释一下面试可能常问和工作经常用到的两个注解@Autowired @Resource
@Autowired:@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用
@Resource:@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
Spring AOP
spring的一些核心概念很多,平时开发基本用不到,但是面试问起来你是真不会,所以我直接copy别人的算了,真心不想自己写
aspect:一定要给spring去管理 抽象 aspectj->类
pointcut:切点表示连接点的集合 -------------------> 表
(我的理解:PointCut是JoinPoint的谓语,这是一个动作,主要是告诉通知连接点在哪里,切点表达式决定 JoinPoint 的数量)
Joinpoint:连接点 目标对象中的方法 ----------------> 记录
(我的理解:JoinPoint是要关注和增强的方法,也就是我们要作用的点)
Weaving :把代理逻辑加入到目标对象上的过程叫做织入
target 目标对象 原始对象
aop Proxy 代理对象 包含了原始对象的代码和增加后的代码的那个对象
advice:通知 (位置 + logic)
advice通知类型:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
Proceedingjoinpoint 和JoinPoint的区别:
Proceedingjoinpoint 继承了JoinPoint,proceed()这个是aop代理链执行的方法。并扩充实现了proceed()方法,用于继续执行连接点。JoinPoint仅能获取相关参数,无法执行连接点。
JoinPoint的方法
1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
2.Signature getSignature() :获取连接点的方法签名对象;
3.java.lang.Object getTarget() :获取连接点所在的目标对象;
4.java.lang.Object getThis() :获取代理对象本身;
proceed()有重载,有个带参数的方法,可以修改目标方法的的参数
Introductions
perthis
使用方式如下:
@Aspect("perthis(this(com.chenss.dao.IndexDaoImpl))")
要求:
1. AspectJ对象的注入类型为prototype
2. 目标对象也必须是prototype的
原因为:只有目标对象是原型模式的,每次getBean得到的对象才是不一样的,由此针对每个对象就会产生新的切面对象,才能产生不同的切面结果
完成一个aop的功能(基于java配置方式的,xml这种方式真心不推荐了)
1.启动@Aspectj
@Configuration @ComponentScan("con") @EnableAspectJAutoProxy public class AopConfig { }2.声明一个Aspect类,并添加申明一个pointCut和advice通知
@Component @Aspect public class CarryAspect { @Pointcut("execution(* con.carry.aop.*.*(..))") public void poittCut(){ } @Before("poittCut()") public void before(){ System.out.println("before---"); } }这样的话,所有在con.carry.aop报下的所有类和所有方法都会添加这个前置通知,想要改变通知的力度和范围,可以改变切点表达式即可
3.按照第二步的方法就可以实现一个动态代理,下面来分析一个代码
我们来分析一下这个原因,如果正常的类,肯定是能够找到了,就是因为我们使用了动态代理,spring默认的代理使用的是jdk的动态代理,是基于接口实现的,我们在容器中拿到的类已经不是我们最初的那个被代理类了,是被加工过的,所以bean注入的时候是不会成功的
这就牵扯了jdk动态代理源码层面的理解了,后续详细介绍这个问题,如果再有面试官问你,为什么jdk的动态代理是基于接口而不是实现类的?你可以这样说,因为jdk的动态代理,代理对象的底层已经继承了jdk的Proxy这个类,由于java的单继承,他只能是基于接口实现的方式。这样一说,只要面试官知道这个底层,他绝逼不敢瞎逼逼。
我们可以看下,这个代理类的实现。把这个类写到一个.class文件中,看一下。
byte[] carries = ProxyGenerator.generateProxyClass("Carry", new Class[]{BaseService.class});
//把字节数组写到一个class文件
FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/oldshuai/Desktop/test/a.class"));
fileOutputStream.write(carries);
这样就很明白了。