Spring是怎么回事?新手入门就看这篇吧

前言

今天壹哥给大家介绍一套开源的轻量级框架,它就是Spring。在给大家详细讲解Spring框架之前,壹哥先给大家介绍Spring框架的主要内容:

  • Spring的基本概念

  • Spring核心思想之ioc

  • Spring核心思想之aop

  • Spring框架对事务的支持

 在本系列文章的讲解中,壹哥会给大家配备丰富的案例、图片以及对应的学习视频,目的就是让大家更好的理解和运用Spring框架,并给大家带来足够的启发和思考。接下来我们正式学习Spring框架。

一. Spring框架的基本概述

1. 什么是Spring

在Spring官网(https://spring.io/projects/spring-frameworkhttps://Spring.io/projects/Spring-framework)里面,是这样介绍Spring的:

如果大家看不懂英文,壹哥结合官网以及自己的理解,给大家介绍什么是Spring:

Spring是一个全栈的、开源的javaEE轻量级企业框架。以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为核心思想提供了基于持久层、业务层和控制层的众多企业级技术解决方案。此外Spring还整合了众多第三方优秀的框架和类库,逐渐成为业界最流行的企业应用框架。

壹哥先给大家画一幅图,让大家更好的理解Spring的概念:

相信通过这幅图,大家对Spring的概念有了清晰的理解。

了解了Spring的基本概念之后,我们再来了解一下Spring的发展历程。

2. Spring的发展历程

在介绍Spring的发展历程之前,壹哥有必要提及一下Spring的开山鼻祖Rod Johnson(罗德·约翰逊),他是Spring框架的缔造者。

其实Spring并不是凭空存在的。早在Spring框架诞生之前,java企业级技术解决方案主要依靠javase和EJB框架。但是EJB框架在企业级开发中存在一些弊端和缺陷。

  1. 2002年,Rod Johnson 撰写了一本名为《 Expert One-to-One J2EE Design and Development 》的书。这本书介绍了当时 Java 企业应用程序开发的情况,并系统阐述了 J2EE 使用 EJB 开发设计的优点及解决方案。

  2. 同年又推出了《Expert one-on-one J2EE Development without EJB》,对EJB的各种笨重臃肿的结构进行了逐一的分析和否定,并分别以简洁实用的方式替换之,堪称经典。

  3. 2003年,推出的《Expert one on one J2EE design and development》中阐述的部分理念,在这本书中,他提出了一个基于普通 Java 类和依赖注入的思想。在书中,他展示了如何在不使用 EJB 的情况下构建高质量,可扩展的系统--这就是Spring的雏形!

  4. 2004年,Spring横空出世!Spring1.0版本发布,随后Spring框架迅速发展。

  5. 2006年10月Spring 2.0于发布。

  6. 经过多年的发展,2017 年 9 月份发布了 Spring具有里程碑意义的版本 Spring 5.0 通用版(GA),并提出了最令人兴奋的响应式编程模型。

  7. 现在Spring的最新版本已经更迭到了Spring 6。

到这里,壹哥就给大家详细介绍了Spring的发展历史,希望大家心中有数,以后你在跟别人聊Spring的时候,这也是一种不可或缺的谈资。接下来,壹哥再给大家介绍一下Spring的优势。

3. Spring的优势

对于javaee开发人员来说,基于Spring庞大的生态系统,Spring几乎是每个人离不开的框架,所以Spring的优势不言而喻!在这里壹哥给大家简单罗列Spring的优势都有哪些:

  • 降低程序耦合度,简化开发:通过 Spring 提供的 IoC 核心容器,可以将对象之间的依赖关系交给 Spring帮我我们进行控制,避免因为硬编码所造成的程序高程序耦合。

  • AOP 面向切面思想的支持:通过 Spring 的 AOP 思想,对传统 OOP 思想进行了延续和扩展。通过面向切面编程,在不侵入源代码的前提下,轻松实现对应用进行更新和扩展。

  • 基于对数据库事务的支持:将我们从单调烦闷的数据库事务管理代码中解脱出来,通过声明式方式和注解式的方式进行灵活的事务管理,将事务代码和业务代码剥离开来。降低业务代码的耦合度,提高开发效率和质量。

  • 方便集成各种第三方优秀框架:Spring降低了各种框架的使用难度,提供了对各种优秀框架( Struts、 Hibernate、 rabbitmq、 elasticsearch、redis...)的无缝支持。

  • 降低 JavaEE API 的使用难度:Spring 对 JavaEE API(如 JDBC、 Redis)进行了薄薄的封装层,提供了模板对象操作这些API,使这些 API 的使用难度大为降低。

  • Java 源码是经典学习范例:Spring 的源代码博大精深,它设计巧妙、别具风格,处处体现着大师对 Java 设计模式的灵活运用以及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践范例。通过对源码的阅读,我们可以更加清晰了解框架的设计逻辑,可以拓宽我们对java知识的认知广度和深度。

聊完了Spring的优势以后,Spring到底包含哪些内容呢?下面壹哥通过一幅图来描述Spring的体系结构。

4. Spring的体系结构

 下面壹哥就这幅图给大家做详细的介绍:

4.1 核心容器(Core Container)

Spring的核心容器是Spring其他功能正常运行的基础,它包含了Spring-core、Spring-beans、Spring-context和Spring-expression(Spring表达式语言)等模块。

  • Spring-core模块:Spring框架的核心部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)的核心功能。

  • Spring-beans模块:提供了BeanFactory,我们俗称bean工厂。Spring将管理的对象称为Bean。Spring容器就是通过这个bean工厂来帮助我们管理bean。

  • Spring-context模块:我们称为Spring 上下文环境,基于Spring-core和Spring-beans模块的基础之上,提供了通过XML和注解的方式来管理bean对象。ApplicationContext接口是Spring-context模块的核心。也是我们在后续学习中需要关注的焦点。

  • Spring-expression模块:提供了强大的表达式语言去支持对bean进行查询和设置。Spring表达式语言支持设置和获取备案的属性值、以及进行逻辑和算术运算等操作。

