The Spring Framework provides a comprehensive programming and configuration model for modern Java-based enterprise applications - on any kind of deployment platform.
A key element of Spring is infrastructural support at the application level: Spring focuses on the “plumbing” of enterprise applications so that teams can focus on application-level business logic, without unnecessary ties to specific deployment environments.
这是Spring官方文档给出的定义,我相信这是最标准的Spring定义,大致意思是说Spring为基于Java的企业应用程序提供了全面的服务,Spring提供了一个专注企业应用程序的“管道”,所以开发人员可以专注于企业开发而不用花时间在部署特定环境上面。
简单说一下Spring的特点:
1、Spring是开源的(免费免费免费!)
2、Spring为简化企业级应用开发而生。使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能
3、Spring是JavaEE/SE的一站式框架。
Spring的优点:
1、方便解耦(Spring像婚姻介绍所,所有的对象都归他管,需要对象只管拿就行)
2、AOP编程的支持(提供切面编程支持,可以实现对程序的权限拦截、运行监控等功能)
3、声明式事务的支持(只需通过配置就可以实现对事务的管理,无需手动编程)
4、方便程序的测试(Spring对Junit4支持,测试很方便)
5、方便集成其他框架(Spring不排斥其他框架,可以集成包括Struts、Hibernate、Mybatis等)
6、降低了javaEE API的使用难度(Spring对JavaEE开发中非常难用的API都进行了封装,使这些API应用难度大大降低)
现在理解这句话可能有点费劲,我们先带着疑问先开始学习,Spring有几个关键字,Spring官方文档也为我们罗列出来了,为了阅读方便,我已经翻译了,想看原文的可以点我~:
核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP。
测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
数据访问:事务,DAO支持,JDBC,ORM,编组XML。
Spring MVC和 Spring WebFlux Web框架。
集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
语言:Kotlin,Groovy,动态语言。
本篇博客作为入门博客,仅围绕以上粗体字进行展开。
Spring有两个非常重要的关键词,一个是IoC(Inversion of Control)控制反转,另一个是DI(Dependency Injection),那么到底是什么意思呢?又有啥用呢?
我们传统的Java开发是先写一个接口,然后写这个接口的实现类,最后再调用实现类里面的方法,比如:
//接口
public interface UserService {
public void sayHello();
}
//实现类
public class UserServiceImpl implements UserService{
// 添加属性:
private String name;
public void sayHello() {
System.out.println("Hello Spring" + name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//测试类
public class SpringDemo1 {
@Test
//传统方式开发
public void demo1(){
// UserService userService = new UserServiceImpl();
UserServiceImpl userService = new UserServiceImpl();
// 设置属性:
userService.setName("张三");
userService.sayHello();
}
}
可以看到,传统开发必须通过手动创建UserServiceImpl类,然后再通过其对象调用类中方法或成员变量,这样开发的耦合度极高,如果有了新的需求,则必须修改源码,比如我想把名字换成李四,则必须打开这个源文件修改userService.setName(“李四”);现在这个例子代码少,可是假如有上千行代码呢?要修改100个数据呢?那是真的很不方便,估计很快就眼花了,再看看把控制权交给Spring有啥好处:
Spring只需要修改配置文件(名称最好是applicationContext.xml,这样比较规范,不过改成别的名字也不错):
<!-- UserService的创建权交给了Spring -->
<bean id="userService" class="com.imooc.ioc.demo1.UserServiceImpl">
<property name="name" value="李四"/>
</bean>
这样,Spring就拥有了创建对象的权利,想要获取李四:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//这里的userService是配置文件中bean的id
UserService userService = (UserService)
applicationContext.getBean("UserService");
userService.sayHello();
ApplicationContext这个类就是婚姻介绍所,是一个很大的容器,里面保存着你的所有存入的对象,需要的时候直接要,然后他就会给你。然后你想要修改名称为李四,则只需要修改配置文件中的value就可以了,是不是很easy。
这样就像原来你找对象,需要自己去找,现在有了Spring,就可以把你要找的对象的类型、属性、参数告诉Spring,然后Spring给你返回一个适合你需求的对象,不需要自己找对象,肯定是非常爽的一件事。
上面这种把创建控制权交由给Spring容器的方式就叫做控制反转,依赖注入是控制反转的一种体现,也就是说比如我当前的bean需要用到其他类或其他类的属性,则Spring像打针一样将其他属性注入到当前的bean中。
既然知道了Spring是跟bean打交道的,那么肯定得知道他们是怎么打交道的:
1、Spring的工厂类
2、Spring的Bean管理(XML方式)
3、Spring的属性注入(XML方式)
4、Spring的Bean管理(注解方式)
5、Spring的属性注入(注解方式)
Spring工厂类有很多,但是我们通常用的就是最下面这两个,一个是ClassPathXmlApplicationContext,这个是在项目下寻找applicationContext.xml配置文件,而FileSystemXmlApplicationContext则是从本机磁盘中的某个盘符下的某个文件中找你的配置文件。
想要使用Bean,先得实例化Bean(初始化)
2.1 使用类构造器实例化(默认无参数)
2.2 使用静态工厂方法实例化(简单工厂模式)
2.3 使用实例工厂模式
上面的例子中我们看到Bean中配置如下:
<bean id="UserService" class="com.bean.ioc.demo1.UserServiceImpl">
<property name="name" value="包头小斌哥"/>
那么id或者name表示的是当前bean的唯一表示,一个bean只能有唯一的id或者name,如果名称含有特殊字符,则需要用name,大多数用id就可以。
class用于设置一个类的完全路径名称,主要作用是IOC容器生成类的实例。
Bean有其对应的作用域scope:
默认是单例模式(singleton)的,也就是不同的bean有相同的内存地址。
假如当前bean是这样的,也就是多例模式(prototype)
<bean id="UserService" class="com.bean.ioc.demo1.UserServiceImpl" scope="prototype">
<property name="name" value="包头小斌哥"/>
</bean>
可以看到他们的内存地址是完全不同的,如果我们删除scope,看看默认是单例模式(singleton)还是多例模式(prototype)
<bean id="UserService" class="com.bean.ioc.demo1.UserServiceImpl">
<property name="name" value="包头小斌哥"/>
</bean>
很明显,内存地址完全相同,也就是说默认的作用域是单例模式(singleton)的。
再来看一下Spring的生命周期:
为什么bean是单例模式才可以调用destroy方法呢?因为多例模式中Spring不知道你到底要销毁哪个对象。
现在看看无参数构造器实现到底是怎么回事:
我先写一个bean:
然后在配置文件下这样配置:
实验结果:
可以看出来Spring默认执行了Bean1的无参构造器。
假设创建第二个类Bean2
它有一个静态工厂,专门生产Bean2:
所谓静态工厂就是有一个静态方法创建Bean2的实例对象而已。
在配置文件中配置如下:
很简单,直接加一个factory-method,后面是你得静态工厂中方法名,我们看一下结果:
显然执行了createBean2这个方法。
所谓实例工厂模式就是去掉static而已,我们创建一个Bean3这个类:
可以看出来就是去掉了static
然后看一下配置文件:
配置文件需要把bean3的工厂也注册了,名字假如就叫bean3Factory,然后把这个bean3Factory再注入到bean3中的factory-bean中(这也是依赖注入),然后再写上factory-method,直接看结果:
对于类成员变量,注入方式有三种
假设有一个Person类,包含一个含参数构造方法:
package com.bean.ioc.CSDN;
public class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
我们在Spring中配置一下:通过constructor-arg来给属性赋值
当然,如果我有两个参数都是相同类型,最好加上name,标识具体设置的是哪个属性:
我们看看打印结果:
可以看出,属性的值已经通过构造器设置注入进去了。
这个就是通过自动生成的setter方法给属性设置,也比较容易理解:
还是刚才的Student,只不过我们把构造器给删了,就是不允许通过构造器注入值:
package com.bean.ioc.CSDN;
public class Student {
private String name;
private Integer age;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
然后配置一下文件:
通过property关键字来给属性进行设值,测试代码还是不变,再看一下结果:
成功啦~
当然还可以通过p名称空间和SpEL方式,也比较简单,但是受制于篇幅,我就简单写一个例子:
首先需要导入一个xmlns在最上方:
xmlns:p="http://www.springframework.org/schema/p"
发现写个p,后面自动弹出了Student类的两个属性name和age
测试结果:
这个功能特别强大,想了解更多的同学可以查一下百度,这里只简单介绍怎么用:
使用就是在property标签下的value中使用#{‘XXX’}的这种格式,当然里面不光可以设置属性,还可以设置bean,指定名属性及其方法,使用静态域字段等。。。
看看测试结果:
这次需要在applicationContext.xml中添加包扫描的代码:
这时候我们创建一个类,比如还是UserService:
需要在类的上方写@Component,表示注解的意思,然后括号中是你的bean的id名称,假设起个名叫userService,然后新建测试类,还是跟之前的一样:
public class SpringDemo1 {
@Test
public void demo1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)applicationContext.getBean("userService");
System.out.println(userService.sayHello("小斌哥"));
}
}
看一下运行结果:
除了component之外,还有三个跟component功能一样的注解,分别是:
假设还是这个类,我们新写一个属性day,并且新写一个方法tomorrowDay();这个时候我们只需要在属性上写一个@Value,就可以给属性赋值了:来看一下测试结果:
在Web开发中我们常常需要在Controller层中导入Service层的对象,假设我们这里需要导入UserDao,也就是Dao层的一个类:
只需要在userDao上加@Autowired,就可以注入userDao了,非常方便。
然后再使用一下这个userDao:
看一下测试类输出结果:
需要注意的是,这个@Autowired默认是按照类型注入的,也就是说,假设我在UserDao写了一个错误的名称:
按理来说应该是报错的,但是由于@Autowired是根据类型寻找UserDao的,所以就算名字错了也会找到他。
这时候,我们需要配合使用@Qualifier(“需要注入的名称”);这样就会根据名称精准确定到需要注入的类,如果名字写错,就会异常:
但是两个注解一起写,太麻烦了,于是诞生出一种新的注解方式:@Resource(name=“名字”),是他们的结合:
这样结果也是OK的,实际开发通常是用@Resource