java学习交流群

spring系列(一):超级经典入门

一  spring是什么

Spring是一个开源框架,它由RodJohnson创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。

目的:解决企业应用开发的复杂性

功能:使用基本的JavaBean代替EJB,并提供了更多的企业应用功能

范围:任何Java应用

简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

■  轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。

■  控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

■  面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务()管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。

■  容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。

■  框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。

所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。


二    spring的历史

Spring的基础架构起源于2000年早期,它是Rod Johnson在一些成功的商业项目中构建的基础设施。

在2002后期,Rod Johnson发布了《Expert One-on-One J2EE Design and Development》一书,并随书提供了一个初步的开发框架实现——interface21开发包,interface21就是书中阐述的思想的具体实现。后来,Rod Johnson 在interface21 开发包的基础之上,进行了进一步的改造和扩充,使其发展为一个更加开放、清晰、全面、高效的开发框架——Spring。

2003年2月Spring框架正式成为一个开源项目,并发布于SourceForge中。

三  Spring的使命

¨  J2EE应该更加容易使用。

¨  面向对象的设计比任何实现技术(比如J2EE)都重要。

¨  面向接口编程,而不是针对类编程。Spring将使用接口的复杂度降低到零。(面向接口编程有哪些复杂度?)

¨  代码应该易于测试。Spring框架会帮助你,使代码的测试更加简单。

¨  JavaBean提供了应用程序配置的最好方法。

¨  在Java中,已检查异常(Checked exception)被过度使用。框架不应该迫使你捕获不能恢复的异常。

四   spring受到的批判

Ø  Spring不是一个“标准”。Spring不是J2EE规范的一部分,没有通过JCP(Java Community Process)的审核认可。

批判来源于EJB的支持者,他们认为EJB是一个标准,是J2EE规范的一部分。当然,标准最主要的目的是希望在应用服务器之间是可移植的,可是EJB的移植却并不轻松,不同应用服务器的ejb部署描述文件总是有着差异。而且EJB开发的类完全依赖于EJB容器。而Spring对其管理的Bean没有任何形式的侵入,这样的Bean是普通Java对象(POJO),那么它就遵循Java标准,可以到处移植。

 

Ø  Spring是“超重量级”的。

Spring涉及的内容确实很多(例如:提供了对jdbc、ORM、远程访问等等的支持),但其本质还是Java技术的庞大。Spring只是为了这些技术提供更好的使用方案而已。同时,你可以只选取你需要使用的部分。

五  spring包含的模块

Spring框架由七个定义明确的模块组成


如果作为一个整体,这些模块为你提供了开发企业应用所需的一切。但你不必将应用完全基于Spring框架。你可以自由地挑选适合你的应用的模块而忽略其余的模块。

就像你所看到的,所有的Spring模块都是在核心容器之上构建的。容器定义了Bean是如何创建、配置和管理的——更多的Spring细节。当你配置你的应用时,你会潜在地使用这些类。但是作为一名开发者,你最可能对影响容器所提供的服务的其它模块感兴趣。这些模块将会为你提供用于构建应用服务的框架,例如AOP和持久性。

核心容器

这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。这里最基本的概念是BeanFactory,它是任何Spring应用的核心。BeanFactory是工厂模式的一个实现,它使用IoC将应用配置和依赖说明从实际的应用代码中分离出来。

应用上下文(Context)模块

核心模块的BeanFactory使Spring成为一个容器,而上下文模块使它成为一个框架。这个模块扩展了BeanFactory的概念,增加了对国际化(I18N)消息、事件传播以及验证的支持。

另外,这个模块提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持。

Spring的AOP模块

   Spring在它的AOP模块中提供了对面向切面编程的丰富支持。这个模块是在Spring应用中实现切面编程的基础。为了确保Spring与其它AOP框架的互用性, Spring的AOP支持基于AOP联盟定义的API。AOP联盟是一个开源项目,它的目标是通过定义一组共同的接口和组件来促进AOP的使用以及不同的AOP实现之间的互用性。通过访问他们的站点http://aopalliance.sourceforge.net,你可以找到关于AOP联盟的更多内容。

Spring的AOP模块也将元数据编程引入了Spring。使用Spring的元数据支持,你可以为你的源代码增加注释,指示Spring在何处以及如何应用切面函数。

JDBC抽象和DAO模块

使用JDBC经常导致大量的重复代码,取得连接、创建语句、处理结果集,然后关闭连接。Spring的JDBC和DAO模块抽取了这些重复代码,因此你可以保持你的数据库访问代码干净简洁,并且可以防止因关闭数据库资源失败而引起的问题。

这个模块还在几种数据库服务器给出的错误消息之上建立了一个有意义的异常层。使你不用再试图破译神秘的私有的SQL错误消息!

另外,这个模块还使用了Spring的AOP模块为Spring应用中的对象提供了事务管理服务。

对象/关系映射集成模块

对那些更喜欢使用对象/关系映射工具而不是直接使用JDBC的人,Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射。Spring的事务管理支持这些ORM框架中的每一个也包括JDBC。

