Java技术迷 | 出品
Spring是什么?我想不用我多说,作为一个Java开发者,要是说自己还不知道什么是Spring,那确实需要找找自己的问题了。Spring是一个开源框架,它由Rod Johnson创建,为解决企业应用开发的复杂性而生。
我们可以了解一下Spring产生的背景,当时的企业级Java技术都非常笨重,最为臭名昭著的当属'EJB'
,虽然我没用过,但也有所耳闻,它对企业的业务代码侵入非常严重,而且API复杂难用,基于此,Spring的出现就是为了解决企业开发的复杂性。现如今,Spring已经在各个领域开花结果,对于一些新兴领域,Spring也在涉足和创新,Spring对于Java的发展起到了关键作用。
01
Spring是如何简化开发的?
Spring的理念是无侵入、简单,Spring可以让普通的JavaBean对象实现各种功能,可以说,在Spring中,一切皆组件(Spring将符合规范的JavaBean称为组件)。
在很多框架的设计中,都或多或少对我们的业务代码进行了侵入,比如Struts2中Action需要继承框架的基类,这一就导致了项目中的类是依赖于Struts2框架的,所以当失去了Struts2的环境时,项目会直接报错,这就是侵入性的体现。而Spring则不会强制你实现它的接口或者继承它的类,比如:
public class User{
private Integer id;
private String name;
private Integer age;
private Integer sex;
}
这就是Spring框架中的一个组件,乍一看,它和一个普通的JavaBean对象没有任何区别,甚至都不要求它是一个JavaBean。
·IOC (Inversion of Control):控制反转·DI (Dependency Injection):依赖注入·AOP (Aspect Oriented Programming):面向切面编程
这是Spring中非常重要的三个概念,但这里我们先不对其进行讲解,只需要知道Spring就是借助它们简化了开发。
02
搭建开发环境
现在我们来搭建一个Spring框架的开发环境,亲自体验一下Spring的简洁开发,如果是Maven项目,则只需引入一个依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.4.RELEASE</version>
</dependency>
否则,你需要添加Spring最基础的四个jar包:
1.spring-bean2.spring-context3.spring-core4.spring-expression
暂时先不要管这些jar包究竟是什么,我们先用起来。
引入jar包后,创建一个类:
public class User {
private String name;
private Integer age;
private Pet pet;
}
这是一个普通到不能再普通的Java类了,而在Spring中,这个类就是一个组件,可能大家对组件这个概念仍然很模糊,没关系,我们由此引出Spring中另一个非常重要的概念,IOC容器
。
03
IOC容器
IOC是Inversion of Control的简写,意为控制反转,实际上,这并不是Spring特有的,早在1996年就被提出,它是面向对象编程中的一种设计原则,那我们就来理解一下控制反转,此时我们面临两个问题,控制的是什么?又反转了什么?
来看一个例子:
public static void main(String[] args) {
Pet pet = new Dog('xh',5);
User user = new User('zs', 20, pet);
}
在这个例子中,我们创建了两个对象,而User对象需要一个Pet,其中,创建对象的过程是我们自己进行的,也就是使用new关键字进行创建,这就是对象的创建权(控制权),而控制反转的意思就是将对象的控制权交出去,那交给谁呢?当然是Spring框架了,而Spring框架则借助反射帮助我们创建出对象, 而一个项目中含有大量的对象,Spring如何管理这些对象呢?它就需要用到一个容器,而Map集合的结构正好适合这种业务,事实上,Spring底层就是用的ConcurrentHashMap作为容器管理所有的对象。
在项目的类路径下创建一个spring.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.xsd'>
<bean id='pet' class='com.wwj.spring.demo.Pet'>
<property name='name' value='xh'/>
<property name='age' value='5'/>
</bean>
<bean id='user' class='com.wwj.spring.demo.User'>
<property name='name' value='zs'/>
<property name='age' value='20'/>
<property name='pet' ref='pet'/>
</bean>
</beans>
暂且先别管这配置文件写的是什么,先来测试一下:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext('spring.xml');
User user = (User) context.getBean('user');
System.out.println(user);
System.out.println(user.getPet());
}
运行结果:
User(name=zs, age=20, pet=Pet(name=xh, age=5))
Pet(name=xh, age=5)
大家发现了什么没有?我们并没有去创建User和Pet对象,但是运行结果却能够输出两个实例的内容,这就是因为Spring帮助我们创建了对象,但帮归帮,对象具体的内容还得是我们说了算,所以,Spring的配置文件起到了至关重要的作用。
04
配置文件的简单分析
这是刚才配置文件的内容:
<?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.xsd'>
<bean id='pet' class='com.wwj.spring.demo.Pet'>
<property name='name' value='xh'/>
<property name='age' value='5'/>
</bean>
<bean id='user' class='com.wwj.spring.demo.User'>
<property name='name' value='zs'/>
<property name='age' value='20'/>
<property name='pet' ref='pet'/>
</bean>
</beans>
首先它有一个最大的标签<beans/>
,在<beans/>
中包含了两个<bean/>
标签,这两个标签就是需要创建的对象的具体内容,先来分析Pet对象吧:
<bean id='pet' class='com.wwj.spring.demo.Pet'>
<property name='name' value='xh'/>
<property name='age' value='5'/>
</bean>
id指的是对象的名字,我们都知道,Map是键值对的结构,所以每个对象实际上对应着一个唯一的id,然后是class,class配置的是类的全类名,因为Spring是借助反射创建的对象,所以需要告知Spring该对象的全类名。此时Spring就已经可以创建出一个对象了, 但如果想为对象设置一些初始值,则需要使用<property/>
标签,该标签的配置非常好理解,name指的是对象的属性名,而value指的是对应属性的属性值。
然后是User对象:
<bean id='user' class='com.wwj.spring.demo.User'>
<property name='name' value='zs'/>
<property name='age' value='20'/>
<property name='pet' ref='pet'/>
</bean>
User对象的配置与Pet基本一致,唯一的区别在于:
<property name='pet' ref='pet'/>
来看看User对象的结构:
public class User {
private String name;
private Integer age;
private Pet pet;
}
User对象的pet属性是一个引用类型,所以它在进行配置的时候就比较特殊,你总不能这样配置吧:
<property name='pet' value='@148080bb'/>
所以我们使用ref属性指向某个对象,ref的值是对象的id。
05
注入方式解析
对于刚才设置对象初始值的过程有一个专业的名词,叫注入
,在Spring中一共有三种注入方式,分别是:
1.setter方法注入2.构造方法注入3.接口注入
而<property/>
标签使用的就是setter方法注入,底层调用的是对象的setter方法,那么接下来分别瞧瞧构造方法注入和接口注入是如何实现的。
首先是构造方法注入,先看配置:
<?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.xsd'>
<bean id='pet' class='com.wwj.spring.demo.Pet'>
<constructor-arg name='name' value='xh'/>
<constructor-arg name='age' value='5'/>
</bean>
<bean id='user' class='com.wwj.spring.demo.User'>
<constructor-arg index='0' value='zs'/>
<constructor-arg index='1' value='20'/>
<constructor-arg index='2' ref='pet'/>
</bean>
</beans>
配置上其实没有太多变化,只是把<property/>
标签替换成了<constructor-arg/>
,其中index用于指定当前配置的属性值处于构造方法入参中的位置。然而使用<constructor-arg/>
标签需要注意一些特殊情况,比如:
public class User {
private String name;
private Integer age;
private Integer sex;
}
若是这样进行配置:
<bean id='user' class='com.wwj.spring.demo.User'>
<constructor-arg index='0' value='zs'/>
<constructor-arg index='1' value='1'/>
<constructor-arg index='2' value='20'/>
</bean>
运行结果:
User(name=zs, age=1, sex=20)
这样运行并不会报错,但是仍然有问题,因为1是sex的值,而20才是age的值,所以这样很容易出现位置上的混乱,<constructor-arg/>
标签还提供了一个type属性:
<bean id='user' class='com.wwj.spring.demo.User'>
<constructor-arg type='java.lang.String' value='zs'/>
<constructor-arg type='java.lang.Integer' value='1'/>
<constructor-arg type='java.lang.Integer' value='20'/>
</bean>
当对象中出现了多个相同类型的属性时,仍然会出现值的设置混乱问题,所以最佳方案是使用name属性:
<bean id='user' class='com.wwj.spring.demo.User'>
<constructor-arg name='name' value='zs'/>
<constructor-arg name='sex' value='1'/>
<constructor-arg name='age' value='20'/>
</bean>
这种方式能够一目了然,知道值是设置给对象的哪个属性的,所以推荐使用该方式。
最后是接口注入:
public class Main implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
this.context = context;
}
}
接口注入的方式是通过实现某个接口来注入一些无法轻易得到的对象。
联系客服