4.2 数据访问/集成(Data Access/Integration)

数据访问/集成层由JDBC、ORM、OXM、JMS和Transactions(事务)模块组成。

  • Spring-jdbc模块:Spring对原生的jdbc技术进行了封装,提供了jdbc Template来进行数据库操作。是Spring基于对数据持久层的解决方案之一。

  • Spring-orm模块:Spring 框架提供了对若干个 ORM 框架的集成层,比如Hibernate等。

  • Spring-oxm模块:是Spring3.0版本中的一个新特性,它提供了一个支持o(对象)/XML映射的抽象层实现,例如JAXB、Castor、JiBX和XStream。

  • Spring-jms模块(Java Messaging Service):指Java消息传递服务,提供了对消息的生产和发送功能,通过对jms的封装,简化了对jms api的使用难度。

  • Spring-tx模块:事务模块支持使用编程式事务、声明式事务和注解式事务对事务进行管理。

4.3 Web层

Web层由Spring-web、Spring-webmvc、Spring-websocket和Portlet模块组成。

  • Spring-web模块:提供了基本的Web开发功能,比如通过监听的机制初始化Spring的上下文环境。

  • Spring-webmvc模块:提供了对Spring MVC的实现。

  • Spring-websocket模块:Spring4.0以后新增的模块,它提供了WebSocket和SocketJS的实现。

  • Portlet模块:提供了Portlet环境下的MVC实现。

4.4 AOP(Aspect Oriented Programming)模块

  • Spring-aop模块:提出了AOP思想的面向切面编程的实现,允许我们定义通知方法对切点方法进行增强,对代码进行解耦合。

  • Spring-aspects模块:提供了对第三方AOP框架AspectJ的集成功能,AspectJ是一个功能强大且易于上手的的AOP框架。我们在后面也会对AspectJ框架进行详细学习。

4.5 植入(Instrumentation)模块

  • Spring-instrument模块:在特定的应用服务器中,提供了类植入(instrumentation)支持和类加载器的实现,比如Spring-instrument-tomcat 模块支持对Tomcat服务器的植入代理。

