通常我们写代码时,先定义一个Class,然后再别的地方实例化,再进行调用,比如下边的Hello World的例子,类Hello有一个say()方法,用于打印出“Hello World”的字符串,另一个类App,承载程序的入口方法main(),我们在main()方法中实例化Hello类,并调用Hello.say()方法,代码如下:
Hello.java
- package demo;
-
- public class Hello {
- public Hello() {
- }
-
- public void say() {
- System.out.println("Hello world!");
- }
- }
App.java
- package demo;
-
- public class App {
-
- public static void main(String[] args) {
- //手动硬编码创建一个Hello对象
- Hello hello=new Hello();
- hello.say();
-
-
- }
-
- }
上边是入门的编程方式,在程序规模较小的情况是没啥问题的,但程序的规模成指数级增长后,可能成千上万的对象需要创建,可能很多对象的方法还是事务性,你如何在这成千上万个new出来的对象间进行事务处理?当然此处先点到为止,暂时还没进入这么高深的步骤。总之,Sun的工程师们为此发明了一个叫EJB的东西,将某些对象的创建进行统一管理,后来的Spring也继承了这一衣钵,EJB和Spring的差别就是,EBJ很臃肿并依赖程序运行的容器,而Spring则轻巧灵活,直接内嵌在程序中。总之就是不要自己去创建对象,统一由容器创建。
Spring定义了一个叫做ApplicationContext的接口,由其实现类完成对象的创建。按照Spring的思路,将上边的代码稍作转换后写法变成了这样:
- package demo;
-
- import org.springframework.context.support.StaticApplicationContext;
-
- public class App {
-
- public static void main(String[] args) {
-
- //StaticApplicationContext是ApplicationContext的一个具体实现,用在独立运行的程序中
- StaticApplicationContext context = new StaticApplicationContext();
- context.registerBean(Hello.class);
- Hello hello = context.getBean(Hello.class);
- hello.say();
-
- }
-
- }
这里我们看到多了一个StaticApplicationContext类的引用,实例化之后,对这Hello作了一些操作,然后通过这个context就可以获取Hello的实例,这里没有了new Hello()的代码,取而代之的是context.getBean(Class),后边的内容就跟前边一样了。这个例子暂时还没看到Spring有什么好处,但我们可以看到Spring的作用,就是用ApplicationContext接管了对象的实例化。当然,一般情况下我们也不会这样去使用Spring,并不会再实际项目中使用上下文获取对象,而是全权托管给Spring。
Spring用了反射机制创建对象,实际效果跟new一个对象是一样的。所以ApplicationContext在创建实例的时候也是调用构造方法的。为了演示Spring的托管效果,我们可以把say()方法在构造方法中调用,这样创建对象时就会调用say()方法,我们改一下Hello及App的代码:
Hello.java
- package demo;
-
- public class Hello {
- public Hello() {
- say();
- }
-
- public void say() {
- System.out.println("Hello world!");
- }
- }
然后将main方法再改造一下,便只剩下Spring的内容,而无需编写自己的程序的调用代码,修改之后变成这样:
App.java
- package demo;
-
- import org.springframework.context.support.StaticApplicationContext;
-
- public class App {
-
- public static void main(String[] args) {
-
-
- StaticApplicationContext context = new StaticApplicationContext();
- context.registerBean(Hello.class);
- //因为没有显式getBean()的调用,所以不会主动创建实例,我们用下边的语句进行预实例化。
- context.getBeanFactory().preInstantiateSingletons();
-
-
- }
-
- }
运行App控制台打出“Hello World!”。
StaticApplicationContext就是所谓的上下文,我们将对象的生命周期交给这个上下文来管理。前边我只体验到了使用上下文来生成对象,实际上上下文还有更多的用法,可以提供除构造方法为的初始化以及销毁方法。由于StaticApplicationContext这个实现只用于测试,所以有些功能并未提供,为了体验更多的功能,接下来我们使用外部xml配置文件来指定要托管的类型。
ApplicationContext本身也有多种具体实现,使用不同的方式来进行自的初始化。ClassPathXmlApplicationContext就是其中之一,其目的是在classpath中搜索xml配置文件来进行上下的初始化。具体xml的配置文件怎么写可以参考下边的链接:Core Technologieshttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-definition
我们创建一个app.xml:
- "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
- https://www.springframework.org/schema/beans/spring-beans.xsd">
- <bean class="demo.Hello" init-method="say" destroy-method="bye">
- bean>
- beans>
Hello.java改成最初的样子:
- package demo;
-
- public class Hello {
- public Hello() {
- }
-
- public void say() {
- System.out.println("Hello world!");
- }
-
- public void bye() {
- System.out.println("Bye bye!");
- }
- }
App.java改成如下:
- package demo;
-
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- public class App {
-
- public static void main(String[] args) {
-
- ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
- //关闭上下文时会销毁上下中创建的所有对象。
- context.close();
-
- }
-
- }
运行App控制台打出“Hello World!”及“Bye bye!”。
前文所体现出来的是设计模式中标准的工厂模式。每个上下文中都有一个beanFactory的实例,然后通过beanFactory产生出对象实例。意思就是说,就像工厂一样,你给定设计模型,工厂就照样子给你生产。
App.java中我们只实例化了一个上下文,但程序正常运行了。当然此处我们依然没有体验到Spring带来了什么好处,但可以看出,Spring的上下文就像一个容器,里边装载了我们需要的对象,本来我们需要在main()方法要写的代码,全部由Spring接管。同时不是说我们不需要处理对象的初始化,而是将对象的初始代码与逻辑代码进行分离,并且使用基于配置的方式实现,并不需要进行硬编码。
接下来,我们研究一下,在自己的程序之上,再套一层Spring究竟有什么好处,为什么要将对象初始化进行单独处理。
我们创建一个跟Hello类差不多的HelloTom类:
- package demo;
-
- public class HelloTom {
-
- public void say() {
- System.out.println("Hello Tome!");
- }
- }
因为跟Hello长得一样,所以无论用不用Spring,调用方式都是一样的,我们先不用Spring来写个示例。为了使程序有一定扩展性,我们可以抽象出一个接口来,我们建一个SayHello的接口:
- package demo;
-
- public interface SayHello {
- void say();
- }
然后Hello与HelloTom都实现这个接口:
Hello.java
- package demo;
-
- public class Hello implements SayHello {
-
- public void say() {
- System.out.println("Hello world!");
- }
- }
HelloTom.java
- package demo;
-
- public class HelloTom implements SayHello{
-
- public void say() {
- System.out.println("Hello Tom!");
- }
- }
然后修改App.java第一次调用Hello.say():
- package demo;
-
- public class App {
-
- public static void main(String[] args) {
- SayHello hello=new Hello();
- hello.say();
-
- }
-
- }
修改App.java再次调用HelloTom.say():
- package demo;
-
- public class App {
-
- public static void main(String[] args) {
- SayHello hello=new HelloTom();
- hello.say();
-
- }
-
- }
从这两个示例中,可以看出两个不同的类型,却可以使用同一个接口类型来表示。不过,这个示例依然需要改两次代码才能实现不同的调用,能不能不改App.java就可以实现不同的调用呢?答案是可以的。我们可以使用工厂模式,通过运行参数告诉程序来选择哪个类型,但我们不必自己去写这样的逻辑,Spring已经自带了这个功能,可以直接拿用。这次我们还是用xml配置加载的方式,这样写的好处是不用在程序中硬编码,只要修改外部的xml文件后,重新运行程序就可以了。
先将App.java改成如下:
- package demo;
-
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- public class App {
-
- public static void main(String[] args) {
-
- ApplicationContext context = new ClassPathXmlApplicationContext("app.xml");
-
- SayHello hello = context.getBean(SayHello.class);
- hello.say();
-
- }
-
- }
然后修改app.xml后运行App.java:
- "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
- https://www.springframework.org/schema/beans/spring-beans.xsd">
-
- <bean class="demo.Hello" >
- bean>
- beans>
然后再修改app.xml
- "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
- https://www.springframework.org/schema/beans/spring-beans.xsd">
-
- <bean class="demo.HelloTom" >
- bean>
- beans>
再次运行,发现两次结果是不同的,这个示例中我们除了刚开始设计需要改了下App.java,后边我们就不用再改了,而是修改明文的app.xml文件。这样做的好处就是,对于一个明确的功能,为了对环境做出一定的适应性,我们只要在外部调整一下配置文件,而不必要对项目的主体进行修改。实现了程序代码的轻耦合,也就是减少无关代码的之间的联系。这个示例中,任意一个实现SayHello接口的子类,都可以这样注册到Spring的上下文中,以达到不一样的效果。
当然,ApplicationContext并不仅仅是个工厂实现,它还给了我们统一的编程框架。无论是CS程序,还是WEB程序,我们写Spring都是类似乐高一样的组装方式。区别就在于使用什么样的上下文。