首先,在学习之前,我们先了解一下什么是Spring
1、Spring简介
顾名思义,Spring : 春天 —>给软件行业带来了春天。
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
1.2、优点
1、Spring是一个开源免费的框架 , 容器 .
2、Spring是一个轻量级的框架 , 非侵入式的 .
3、控制反转 IOC , 面向切面 Aop
4、对事物的支持 , 对框架的支持
…
一句话概括:
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)。
1.3、组成
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式 .
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
粗略介绍完Spring,我们就不得不着重学习一下它最重要的两个思想,也就是上文中提到过的控制反转(IOC)和面向切面(AOP)
2、控制反转 Inversion Of Control(IOC)
2.1.分析实现
在学习Spring之前
我们创建对象一般有两种方法:一个是new,一个是反射,并且用什么创建什么。
那么能不能有一个很简便的方法让我们用的时候省去了创建的步骤,直接拿来用呢?
就像我们现实中找对象一样,找一个对象需要花费时间,金钱,还有肾,才可能拥有一个对象,那么能不能真的有一天国家开始分发对象,谁用谁去领呢?别担心,Spring替我们实现了这个梦想!!!
以前,所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
也是Spring最大的特点之一,说白了就像是工厂模式一样,把创建对象的任务交付给工厂,从而降低耦合性,其实Spring中创建Bean,就是用的工厂,叫做BeanFactory。
2.2、IOC本质
实质上,控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法。没有IOC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(Dependency Injection,DI)。
那么说白了,他就是创建对象的,并且给他创建的对象起了个名字叫做Bean,这就是IOC最浅显的意思和道理。
那它究竟是怎么具体创建出来这个叫做Bean的对象的呢?
这就涉及到Spring这里很常见的一个面试题叫做:Bean的生命周期。也就是一个Bean从无到有的过程。
我们去源码里面看一看它!
2.3、SpringBean的生命周期
就像上文中的例子一样,国家的确给你创建出来了对象,你也可以去领,那么国家对象管理局是怎么创建对象的呢?我们是不是也要了解一下,不能拿来主义呢?
下面我们详细介绍一下国家对象管理局创建对象的过程:
1、配置文件
既然要创建对象,那么就要告诉Spring你要创建什么对象,就需要配置文件,Spring中配置对象的属性文件一般有两种形式:XML文件和注解。
推荐大家要用就用一种,免得显得你的对象很low,上身穿着貂,下面大红裤衩,不伦不类。
他们的具体实现方式大家一定都知道了。
一、Xml文件配置:
<bean name="Car" class="com.dyit.entity.Car">
<property name="brand" value="奔驰G"/>
<property name="color" value="黑色"/>
<property name="price" value="2000000.00"/>
</bean>
二、注解配置
<context:component-scan base-package="com.dyit.entity"/>
@Component
public class Car {
private String brand;
private String color;
private Double price;
private Company company;
public void run(){
System.out.println("小火车呜呜呜");
}
}
然后通过以下代码,运行同一种接果:
public void Test(){
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml");
Car car = context.getBean("Car", Car.class);
car.run();
}
这就是一个最简单的IOC实例,在这个过程没有出现new吧!正式代码中我们只是用了一个Get方法,就获取到了对象,因此看得出来,创建对象全部在
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean.xml")
这一行代码中完成,具体怎么操作我们不如点进去看看。
进入源码我们可以看见这样两段代码:
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
其中,setConfigLocations就是用来设置我们的配置文件位置的,也就是我们传进去的参数“...Xml”,而这里面这个
refresh()
才是完成Bean创建的核心方法!
在这个方法中,实现了以下步骤,用来创建一个Bean对象:
步骤一、构建工厂加载Bean定义信息
在读取配置文件,创建对象之前,我们自然要构建一个工厂,不然对象谁来创建?
所以第二步,自然而然就是创建工厂,在refresh方法中,有这样一行代码:
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
在这个方法中,有一个这样的方法:
protected DefaultListableBeanFactory createBeanFactory() {
return new DefaultListableBeanFactory(this.getInternalParentBeanFactory());
}
这就是创建工厂的代步骤,看的出来我们创建工厂的工具类叫做DefaultListableBeanFactory而不是BeanFactory,一定注意!!!
同时还有一段代码叫做:
this.loadBeanDefinitions(beanFactory);
这是用来加载Bean的定义信息的,在这一步就会扫描我们的配置文件,完成Bean定义信息的加载,并不实例化。
Bean对象在Spring底层中时放在Map集合中的,这一步就明确了这个Map集合(我一般叫它单例池)的大小,也就是说将来放多少对象进去。
然后紧接着在创建完成之后,会将Bean工厂丢进这样一行代码中:
this.prepareBeanFactory(beanFactory);
这里面有一大堆Set方法,用来定义Bean工厂的基本信息,我们今天今天讲的时IOC我也就没去看。
步骤二、执行postProcessBeanFactory方法
这个方法是一个空的方法,是留给我们去复写使用的,叫做Bean工厂的处理器,起码我现在的水平,对我而言没多大卵用。执行BeanFactoryPostProcessors一系列方法。
步骤三、执行invokeBeanFactoryPostProcessors方法
然后还有一大堆注册监听等过程,不在赘述
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
步骤五、执行finishBeanFactoryInitialization(beanFactory)方法
这行代码,我只能说重中之重!!!他的注解叫做“实例化所有懒加载的单例对象”,注意实例化三个字,也就是说,前面做了那么多工作,就是为了在这里创建对象出来。具体怎么做呢?
1、这个方法中有一个方法叫做:
beanFactory.preInstantiateSingletons();
在这个里面创建,这个方法中有这样一串代码:
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
注意这个词:beanDefinitionNames,这个东西叫做Bean的定义信息,是在上面第二步构建工厂的时候,加载好的,现在这个方法中拿到了这些定义信息,然后放在一个List集合中,在进行迭代实例化。
2、迭代的时候。利用preInstantiateSingletons方法里面一个叫做getBean的方法里面的doGetBean方法来实例化。这里有一个常识,Java里面凡是带有“do”字样的方法,一般就是干实事的方法。
3、dogetBean方法这里有一个叫做createBean的方法,里面还有一个doCreatBean方法。
doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
再一次看的“do”,说明真的要干实事了!(实例化)
4、这个doCreateBean里还有一个方法(是不是懵逼的一批?),里面这个方法叫做creatBeanInstance,顾名思义:创建Bean的单例对象。
他是这么操作的:
doCreateBean(){
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
}
注意: 这里这里有一个很小的细节,方法中并没有直接返回Bean类型,而是重新创建了一个包装类叫做BeanWrapper用来存放返回的Bean的实例对象。
这个类再后面的AOP中,大展宏图!!!
5、包装类创建好之后,createBeanInstance里面会有一段这样的代码:
Constructor<?>[] ctors = this.determineConstructorsFromBeanPostProcessors(beanClass, beanName);
在这里,先通过反射得到我们要创建对象的类,然后再返回当前类的构造器(构造方法)的一个集合,他返回了一个构造器的集合,所以我们现在有构造器了。
6、拿到构造器之后,会进入一个这个方法之中:
instantiateBean();
注意:这个方法的返回值是BeanWrapper类型的。
7、这个方法中有一个返回方法:
return BeanUtils.instantiateClass(constructorToUse, new Object[0])
8、他的里面有一行这样的代码:
return ctor.newInstance(argsWithDefaultValues);
这行代码我们再熟悉不过,这TMD不就是反射嘛!!!这不就创建出对象了!!!我终于成功了!!!
终于完了???
当然还没有.............
到了这一步,我们的确以及完成了对象的实例化,但是他是个空壳呀!就像上面我们说的向国家身前对象,国家给你创建对象总得有标准嘛!不能人人一样,比如张三喜欢腿长的,李四喜欢胸大的。。。
众口难调也得调!所以下一步就是属性填充!
属性填充:
9、我们再次回到doCreateBeanC这个方法下面,现在我们已经执行完了creatBeanInstance方法,接下来进入一个这个方法:
this.populateBean(beanName, mbd, instanceWrapper);
populate英文单词,你们比我强的人都知道,这个单词的意思就是:填充!
填充当然很简单,一大堆set方法呗!
10、然后,就是紧跟着这行代码的一个代码:
exposedObject = this.initializeBean(beanName, exposedObject, mbd);
这里执行的,是一系列BeanPostProcessor,什么是BeanProCessor呢?这个就是Spring的另一个重要概念AOP所涉及到的了,我们待会说。
走到这里。终于,从上面preInstantiateSingletons方法中的List中实例化了一个对象出来,接下来Spring继续迭代,如法炮制。
步骤六:执行finishRefresh()完成refresh
随后。我们从这么多方法中一个个将实例化的对象一个个返回,我们就完成了所有对象的实例化!!!然后放进我们第一步创建的单例池中,放进IOC容器。
这,就是SpringIOC的原理的源码详解。
这么梳理下来,我都有点晕,所以我们画一个框图,来表示以下
3、面向切面 Aspect-Oriented Programming (AOP)
面向切面编程(AOP)就是纵向的编程。比如业务A和业务B现在需要一个相同的操作,传统方法我们可能需要在A、B中都加入相关操作代码,而应用AOP就可以只写一遍代码,A、B共用这段代码。并且,当A、B需要增加新的操作时,可以在不改动原代码的情况下,灵活添加新的业务逻辑实现。
在实际开发中,比如商品查询、促销查询等业务,都需要记录日志、异常处理等操作,AOP把所有共用代码都剥离出来,单独放置到某个类中进行集中管理,在具体运行时,由容器进行动态织入这些公共代码。
AOP主要一般应用于签名验签、参数校验、日志记录、事务控制、权限控制、性能统计、异常处理等。
3.1、涉及名词
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):切面通知 执行的 “地点”的定义。
连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice,即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .
3.2、实现
使用注解实现
第一步:编写一个注解实现的增强类
package com.kuang.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>