4.6 消息传输(Messaging)

  • 这是Spring4.0以后新增了消息(Spring-messaging)模块,集成了消息api,对消息协议也提供了支持。

4.7 测试(Test)模块

  • Spring-test模块:支持使用JUnit对Spring组件进行单元测试。Spring提供了对junit的整合,提供了一个带有Spring环境的标准测试模板,后续我们也会学习Spring对junit单元测试框架的整合。

二. 程序中的耦合

前面壹哥给大家介绍Spring优势的时候,给大家描述了Spring框架可以降低代码的耦合度。那到底什么是程序的耦合,现在壹哥用几个例子给大家介绍清楚。

1. 耦合案例1

1.1 代码示例

我们来看一段代码,这段代码相信大家都很熟悉,就是使用jdbc技术进行数据表的查询操作。首先我们新建一个maven工程,并导入相关依赖。

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>
  
   <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13</version>
      <scope>test</scope>
   </dependency>
</dependencies>

注意:壹哥友情提示,这个依赖适配的mysql数据库版本是5.x哦。

我们在这里编写一段jdbc代码。

public class TestMySql {

    @Test
    public void test01() throws Exception{
        //注册驱动,此时我们不要Class.Name()的方式进行驱动加载,为什么?请看下文分解~
        DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        String url = "jdbc:mysql://192.168.10.137:3306/spring";
        //获取连接对象
        Connection connection = DriverManager.getConnection(url, "root", "Admin123!");
        PreparedStatement statement = connection.prepareStatement("select * from account");
        ResultSet resultSet = statement.executeQuery();
        while (resultSet.next()){
            Integer id = resultSet.getInt("id");
            String name = resultSet.getString("name");
            Double money = resultSet.getDouble("money");
            System.out.println(id + " " + name + " " + money);
        }
        resultSet.close();
        statement.close();
        connection.close();
    }
}

我们运行程序,查看效果是没有问题的。但是如果我们把POM文件中的依赖注释掉呢?

<!--
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>
-->

这个时候,可能有小伙伴要问了,老师,你为什么要把这个依赖注释掉呢?其实壹哥是用来模拟在项目中,如果资源缺失,对程序带来的影响是什么。

此时我们在重新编译程序(Ctrl+F9)。我们发现程序报错,程序编译不通过!

小伙伴们想一下:程序编译不通过意味着什么呢?壹哥给大家解释一下:

一个企业级的项目,里面的业务功能肯定有很多,假设一个业务功能出现了问题,但是其他业务功能是正常的。如果一个业务功能出现了问题,导致程序编译不通过的后果是什么?是不是其他正常的业务功能也无法正常运行了,这实在是我们不想看到的情况。

大家想想,如果出现了这种情况,理想的解决方案是什么?当然最好的解决方案是修复当前程序的bug,让程序正常编译通过。但是假设一时半会解决不了这个问题呢?

即使这个业务功能出现了问题,我们也要期望其他能功能也应该正常执行,换句话说,就是一定要让程序编译通过!!!而之所以会出现这种编译不通过的情况,就是因为我们的代码出现了高耦合!

1.2 耦合的分析以及对应的解决方案

什么是耦合?

耦合指的是程序之间的依赖关系。这种依赖关系包括类与类之间存在依赖关系,类与方法之间存在依赖关系。

注意:壹哥说重点,我们所说的降低耦合,就是降低程序的依赖关系,但是依赖关系只能尽量降低,不能彻底消除!

上面的案例,其实就是类与类之间存在高耦合的体现。在TestMySql这个类里面使用了Driver这个驱动类,说明TestMySql依赖于Driver驱动类。

由于我们注释了驱动类,导致Driver这个类不存在,最后导致TestMySql这个类编译不通过。

如何解耦?我们解耦的最终目的要达到一个效果,就是即使依赖缺失了。程序也应该正常编译通过,这就叫编译时不依赖,也就是在编译期不检查程序间的依赖关系。所以我们需要对原来的代码进行改造,如何改造?基于反射的技术降低代码的耦合度。

