Spring啊,可以说是我们大部分Java玩家【最熟悉的陌生人】了吧。八个字形容:似懂非懂,会也不会
你说简单应用,我们大家都会,那真要展开说两句的话,那只能来这么两句:这是第一句,接着是第二句,好了我说完了。
但是啊xdm,据说Spring是一份非常非常非常优秀的源码,不但有丰富的设计模式应用场景,代码写的也很优美,有条理,所以非常推荐大家学习。除了能在日常装逼以外,还能丰富一下见识,提升自己写代码的能力。
阅读对象:有过Spring开发经验的人
(PS:为什么要问这个问题?因为Spring是IOC技术,就算再怎么玩出花来,他也要按照这个基本流程来创建对象。只不过,可以提前告知大家的是,SpringIOC在这个流程之中,穿插了很多自己的逻辑,以此丰富了IOC的功能!)
答:Spring的特性就是IOC跟AOP两大概念!甚至可以这么说:Spring就是实现了AOP技术的IOC容器。(容器,容器,容器)
Q3:什么是IOC,什么是AOP?
答:下面答案来源于百度【文心一言】:
Spring框架通过实现IOC和AOP,使得程序更加模块化、灵活和易于维护。同时,Spring还提供了许多其他模块和功能,如DAO、ORM、WebMVC等,使得它成为一个功能强大的Java开发框架。
从上面的问题里面,我们提到了一个很重要的东西,即:Spring就是实现了AOP技术的IOC容器。并且,也概括地描述了IOC跟AOP的概念。既然我们也知道了,IOC其实也管理了对象的创建,那么说到对象创建,肯定也离不开我们在Q1说的,对象创建的过程。而且,无论对象怎么创建,谁创建,都没办法离开上面的流程的。
事实上,可以提前告诉大家的是,IOC在创建对象的过程,无非就是在上面的对象创建流程中,丰富了一些细节,新增了一些拓展点,为Spring功能实现提供支持。
为了展开对Spring源码的研究,我们这里线大致地串讲一下Spring的一些核心知识点,让大家对Spring的底层一些基础逻辑有个清晰的认知。
我想,经历过SSM/SSH时代的朋友,对下面的代码都不会陌生:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="userService" class="org.example.spring.bean.UserService"/>
beans>
如果真的很陌生也没关系,下面这个可能就相对熟悉一点了:(后面也会围绕这个启动方式的Spring讲解。除了是下面的比较主流,也因为,下面这种方式使用更广、更新,内容相对丰富点!)
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();
System.out.println(userService);
@Component
public class UserService {
public void test() {
System.out.println("这是一个测试方法");
}
}
哈哈,我估计很多直接进入了Java【SpringBoot】时代的朋友,可能连上面这个都没看到过。
那上面段代码是干啥的呢?很简单,就是启动一个Spring容器而已。上面两个不同的启动方式,也仅仅是Bean注册方式不一样。比如前者是通过读取xml
里面的
标签定义,后者是读取的注解式Bean。
到这里,想问大家一个问题,那就是,通过上面第二种方式的代码,你发现了什么?我的发现是:我仅仅只是调用了一行代码,就可以开始使用Spring定义的Bean了,什么依赖注入,AOP啥的,我都没管,直接就可以了。这证明了啥?其实很粗浅,也有点废话,那就是证明:通过这一行代码,里面就帮我完成了所有我们平时使用过的,Spring的基础能力。
根据我们之前学习过Spring相关的操作,简单推测一下,这一行代码里面干了什么。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
首先是第一点【扫描】。我们在项目中,写了这么多Bean,或者说,我项目里面这么多类,哪些是Bean,哪些是普通类,Spring是怎么识别到的?其实道理很简单的,Spring它也没那么智能,想要获取这个类的信息,Spring肯定要【亲自】去看一眼,才能知道这个类的具体信息。你有这么多少个文件,他就要扫描多少个类。关键代码如下:
// 定义需要扫描的基础包名
@ComponentScan("org.tuling.spring")
public class AppConfig {
}
扫描完了所有的文件,那基本上Spring已经能确定,哪些是Bean,哪些是普通类了。接下来,就可以开始创建Bean了,这里,就是所谓的IOC过程
AOP肯定是发生在IOC之后的,如果你们了解设计模式里面的【代理模式】的话,理解这一点并不难。毕竟,如果目标对象功能不完整,代理对象的功能也会收到影响。
之前我们也说了,扫描,就是需要Spring亲自去看一看,哪些是需要被创建Bean的,哪些是不需要的。就拿我们举例用的new AnnotationConfigApplicationContext(AppConfig.class)
来说,大概步骤如下:(简单推测,不详)
AppConfig.class
,读取扫描包的基础路径@Component
、@Service
等注解,则确认为是一个BeanIOC过程,其实在Spring中有个比较专业的术语,叫做:Bean的生命周期。简单的几个字,包含了很多内容。在此之前,大家先看一看【前置知识】里面【JVM对象创建过程】,加深一下印象。
其中有:
ApplicationContextAware
,我们可以直接直译,叫做:ApplicationContext感知,感知ApplicationContext,所以通过设置,我们就可以在Bean里面获取到这个组件了;init
方法。如果大家有过@PostConstruct
以及InitializingBean
使用经验的话,或许知道,创建完Bean之后,在返回Bean之前,还有这一步动作。事实上,根据我们在前言里面说的【IOC在创建对象的过程,无非就是在上面的对象创建流程中,丰富了一些细节,新增了一些拓展点】,所以,在IOC里面还有很多切入点,比如:实例化前、实例化后;初始化前、初始化后等等另外需要注意的是,Bean对象创建出来后:
Spring在基于某个类生成Bean的过程中,需要利用该类的构造方法来实例化得到一个对象,但是如果一个类存在多个构造方法,Spring会使用哪个呢?
Spring的判断逻辑如下:
@Autowired
修饰,有就选择;没有就只能报错了还有一个问题。如果Spring选择了一个有参的构造方法,Spring在调用这个有参构造方法时,需要传入参数,那这个参数是怎么来的呢?答案是:Spring会根据入参的类型和入参的名字去Spring中找Bean对象。
3. 先根据入参类型找,如果只找到一个,那就直接用来作为入参;
4. 如果根据类型找到多个,则再根据入参名字来确定唯一一个;
5. 最终如果没有找到,则会报错,无法创建当前Bean对象。
AOP就是进行动态代理,在创建一个Bean的过程中,Spring在最后一步(放入单例池之前)会去判断当前正在创建的这个Bean是不是需要进行AOP,如果需要则会进行动态代理。
那么,如何判断一个Bean是否需要被AOP代理呢?步骤如下:
利用cglib进行AOP的大致流程:(看上面的代理范式大概就知道了)
然后这里给大家一个【代理模式】的范式:
// 被代理对象
public class ProxyTarget {
public void run() {
System.out.println("这是普通对象的run");
}
}
// 代理对象
public class ProxyModel extends ProxyTarget {
private ProxyTarget proxyTarget;
public void setProxyTarget(ProxyTarget proxyTarget) {
this.proxyTarget = proxyTarget;
}
@Override
public void run() {
System.out.println("我代理对象可以在这里做加强---1");
super.run();
System.out.println("我代理对象也可以在这里做加强---2");
}
}
当我们在某个方法上加了@Transactional注解后,就表示该方法在调用时会开启Spring事务,而这个方法所在的类所对应的Bean对象会是该类的代理对象。
Spring事务的代理对象执行某个方法时的步骤:
Spring事务是否会失效的判断标准:某个加了@Transactional注解的方法被调用时,要判断到底是不是直接被代理对象调用的,如果是则事务会生效,如果不是则失效。