Spring的Web模块

Web上下文模块建立于应用上下文模块之上,提供了一个适合于Web应用的上下文。另外,这个模块还提供了一些面向服务支持。例如:实现文件上传的multipart请求,它也提供了Spring和其它Web框架的集成,比如Struts、WebWork。

Spring的MVC框架

Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。

它也允许你声明性地将请求参数绑定到你的业务对象中,此外,Spring的MVC框架还可以利用Spring的任何其它服务,例如国际化信息与验证。


六  依赖注入(IOC)的引入

       我们先看看需求:实现一个用户注册信息持久化的类。

功能:

1、 保存用户注册的信息;

2、 根据用户的名称获得该注册用户。

虽然功能简单,但它对持久化方式的要求却非常的灵活:

1、 在内存中持久化,供测试、演示使用。

2、 如果用户的数据很少,将用户信息持久化到文本文件中。

3、 如果用户信息很多,并需要一些灵活的查询,则需要使用JDBC技术将用将用户信息持久化到数据库中。

4、 面对企业复杂关联的数据,甚至需要使用持久层框架来实现用户信息的持久化,比如:iBATIS、Hibernate等。

 

如何去设计、实现我们这个持久化类呢?

我们遵循软件开发的原则“首先让它跑起来,再去优化(重构)它”,我们首先实现最简单的在内存中持久化用户信息。

       既然我们要保存和取得用户信息,首先应该设计用户类。代码如下:

User.java

    public class User {
        private Long id;
        private String name;
        private String password;
        private String group;
        
        public User(String name,String password){
            this.name = name;
            this.password = password;
    }
    //相应的get/set方法
    ………..
    }

持久化类有两个方法,分别在内存中保存和获取User对象。代码如下:

MemoryUserPersist.java

    public class MemoryUserPersist {
        private static Map users = new HashMap();
        static{
            User defaultAdmin = new User("Moxie","pass");
            users.put(defaultAdmin.getName(),defaultAdmin);
        }
        public MemoryUserPersist (){
            
    }
    //将信息保存到Map中(内存存储)
        public void saveUser(User user){
            users.put(user.getName(),user); //用用户名做键值
        }
        public User LoadUser(String userName){
            return (User)users.get(userName);
        }
    }

用户持久化类完成之后,我们就可以在客户端UserRegister中使用它了。例如:用户注册时,UserRegister代码片断如下:

MemoryUserPersistuserPersist = new MemoryUserPersist ();

userPersist.saveUser(user);

可是,现在如果要在文本文件中持久化User,又该如何实现呢?实现一个TextUserPersist类,这个并不困难。但客户端代码将面临重大灾难:找到所有使用过MemoryUserPersist的客户端类,将他们中的MemoryUserPersist逐个手工修改为 TextUserPersist,并且重新编译,当然以前的测试也必须全部从头来过!

       人生的浩劫只是刚刚开始,因为根据前面的需求我们至少要分别实现四种持久化方式!这时,你一定和我一样在期待着救世主的早日降临——接口(Interface)。

面向接口编程

什么是接口?

¨  接口定义了行为的协议,这些行为在继承接口的类中实现。

¨  接口定义了很多方法,但是没有实现它们。类履行接口协议并实现所有定义在接口中的方法。

¨  接口是一种只有声明没有实现的特殊类。

 

接口的优点:

¨  Client不必知道其使用对象的具体所属类。

¨  一个对象可以很容易地被(实现了相同接口的)的另一个对象所替换。

¨  对象间的连接不必硬绑定(hardwire)到一个具体类的对象上,因此增加了灵活性。

¨  松散藕合(loosens coupling)。

¨  增加了重用的可能性。

 

接口的缺点:

设计的复杂性略有增加


(用户持久化类)重构第一步——面向接口编程

1、 设计用户持久化类的接口UserDao,代码如下:

    public interface UserDao {
        public void save(User user);
        public User load(String name);
    }

2、 具体的持久化来必须要继承UserDao接口,并实现它的所有方法。我们还是首先实现内存持久化的用户类:

    public class MemoryUserDao implements UserDao{
        private static Map users = new HashMap();;
        static{
            User user = new User("Moxie","pass");
            users.put(user.getName(),user);
        }
     
        public void save(User user) {
            users.put(user.getId(),user);
        }
     
        public User load(String name) {
            return (User)users.get(name);
        }
    }

MemoryUserDao的实现代码和上面的MemoryUserPersist基本相同,唯一区别是MemoryUserDao类继承了UserDao接口,它的save()和load()方法是实现接口的方法。

这时,客户端UserRegister的代码又该如何实现呢?

UserDao userDao= new MemoryUserDao();

userDao.save(user);

(注:面向对象“多态”的阐述)

如果我们再切换到文本的持久化实现TextUserDao,客户端代码仍然需要手工修改。虽然我们已经使用了面向对象的多态技术,对象userDao方法的执行都是针对接口的调用,但userDao对象的创建却依赖于具体的实现类,比如上面MemoryUserDao。这样我们并没有完全实现前面所说的“Client不必知道其使用对象的具体所属类”。