@Test
public void test01() throws Exception{
    //注册驱动
    //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
    //使用反射的机制加载数据库驱动
    Class.forName("com.mysql.jdbc.Driver");
    String url = "jdbc:mysql://192.168.10.137:3306/spring";
    //获取连接对象
    Connection connection = DriverManager.getConnection(url, "root", "Admin123!");
    PreparedStatement statement = connection.prepareStatement("select * from account");
    ResultSet resultSet = statement.executeQuery();
    while (resultSet.next()){
        Integer id = resultSet.getInt("id");
        String name = resultSet.getString("name");
        Double money = resultSet.getDouble("money");
        System.out.println(id + " " + name + " " + money);
    }
    resultSet.close();
    statement.close();
    connection.close();
}

此时重新编译代码,这个时候就不会报错。我们运行试试(此时pom.xml的坐标被注释了,在运行时会报异常)。

但此时报的是运行时异常,而不是编译时异常,换句话说就是整个应用编译通过,其他正常功能可以运行,但是有问题的功能会抛出运行时异常。虽然有异常,但至少没有拖后腿,影响其他正常功能的执行!

在这里壹哥给大家总结一下,如何降低代码的耦合性:

使用反射的机制,避免使用new关键字。

2. 耦合案例2

接下来我们再看一个案例,进一步理解耦合的概念,以及解决耦合的方案。

2.1 代码示例

现在我们定义一段代码,模拟新增用户的功能。

dao部分:

我们定义一个模拟用户新增的dao接口。

public interface UserDao { 
    //模拟新增user的方法 
    public void addUser(); 
}

接下来,我们对接口进行实现,假设我们现在对持久层的实现技术是基于jdbc技术实现的。我们现在就在接口实现类里面输出一句话,模拟我们完成了对数据库的新增操作。

public class UserDaoImpl implements UserDao{
    
    //模拟新增user的dao
    public void addUser() {
        System.out.println("基于jdbc技术实现了对新增用户的操作.....");
    }
}

service部分:

接下来我们再定义一个新增用户的业务接口。

public interface UserService {
    //模拟新增user的业务
    public void add();
}

我们定义业务实现类:

public class UserServiceImpl implements UserService {
    //实现新增用户的业务方法
    public void add() {
        UserDao dao = new UserDaoImpl();
        dao.add();
    }
}

2.2 耦合的分析以及对应的解决方案

在这里壹哥做一个假设。假设现在因为客户需求的变更,我们需要对持久层的技术进行变化,我们需要对原来jdbc技术实现接口的方法变成使用mybatis框架实现。所以我们需要重新定义一个接口实现类:

public class UserDaoImpl2 implements UserDao{
    
    //模拟新增user的dao
    public void addUser() {
        System.out.println("基于mybatis技术实现了对新增用户的操作.....");
    }
}

然后我们需要修改业务实现类的源代码,更改接口的调用方式:

public class UserServiceImpl implements UserService {
    //实现新增用户的业务方法
    public void add() {
        //UserDao dao = new UserDaoImpl();
        UserDao dao = new UserDaoImpl2();
        dao.addUser();
    }
}

这样修改会有什么问题呢?

这会造成源代码的改动

做过开发的小伙伴们都知道,如果我们的应用要进行更新迭代,或者需要扩展一些新功能,必须在不修改原来功能代码的基础上完成,因为一旦修改源代码,很有可能会导致其他正常的功能代码也出现问题。上面出现修改源代码的原因其实也是高耦合的一种体现!究其原因还是UserServiceImpl这个类依赖了UserDao的实现类。

那么又该如何解决这个问题呢?

在解决这个问题之前,壹哥先要明确一个概念,什么是javabean?

javaBean就是在java程序中,可以重用的一些组件,比如实体类、dao和service接口实现类的对象。解决方案如下:

1.需要一个配置文件来配置我们的service和dao。配置的内容:唯一标志=全限定类名(key=value)。

2.在一个工厂类里面,通过读取配置文件中配置的内容,反射创建对象。也就是通过工厂来管理我们的bean。

我们选取什么样的配置文件最合适?我们就选取大家最熟悉的properties配置文件。

定义配置文件,管理我们的service和dao:

