欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Action 笔记(一) 初识Spring

程序员文章站 2022-03-31 21:07:35
...

前言

决定写博客,是受了《软技能-代码之外的生存之道》的启发,深知写博客利人利已的好处。

本人之前主要使用的语言并不是java,而是c#,也不是因为想转java而来学习Spring。我认为学习一种框架,不光知道如何使用框架,更重要的是理解框架背后的思想,而这个思想是不受语言限制的。在读了《Spring Action》第一章后,收获很多,也理解了之前一直模糊的一些思想,可以说收获很大,我会坚持这个博客系列,将我所收获的分享出来。下面切入正题。

Spring使命

简化java开发是Spring框架的终极目标,看似简单的几个字,实现起来并不简单,Spring一举颠覆java之前的众多框架,划出一道清新亮丽的风景线。Spring从以下几个方面做了努力使代码更简洁:

  • 激发POJO的潜能,不侵入代码。
  • 通过依赖注入和面向接口实现松耦合
  • 基于切面和惯例进行声明式编程
  • 通过切面和模板减少样板式代码

激发POJO的潜能,不侵入代码。

    之前很多框架都要求使用者继承框架接口或类,使得开发者需要更多的代码来适配框架。Spring可以让开发者只关注业务本身,POJO(Plain Ordinary Java Object)是一个简单的java类,无需为适配框架来写过多的不需要的代码。之下是两个例子:

程序1:EJB2.1 强迫实现你根本不需要的方法

public class HelloWorldBean implements SessionBean {
    public void ejbActivate(){}  // 业务无关的代码
    public void ejbPassivate(){}
    public void ejbRemove(){}
    public void setSessionContext(SessionContext ctx){}
    public void ejbCreate(){}

    public String sayHello() {
        return "Hello World";
    }
}

程序2:Spring简洁的代码

public class HelloWorldBean {
    public String sayHello() {
        return "Hello World";
    }
}

通过依赖注入和面向接口实现松耦合

要理解理解依赖注入,首先搞清楚面向接口编程和面向实现编程的区别。

下面是一个简单的例子,面向接口编程的汽车类(CarA)依赖的是具有跑(Run)功能的*(IWheel),只要符合接口的任何厂商的*都能装入汽车A,以后想更换*,不需要重新来修改汽车架构。而面向实现编程的汽车B(CarB)依赖特定的*实现(WhellA),若以后出来更先进的*,汽车B也只能相望,除非重新改造自己。所以面向接口编程使得类之间是松耦合的。

程序3:面向接口编程和面向实现编程比较

// 面向接口编程
public class CarA{
    Iwheel whell;  
    
    public Run(){
        whell.Run();
    }
}

// 面向实现编程
public class CarB{
    WhellA whell;  

    public Run(){
        whell.Run();
    }
}

那么依赖注入和面向接口编程是什么关系呢。上例中的汽车A的*从何而来?如果是汽车内部new出来,还是依赖了具体实现类,以后换个*仍然要修改汽车类,下面的例子,汽车A依赖的*由构造函数注入进来,这样把具体使用哪种*交给汽车的装配者,汽车框架的制造者不关心*选用何种,从何而来,只知道*能实现自己想要的Run功能就够了。这样汽车装配者可以根据自己的喜好装出各种汽车(跑车、普通汽车、越野车),所以依赖注入是面向接口编程的一种实现方式。依赖注入除了构造函数注入,还会有其他的注入方式,会在后面的博客中一一解释,这里只需了解依赖注入的思想就够了。

// 面向接口编程
public class Car{
    Iwheel whell;

    public Car(Iwhell whellA){ // 构造函数依赖注入
        this.whell= whellA;
    }

    public Run(){
        whell.Run();
    }
}

基于切面和惯例进行声明式编程

依赖注入让相互协作的软件组件保持松散耦合,而AOP编程允许你把遍布应用各处的功能分离出来形成可重用的组件。哪些功能是遍布应用各处的?比如日志功能,权限功能等,这些功能被称为切面功能,因为他们横切各个业务模块。

