一、Spring是什么?
Spring是一个开源框架,最早由Rod Johnson创建,它解决的是业务逻辑层和其他各层的松耦合问题。在诞生之初,创建Spring的主要目的是用来替代更加中重量级的企业级Java技术,尤其是EJB。相对于EJB来说,Spring提供了更加轻量级和简单的编程模型。经过十几年的发展,Spring正在扩展其他的领域,如:移动开发、社交API集成、NoSQL数据库、云计算、大数据以及微服务架构。
二、Spring IOC和AOP是什么
spring IOC和AOP是spring最重要的两个概念。
1、那么IOC是什么呢,翻译成中文是控制反转,比如有一个类,在类里面有方法(不是静态的方法),调用类里面的方法,创建类的对象,使用对象调用方法,创建类对象的过程,需要new出来对象把对象的创建不是通过new方式实现,而是交给spring配置创建类对象,就是让spring帮我们创建并管理对象。
2、AOP:面向切面编程,扩展功能不修改源代码实现。
IOC能够让相互协作的软件组件保持松散耦合,而面向切面编程(AOP)允许你把遍布系统各处的功能抽离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点的分离技术。系统由许多不同的组件组成,每一个组件各负责一块特定功能。除了实现自身核心的功能之外,这些组件还经常承担着额外的职责。诸如日志、事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件。
如果将这些关注点分散到多个组件中去,你的代码将会带来双重的复杂性。
- 实现系统关注点功能的代码将会重复出现在多个组件中。
- 组件会因为那些与自身核心业务无关的代码而变得混乱。
由于篇幅问题spring AOP 的相关内容写在了下一篇博客中——Spring AOP的相关内容。
三、Spring IOC
1、使用spring IOC的步骤:
(1)下载并引入spring相关的包:
(2)引入相关配置:
springConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
</beans>
(3)编写相关的类
public interface ComputerSystem {
public void run();
}
public class Windows2000 implements ComputerSystem {
public Windows2000() {
System.out.println("实例化Windows2000");
}
public void run(){
//insert log 记录用户操作日志 校验用户权限
System.out.println("电脑工作在Windows 2000系统上。");
}
}
public class Computer {
private ComputerSystem system;
public Computer(ComputerSystem system) {
this.system = system;
}
public void work(){
this.system.run();
//干点别的
}
}
(4)完成配置:
<?xml version="1.0" encoding="UTF-8"?>
<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-3.0.xsd">
<bean id="windows2000" class="com.prosay.ioc.spring.Windows2000"/>
<bean id="computer" class="com.prosay.ioc.spring.Computer">
<constructor-arg ref="windows2000" />
</bean>
</beans>
(5)编写测试程序
public class Test {
public static void main(String[] args) {
//通过Spring的配置文件来启动Spring框架的上下文
AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/ioc/spring/springConfig.xml");
Computer computer = (Computer) context.getBean("computer");
computer.work();
context.close();
}
}
运行结果输出:
实例化Windows2000
电脑工作在Windows 2000系统上。
在测试类Test中并没有看到new 对象的过程,实际上是spring帮我们创建并管理了computer对象。过程是在配置文件中声明了windows2000这个bean是com.prosay.ioc.spring.Windows2000的实例,声明computer这个bean是com.prosay.ioc.spring.Computer的实例,并且把windows2000这个实例作为构造函数的参数传给了computer。spring会根据bean的类路径通过反射创建出对应的实例并存放在 spring容器
(关于spring容器的内容在下文会有介绍)中。 Test类通过Spring的配置文件来启动Spring框架的上下文,然后就可以从容器中取出对应的实例供程序使用。 注意:
spring实例化bean对象有反射、代理模式等多中方式,但实例化后都会以键值对的形式存放在缓存(spring容器)中,需要的时候可以根据bean id取出相应的对象。
bean标签常用属性补充:
(1)id属性:起名称,id属性值名称任意命名
id属性值,不能包含特殊符号,用于根据id值得到配置对象。一般需要从spring容器取出bean对象时就需要用这个id去匹配。
(2)class属性:创建对象所在类的全路径
(3)name属性:功能和id属性一样的,id属性值不能包含特殊符号,但是在name属性值里面可以包含特殊符号
(4)scope属性
singleton(默认值):单例,prototype:多例
2、属性注入介绍
Spring容器负责创建应用程序中的bean并通过依赖注入来协调这些对象之间的关系。但是作为开发人员,你需要告诉Spring要创建哪些bean并且如何将其装配在一起。当描述bean如何进行装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:
①、在XML中进行显示配置——XML配置。
②、在Java中进行显示配置——JavaConfig。
③、隐式的bean发现机制——注解。 注意:
spring在注入对象的时候分别会先根据以下情况去spring容器匹配bean对象:
①、指定注入的bean id,根据bean id匹配对象。
②、根据注入属性的变量名作为bean id 去匹配bean对象。
③、根据类型匹配对应类型的bean对象。
当三个条件都不满足时注入不成功,这个过程还是比较复杂的,详情可看这篇博客:
Spring 依赖注入优化
(1)通过XML装配Bean
在Spring刚刚出现的时候,XML是描述配置的主要方式。但是XML不再是配置Spring的唯一可选方案。不过,鉴于已经存在那么多基于XML的Spring配置,所以理解如何在Spring中使用XML还是很重要的。上面IOC的例子就用xml的配置方式。
通过XML装配Bean的两种方式: 构造函数注入 和 设值方法注入
以下代码中 下载并引入spring相关的包
和 引入相关配置
这两个步骤就不再重复了,读者如果有需要自己操作即可。
a、构造函数注入
(1)编写相关的类
public interface Person {
}
public class ChinesePerson implements Person {
}
public interface ComputerSystem {
public void run();
}
public class Windows7 implements ComputerSystem {
public Windows2000() {
System.out.println("实例化Windows7");
}
public void run(){
//insert log 记录用户操作日志 校验用户权限
System.out.println("电脑工作在Windows7系统上。");
}
}
public class Windows10 implements ComputerSystem {
public void run() {
System.out.println("启动了windows10系统");
}
}
public class Computer {
private ComputerSystem system;
private Person person;
//1.构造注入
public Computer(ComputerSystem system,ComputerSystem system2, Person person) {
this.system = system;
this.person = person;
}
public void work(){
this.system.run();
}
}
(2)完成配置:
springConfig.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="windows10" class="com.prosay.ioc.xml.Windows10"/>
<bean id="windows7" class="com.prosay.ioc.xml.Windows7"/>
<bean id="person" class="com.prosay.ioc.xml.ChinesePerson"/>
<bean id="computer" class="com.prosay.ioc.xml.Computer">
<!-- 构造函数注入 -->
<constructor-arg ref="person"></constructor-arg>
<constructor-arg ref="windows7"></constructor-arg>
<constructor-arg ref="windows10"></constructor-arg>
</bean>
</beans>
(3)编写测试程序
public class Client {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/ioc/xml/springConfig.xml");
Computer computer = (Computer) context.getBean("computer");//computer和xml配置的bean id对应
computer.work();
}
}
运行结果输出:
启动了windows7系统
b、设值方法注入
其他代码和构造函数注入的方式是一样,只需要改变Computer和springConfig.xml配置文件:
public class Computer {
private ComputerSystem system;
private Person person;
//1.构造注入
// public Computer(ComputerSystem system,ComputerSystem system2, Person person) {
// this.system = system;
// this.person = person;
// }
//2.方法注入
public void setSystem(ComputerSystem system){
this.system = system;
}
public void work(){
this.system.run();
}
}
springConfig.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="windows10" class="com.prosay.ioc.xml.Windows10"/>
<bean id="windows7" class="com.prosay.ioc.xml.Windows7"/>
<bean id="person" class="com.prosay.ioc.xml.ChinesePerson"/>
<bean id="computer" class="com.prosay.ioc.xml.Computer">
<!-- 构造函数注入 -->
<!-- <constructor-arg ref="person"></constructor-arg>
<constructor-arg ref="windows7"></constructor-arg>
<constructor-arg ref="windows10"></constructor-arg> -->
<!-- 设值方法注入 -->
<property name="system" ref="windows10"></property>
</bean>
</beans>
运行结果输出:
启动了windows10系统
(2)、在Java中进行显示配置——JavaConfig。
尽管在很多场景下通过组件扫描和注解实现Spring的自动化配置是更为推荐的方式,但是有时候自动化配置的方案行不通,因此需要显示配置Spring Bean。比如说,你想要将第三方库中的组件装配到你的应用中,在这种情况下,是没有办法在它的类上添加@Component和@Autowired注解的,因此就不能使用自动化装配的方案了。
在这种情况下,你必须要采用显示配置的方式。在进行显示配置的时候,有两种可选方案:JavaConfig和XML,而JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它是Java代码,就像应用程序中的其他Java代码一样。
同时,JavaConfig与其他的Java代码又有所区别,在概念上,它与应用程序中的业务逻辑和领域代码是不同的。尽管它与其他组件一样都使用相同的语言进行表述,但JavaConfig是配置代码。这意味着它不应该包含任何业务逻辑,JavaConfig也不应该侵入到业务逻辑代码中。尽管不是必须的,但通常会将JavaConfig放到单独的包中,使它与其他应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了。
Java代码装配Bean的几种方式:构造函数注入 和 设值方法注入
a、构造函数注入
(1)编写相关的类
public interface ComputerSystem {
public void run();
}
public class Windows10 implements ComputerSystem {
public void run() {
System.out.println("启动了windows10系统");
}
}
public class Computer {
private ComputerSystem system;
//2.方法注入
public void setSystem(ComputerSystem system){
this.system = system;
}
public void work(){
this.system.run();
}
}
(2)JavaConfig配置
// @Configuration : 表示 该类用于 对象的实例化已经依赖注入的实现
@Configuration
public class JavaConfigClass {
//@Bean : 表示该方法是在做实例化的事情 ; 方法返回类型,代表对象实例化的类型; 方法名称,代表的是实例对象的ID
// 该注解注释的方法, Spring 只会调用一次(只会进行一次new操作), 其他位置调用该方法,都是在去缓存中的对象,所以对象还是单例的。
@Bean
public Computer newComputer(){
Computer computer = new Computer();
computer.setSystem(windows10());
return computer;
}
@Bean
public ComputerSystem windows10(){
return new Windows10();
}
}
(3)编写测试程序
public class Client {
public static void main(String[] args) {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(JavaConfigClass.class);
Computer computer = (Computer) context.getBean("newComputer");//bean的名称和JavaConfigClass里面@Bean注解的方法名一致
computer.work();
}
}
运行结果输出:
启动了windows10系统
b、设值方法注入
其他代码不变只要改变Computer和JavaConfigClass
public class Computer {
private ComputerSystem system;
// //1.构造注入
public Computer(ComputerSystem system) {
this.system = system;
}
//2.方法注入
// public void setSystem(ComputerSystem system){
// this.system = system;
// }
public void work(){
this.system.run();
}
}
// @Configuration : 表示 该类用于 对象的实例化已经依赖注入的实现
@Configuration
public class JavaConfigClass {
//@Bean : 表示该方法是在做实例化的事情 ; 方法返回类型,代表对象实例化的类型; 方法名称,代表的是实例对象的ID
// 该注解注释的方法, Spring 只会调用一次(只会进行一次new操作), 其他位置调用该方法,都是在去缓存中的对象,所以对象还是单例的。
@Bean
public Computer newComputer(){
Computer computer = new Computer(windows10());
// Computer computer = new Computer();
// computer.setSystem(windows10());
return computer;
}
@Bean
public ComputerSystem windows10(){
return new Windows10();
}
}
运行结果输出:
启动了windows10系统
(3)隐式的bean发现机制——注解。
Spring从两个角度来实现自动化装配:
组件扫描:Spring会自动发现应用上下文中所创建的bean;
自动装配:Spring自动满足bean之间的依赖。
组件扫描和自动装配组合在一起就能发挥出强大的威力,他们能够将你显示配置降低到最少。
1)、创建可被发现的bean
2)、为组件扫描的bean命名(有指定名字时按指定名字命名,没有指定名字时把类的首字母改成小写作为bean id)
3)、设置组件扫描的基础包
4)、通过为bean添加注解实现自动装配
(1)编写相关的类(包含步骤1)和 2))
public interface ComputerSystem {
public void run();
}
@Component
public class Windows7 implements ComputerSystem {
public void run() {
System.out.println("启动了windows7系统");
}
}
@Component
public class Windows10 implements ComputerSystem {
public void run() {
System.out.println("启动了windows10系统");
}
}
通过为bean添加注解实现自动装配(注入过程就是步骤4))
//@Component : 把这个类交给Spring来管理 负责 实例化 注入 初始化 .... 销毁
@Component
public class Computer {
//@Autowired: 这个属性 需要通过Spring来进行一个注入, 所以 Spring它会去找 跟这个属性相匹配的 实例 然后注入进来。
//1.成员变量注入方式
@Autowired
@Qualifier("windows10")
private ComputerSystem system;
//2.构造函数注入方式
// private ComputerSystem system;
// @Autowired
// public Computer(@Qualifier("windows10") ComputerSystem system){
// this.system = system;
// }
//3.设值方法注入
// private ComputerSystem system;
// @Autowired
// @Qualifier("windows10")
// public void setSystem(ComputerSystem system){
// this.system = system;
// }
public void work(){
this.system.run();
}
}
(2)完成配置(设置组件扫描的基础包):
springConfig.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 配置Spring自动扫描的目录 -->
<context:component-scan base-package="com.prosay.ioc.annotation"/>
</beans>
(3)编写测试程序
public class Client {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/ioc/annotation/springConfig.xml");
Computer computer = (Computer) context.getBean("computer");
computer.work();
}
}
运行结果输出:
启动了windows7系统
上面Computer包含了三种属性注入方式:成员变量注入、 构造函数注入、 设值方法注入 ,上例种三种方式都是注入windows10,其他代码不用改,效果一样,输出结果一样。
补充
Spring注解
@Autowired:自动装配成员变量,@Autowired(required=false) 在找不到匹配Bean时也不报错;
@Qualifier:注释指定注入bean的ID;
@Component:定义一个bean;
@Controller:控制器;
@Service:定义一个service bean;
@Repository:用于将数据访问层(DAO)的类标识为一个bean;
@Scope:设置bean的类型(作用域);
Bean的作用域(类型)
单例(singleton ):在整个应用中,只创建bean的一个实例;
value=ConfigurableBeanFactory.SCOPE_SINGLETON
原型(prototype):每次注入或者通过Spring上下文获取时,都会创建一个新的bean实例;
value=ConfigurableBeanFactory.SCOPE_PROTOTYPE
请求(request):在Web应用中,为每个请求创建一个bean实例;
value=WebApplicationContext.SCOPE_REQUEST
会话(session):在Web应用中,为每个会话创建一个bean实例;
value=WebApplicationContext.SCOPE_SESSION
3、bean实例化过程
(1)通过xml配置bean:
把xml解析成document对象,对每个标签进行解析,获取bean的id名称,类路径,bean属性,对象属性等,然后根据类路径去反射出类对象,并且和id分别为key和value存放到map缓存中,然后再填充属性。
(2)通过扫描注解实例化:
扫描注解要在xml中配置 <context:component-scan base-package="com.packageName"/>
spring会扫描该包下的所有类,然后同样通过反射实例化对象,并放到缓存中,然后填充属性。
补充: 当两个spring对象相互依赖注入时会不会造成死循环的问题,可以去看我的前一篇博客:[spring处理对象相互依赖注入的问题]。(https://blog.csdn.net/w1lgy/article/details/81086171)
(3)bean的生命周期
spring对象的实例化过程要仔细说起来的话是很复杂的,在这里就把一些重要的过程和接口说明一下。
1.Spring对bean进行实例化;
2.Spring将值和bean的引用注入到bean对应的属性中;
3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法;
4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入;
5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来;
6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessBeforeInitialization()方法;
7.如果bean实现了InitializingBean接口,Spring将调用它们的after-PropertiesSet()方法。类似地,如果bean使用init-method声明了初始化方法,该方法也会被调用;
8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的post-ProcessAfterInitialization()方法;
9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
10.如果bean实现了DisposableBean接口,Spring将调用它的destroy()接口方法。同样,如果bean使用destroy-method声明了销毁方法,该方法也会被调用。
三、Spring容器
容器是Spring框架的核心,Spring容器使用IOC管理构成应用的组件,它会创建相互协作的组件之间的关联。使这些对象更简单、干净,更易于理解,更易于重用并且更易于进行单元测试。
Spring容器并不是只有一个,Spring自带了多个容器实现,可归为两种不同类型。Bean工厂(由org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,提供基本的DI支持。
应用上下文(由org.springframework.context.ApplicationContext接口定义)基于BeanFactory构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布应用实践给感兴趣的事件监听者。
Bean工厂对于大多数应用来说往往太低级,因此应用上下文要比Bean工厂更受欢迎。
下面举几个常用的加载配置的例子:
//通过Spring的配置文件来启动Spring框架的上下文
AbstractApplicationContext context = new ClassPathXmlApplicationContext("/com/prosay/ioc/spring/springConfig.xml");
Computer computer = (Computer) context.getBean("computer");
System.out.println("computer.beanId:"+computer.beanId);
computer.work();
context.close();
//FileSystemXmlApplicationContext 的ApplicationContext实现
ApplicationContext context = new FileSystemXmlApplicationContext("E:\\code\\vip_spring01\\src\\main\\java\\com\\prosay\\ioc\\spring\\springConfig.xml");
Computer computer = (Computer) context.getBean("computer");
computer.work();
//web项目 ApplicationContext构建 通过XmlWebApplicationContext来实现
ApplicationContext context = new XmlWebApplicationContext();
//BeanFactory容器实现
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("/com/prosay/ioc/spring/springConfig.xml"));
Computer computer = (Computer) factory.getBean("computer");
computer.work();
System.out.println(computer);
Computer computer2 = (Computer) factory.getBean("computer");
System.out.println(computer2);