#key=value  key bean的名称  value:类的全限定名
userDao=com.qf.dao.impl.UserDaoImpl
userDao2=com.qf.dao.impl.UserDaoImpl2
userService=com.qf.service.impl.UserServiceImpl

定义的配置文件,效果如下:

定义一个工厂类,通过工厂来获取bean

public class BeanFactory {

    static Properties properties;

    static{
        //通过类加载器读取properties配置文件
        InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //定义一个获取bean的方法
    public static Object getBean(String name){
        String value = properties.getProperty(name);
        try {
            //基于反射的机制实例化bean对象
            Object o = Class.forName(value).newInstance();
            return o;
        } catch (Exception e) {
            throw  new RuntimeException();
        }
    }
}

改造业务实现类的代码:

public class UserServiceImpl implements UserService {
    //实现新增用户的业务方法
    public void add() {
        //UserDao dao = new UserDaoImpl();
        //UserDao dao = new UserDaoImpl2();
        UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
        userDao.addUser();
    }
}

如果我们想要切换使用mybatis的技术实现dao接口的话,我们只需要把getBean方法里面的bean的名称修改即可。

UserDao userDao = (UserDao) BeanFactory.getBean("userDao2");

现在我们通过工厂模式解决了代码出现的耦合度问题。接下来,大家再思考一下,目前我们的代码有没有出现问题。如果我们循环调用5次service,会出现什么结果?

我们进一步改造代码:

public class UserServiceImpl implements UserService {

    public void add() {
        UserDao accountDao = (UserDao) BeanFactory.getBean("accountDao2");
        //输出accountDao实例对象
        System.out.println(accountDao);
        accountDao.addUser();
    }
}

定义测试类:

@Test
public void test02(){
    for(int i = 0;i<5;i++){
        UserService accountService = new UserServiceImpl();
        accountService.add();
    }
}

查看输出结果:

结论:该业务实现类调用的dao实现类对象是多例的。

我们的”工厂类”生产的bean之所以是多例模式,是因为下面这句代码:

在单线程的场景下面,我们期望工厂生产的bean的单例的。因为单例bean带来的优势也比较明显:

  • 可以减少JVM垃圾回收的次数。

  • 提高了获取bean的速度。

  • 降低了实例化bean带来的性能消耗。

接下来,我们就来改造我们的工厂代码。

2.3 优化工厂代码,生产单例bean

改造工厂类:

public class BeanFactory {

    static Properties properties = null;
    //用来存储实例对象的 key是bean的名称 value是实例对象
    static Map<String,Object> map = new HashMap<String, Object>();

    static {
        try {
            properties = new Properties();
            //通过类加载器加载properties配置文件
            InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            properties.load(in);
            //获取properties配置文件中的所有key值
            Enumeration<Object> keys = properties.keys();
            while(keys.hasMoreElements()){
                String name = keys.nextElement().toString();
                String value = properties.get(name).toString();
                //通过key的名称获取对应的value值
                Class<?> clazz = Class.forName(value);
                Object o = clazz.newInstance();
                map.put(name,o);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //根据key获取对应的value
    public static Object getBean(String name){
        return map.get(name);
    }
}

测试:

查看控制台输出效果:

现在我们终于使用第二种方案降低了代码的耦合性,在这里壹哥再次总结一下:

可以使用工厂+反射的机制降低代码的耦合度!

但是我们每次都自己通过工厂+配置的方式创建对象的过程过于繁琐,其实Spring已经帮助我们做了这些事情,spring如何帮助我们管理bean的?在下期文章,壹哥再给大家细细道来。

三. 结语

通过这篇文章的介绍,壹哥带领大家一步步剖析了什么是耦合,以及解耦合对应的具体方案,希望大家能够理解为什么要使用这种思想帮助我们管理bean。也希望大家理解为什么我们要手动基于反射+工厂的方式去管理bean的用意。那么Spring到底如何使用ioc管理bean的?请大家继续关注壹哥的后续文章,谢谢大家的支持和鼓励!

四. 今日作业

1. 什么是程序的耦合,在评论区给出你们的答案。

2. 如何自定义容器管理bean? 在评论区说出你们的解决方案。

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一一哥Sun

您的鼓励是我继续创作的动力哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值