相信很多人都看到过一个业务模块被淹没在日志、权限控制、事务这些跟业务逻辑无关的代码中的情况,即使这些功能已经很好的封装,但调用代码还是嵌在了业务逻辑代码中。AOP不但很好地将公共功能封装成组件,还使这些公共功能不会嵌在业务逻辑代码中,使得POJO更简单。如何使得切面功能代码不侵入业务代码中却能实现切面功能呢,Spring采用的是配置的方式,在需要的地方配置需要的切面功能。 比较下面两份代码,你可以很明显的看到切面编程使得POJO非常简洁,只关注业务逻辑,不关心切入功能,使得切面功能和业务模块耦合的更松散。

// 没有使用AOP,依赖日志接口,日志代码侵入业务逻辑
public class Car{
    Iwheel whell;
    ILog log;

    public Run(){
        log.info("before Run");
        whell.Run();
        log.info("after Run");
    }
}

// 使用AOP,POJO根本不知道有日志,日志是否需要和使用何种日志交给使用者,POJO只关心业务逻辑
public class Car{
    Iwheel whell;

    public Run(){
        whell.Run();
    }
}

通过切面和模板减少样板式代码

相信大家有过这种经历,在编写某些代码的时候,感觉跟别的代码很相似,比如数据库代码,或者远程调用,或socket。先连接,后处理业务,然后关闭,还要catch exception等一系列重复性的代码,看下面这个例子,业务代码被样板代码淹没了。

Connection con = null;
        try {
            con = DriverManager.getConnection(dbUrl1, dbUserName, dbPassword);
            var stmt = con.prepareStatement("select * from users");  // 真正的业务代码只有这一行
            var rs = stmt.executeQuery();
            System.out.println("获取数据库连接成功!");
            System.out.println("进行数据库操作!");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("获取数据库连接失败!");
        }finally{
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
Spring旨在通过模板封装来消除样板式代码,使用jdbcTemplate,样板代码消除了,只需要关心业务逻辑。
jdbcTemplate.queryForObject(
        "select * from users",  // 查询语句
        new RowMapper<User>{} // 结果映射
  )

Bean容器

在spring中,POJO只关心自己的业务逻辑,不关心依赖的创建,实现。那有谁复杂依赖的创建,谁负责依赖的生命周期呢,那就是Bean容器。Bean容器装载所有的Bean(POJO类实例),通过注入bean来组装复杂bean。


Bean在容器中有一系列的生命周期节点,Bean开发者可以定义节点的钩子函数来定制Bean,比如销毁的时候,同时释放一些资源。

  • Bean的建立, 由BeanFactory读取Bean定义文件,并生成各个实例
  • Setter注入,执行Bean的属性依赖注入
  • BeanNameAware的setBeanName(), 如果实现该接口,则执行其setBeanName方法
  • BeanFactoryAware的setBeanFactory(),如果实现该接口,则执行其setBeanFactory方法
  • BeanPostProcessor的processBeforeInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processBeforeInitialization()方法
  • InitializingBean的afterPropertiesSet(),如果实现了该接口,则执行其afterPropertiesSet()方法
  • Bean定义文件中定义init-method
  • BeanPostProcessors的processAfterInitialization(),如果有关联的processor,则在Bean初始化之前都会执行这个实例的processAfterInitialization()方法
  • DisposableBean的destroy(),在容器关闭时,如果Bean类实现了该接口,则执行它的destroy()方法
  • Bean定义文件中定义destroy-method,在容器关闭时,可以在Bean定义文件中使用“destory-method”定义的

Spring模块

Spring框架由几个不同的模块组成,为开发企业级应用提供了所需的一切,下面是模块的概览图,会在后面的博客中详细介绍各个模块,这里只需要了解Spring的模块组成和各模块的功能就够了。

Spring Action 笔记(一) 初识Spring

总结

以上是spring action第一章的总结,主要理解Spring的目标以及通过哪些手段来实现这些目标。相信你对Spring已经有了大概了解。后续博客将对Spring进行全面详细介绍。