Spring 基础
学完Spring有大半年了,但是一直没怎么用,复习一下。
Spring是什么?
这个问题,应该是每个人都有每个人的答案,难道仅仅说他就是一个框架?好像不是很对,因为我觉得它不仅仅是一个框架,应该说是一种架构的模式。为了解决程序之间的依赖,简化编程,快速开发具有高拓展性的工程所用的一种的模式。
一个Demo引出的问题
一个很简单的sayhello()的功能:
public class User {
private String name;
public void setName(String name){
this.name=name;
}
public void sayHello(){
System.out.println("Hello:"+name);
}
}
这是传入名字,输出名字:hello.
主函数测试:
public class Main {
public static void main(String[] args) {
User u=new User();
u.setName("???");
u.sayHello();
}
}
这样其实程序不具有可拓展性,因为不能动态的设定name。
如何动态的设定name?
配置文件解决
解决动态的修改名字这个问题,可以采用属性配置文件,新建一个name.properties:
里面填上对应的属性:
name="???"
这样可以通过每次读取配置文件获取信息:
public class Main {
public static void main(String[] args) {
User u=new User();
String name="";
Properties prop=new Properties();
try {
prop.load(Main.class.getClassLoader().getResourceAsStream("name.properties"));
name=prop.getProperty("name");
}catch (Exception e){
e.printStackTrace();
}
u.setName(name);
u.sayHello();
}
}
但是配置文件只能解决一些简单的问题,一旦涉及到类与类之间的依赖,这种配置文件就很难表示。
问题的本质
究其原因:
(1)首先程序之所以需要配置文件是因为在程序启动时,需要的参数值是不确定的,等到确定的时候,程序已经启动了,或者说在运行了。这样怎么把参数给设定一下呢?
(2)程序的依赖关系,其实指的是当前类需要调用另外一个类的方法,这个被调用的类在程序启动之前也是不确定的,或者说在程序运行期间一直是变化的,这样如何在程序变化的时候动态的修改他们的依赖关系?
这两个问题本质上是一个问题。第一种是数据依赖,第二种是类之间的依赖,本质上来说都是一种问题,为了解决这种依赖关系,大牛们提出了解决方案,并且发明了Spring框架。Spring框架能够很好的解决这个问题,如果说Spring的核心任务,我个人认为就是解耦和。
IOC
IOC(Inversion of Control).控制反转,值得是在类之间存在依赖关系时,依赖抽象非具体的类。下面通过一段简单的Spring代码来说明IOC。
Disk这个设备大家很常用,比如简单的就是U盘,还有可移动硬盘等等。电脑通过USB接口和这些外设经行交互,那么电脑对这个接口具有依赖性。显然,电脑只能依赖于一个抽象的接口,不能依赖具体的接口,很容易想到的一个问题,如果依赖U盘实现的接口,那很多其他的外设就无法识别。
下面基于这个场景来用Spring实现。
首先是抽象接口:
package com.spring.TestUSB;
// 只有两个方法,读写。
public interface Disk {
public void read();
public void write();
}
U盘的实现:
package com.spring.TestUSB;
public class Udisk implements Disk{
@Override
public void read() {
System.out.println("read form Udisk");
}
@Override
public void write() {
System.out.println("Wirte to Udisk");
}
}
可移动硬盘的实现
package com.spring.TestUSB;
public class MoveDisk implements Disk {
@Override
public void read() {
System.out.println("read from MoveDisk");
}
@Override
public void write() {
System.out.println("Write to MoveDisk");
}
}
然后是computer。computer肯定要有一个USB的对象作为自己类的成员,然后调用这个成员的方法。
package com.spring.TestUSB;
public class Computer {
private Disk d;
public Disk getDisk(){
return d;
}
public void setDisk(Disk d){
this.d=d;
}
public void test(){
d.read();
d.write();
}
}
r然后是主函数:
package com.spring.TestUSB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
Computer com=new Computer();
Udisk u=new Udisk();
com.setDisk(u);
com.test();
}
}
这样写对吗?显然是错的,因为这在编译期就指定了数据,是无法做到动态修改的。
所以就到了Spring最重要的配置文件了,在当前目录下新建一个beans.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="Udisk" class="com.spring.TestUSB.Udisk" />
<bean id="MoveDisk" class="com.spring.TestUSB.MoveDisk"/>
<bean id="Computer" class="com.spring.TestUSB.Computer">
<property name="disk" ref="MoveDisk"></property>
</bean>
</beans>
简答的解读一下这个配置文件,首先所有的类都是bean。然后类的成员变量可以通过property指定,name指的是这个成员变量的名字,ref指的是这个变量的类型。通过这种文件,可以看出来,参数是可以动态的修改的。那么主函数怎么写呢?
package com.spring.TestUSB;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Computer com= (Computer) applicationContext.getBean("Computer");
com.test();
}
}
可以看出,这个主函数里面没有直接new Computer的对象,直接通过Application对象拿到对象。
这样做的好处
可以看到,这样做之后,参数是可以动态的修改,类直接的依赖关系也是可以通过配置文件指定。也不用自己创建对象。接下来讨论这些机制,首先参数可以动态修改,这个叫做动态注入参数,也叫做DI,依赖反转。不用自己创建类,类是由Spring创建完成,Spring来创建我们需要调用的类,所以把类的创建权交给了Spring,这就是IOC,控制反转。
BeanFactory,IOC容器的理解
什么是容器?在Spring里面可以理解就是管理,存放所有的bean的地方。可以看到,上面得到类的实例,调用的是Application对象,其实Application是Beanfactoy的一个实现。BeanFactory字面意思就是Bean工厂,所有的Bean都是可以通过这里产生的,所以上面的主函数也是可以这样写:
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
public class Main {
public static void main(String[] args) {
Resource r=new ClassPathResource("applicationContext.xml");
BeanFactory factory=new XmlBeanFactory(r);
Computer com= (Computer) factory.getBean("Computer");
com.test();
}
}
因为Application比较好用,所以一般都是用这个
Spring IOC
上面说到,把创建Beans的权利交给了Spring,我们自己不再需要new对象,全部都是Spring帮助我们完成,那么Spring是怎么完成的呢?
其实Spring也是通过调用类的的构造方法完成的。
比如有个people类:
public class People {
private String name;
private int age;
public People(){
System.out.println("?????");
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在默认构造里打印一点东西,然后在配置文件中配置一下,就可的得到:
可以看到,控制台有输出。
并且需要注意的是:Spring调用的默认构造函数来构造对象,如果写了其他构造函数,记得把默认构造加上,否则:
如果是People是这种样子,忘记了某人构造函数:
package BeanFactoryTest;
public class People {
private String name;
private int age;
// public People(){
// System.out.println("?????");
// };
public People(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Test一下就会报错:
显示没有默认构造函数。
Spring 构造出来的对象默认是单例
这就涉及到Spring的作用域了,简单说一点就是Spring构造出来的对象默认都是单例,可以验证:
可以发现打印出来的是true,如果想要多例,在配置文件中修改:
构建对象的几种方法
除了上面Spring直接使用默认构造函数构造对象,还可以自己指定构造方法,基本上还有其他两种方法
静态方法
在People中加入静态方法,返回该类的对象,其他代码不变
然后在配置文件中指定这个方法:
这个方法也叫静态工厂方法,所以命名被命名factory-method.这个方法其实很常见,是为了返回一个指定的,可控制的对象。有关静态工厂方法,用处很多,详细介绍在Effective JAVA中第一章有详细的介绍,有兴趣的可以去看看。
工厂方法
这个方法采用外部工厂的构建对象,首先新建一个PeopleFactory的类,作为用来构建这个类的工厂:
package BeanFactoryTest;
public class PeopleFactory {
public People Create(){
System.out.println("call factory method!");
return new People();
}
}
r然后在配置文件中,首先配置这个工厂:
完成之后配置该方法,将构造方法指向这个工厂提供的方法:
这样也可以完成构造。
参数注入的方法
// tried,算了,这个比较简单,就是通过配置文件来注入参数,不过分为几种不同类型的参数注入,但是都不难,鸽了。