如何解决客户端对象依赖具体实现类的问题呢?

下面该是我们的工厂(Factory)模式出场了!

(spring 实现用户调用某个类的时候真正的不用再实例化某个具体的类,而是仅仅调用它的实现接口就可以完成所要达到的目的!)


重构第二步——工厂(Factory)模式

我们使用一个工厂类来实现userDao对象的创建,这样客户端只要知道这一个工厂类就可以了,不用依赖任何具体的UserDao实现。创建userDao对象的工厂类UserDaoFactory代码如下:

public class UserDaoFactory {

   public static UserDao createUserDao(){

       return new MemoryUserDao();

    }

}

客户端UserRegister代码片断如下:

UserDao userDao= UserDaoFactory. CreateUserDao();

userDao.save(user);

现在如果再要更换持久化方式,比如使用文本文件持久化用户信息。就算有再多的客户代码调用了用户持久化对象我们都不用担心了。因为客户端和用户持久化对象的具体实现完全解耦。我们唯一要修改的只是一个UserDaoFactory类。


重构第三步——工厂(Factory)模式的改进

到这里人生的浩劫已经得到了拯救。但我们仍不满足,因为假如将内存持久化改为文本文件持久化仍然有着硬编码的存在——UserDaoFactory类的修改。代码的修改就意味着重新编译、打包、部署甚至引入新的Bug。所以,我们不满足,因为它还不够完美!

如何才是我们心目中的完美方案?至少要消除更换持久化方式时带来的硬编码。具体实现类的可配置不正是我们需要的吗?我们在一个属性文件中配置UserDao的实现类,例如:

在属性文件中可以这样配置:userDao= com.test.MemoryUserDao。UserDao的工厂类将从这个属性文件中取得UserDao实现类的全名,再通过Class.forName(className).newInstance()语句来自动创建一个UserDao接口的具体实例。UserDaoFactory代码如下:

    public class UserDaoFactory {
        public static UserDao createUserDao(){
            String className = "";
    // ……从属性文件中取得这个UserDao的实现类全名。
            UserDao userDao = null;
            try {
                userDao = (UserDao)Class.forName(className).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return userDao;
       }

通过对工厂模式的优化,我们的方案已近乎完美。如果现在要更换持久化方式,不需要再做任何的手工编码,只要修改配置文件中的userDao实现类名,将它设置为你需要更换的持久化类名即可。

我们终于可以松下一口气了?不,矛盾仍然存在。我们引入了接口,引入了工厂模式,让我们的系统高度的灵活和可配置,同时也给开发带来了一些复杂度:1、本来只有一个实现类,后来却要为这个实现类引入了一个接口。2、引入了一个接口,却还需要额外开发一个对应的工厂类。3、工厂类过多时,管理、维护非常困难。比如:当UserDao的实现类是JdbcUserDao,它使用JDBC技术来实现用户信息从持久化。也许要在取得JdbcUserDao实例时传入数据库Connection,这是仍少UserDaoFactory的硬编码。

当然,面接口编程是实现软件的可维护性和可重用行的重要原则已经勿庸置疑。这样,第一个复杂度问题是无法避免的,再说一个接口的开发和维护的工作量是微不足道的。但后面两个复杂度的问题,我们是完全可以解决的:工厂模式的终极方案——IoC模式。

重构第四步-IoC容器

使用IoC容器,用户注册类UserRegister不用主动创建UserDao实现类的实例。由IoC容器主动创建UserDao实现类的实例,并注入到用户注册类中。我们下面将使用Spring提供的IoC容器来管理我们的用户注册类。

用户注册类UserRegister的部分代码如下:

    public class UserRegister {
        private UserDao userDao = null;//由容器注入的实例对象
        
        public void setUserDao(UserDao userDao){
            this.userDao = userDao;
        }
        // UserRegister的业务方法
    }

在其它的UserRegister方法中就可以直接使用userDao对象了,它的实例由Spring容器主动为它创建。但是,如何组装一个UserDao的实现类到UserRegister中呢?哦,Spring提供了配置文件来组装我们的组件。Spring的配置文件applicationContext.xml代码片断如下:

    <bean id="userRegister" class="com.dev.spring.simple.UserRegister">
            <property name="userDao"><ref local="userDao"/></property>
    </bean>
    <bean id="userDao" class="com.dev.spring.simple.MemoryUserDao"/>


七  总结

      Spring带来了复杂的J2EE开发的春天。它的核心是轻量级的IoC容器,它的目标是为J2EE应用提供了全方位的整合框架,在Spring框架下实现多个子框架的组合,这些子框架之间可以彼此独立,也可以使用其它的框架方案加以代替,Spring希望为企业应用提供一站式(one-stopshop)--一站式服务的解决方案。
                                        

                                               扫码关注我哦!

  

 

posted @ 2019-07-17 09:05  十分钟学java  阅读(173)  评论(0编辑  收藏  举报
java学习交流群