框架 订阅
框架(framework)是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。框架这个广泛的定义使用的十分流行,尤其在软件概念。框架也能用于机械结构。 展开全文
框架(framework)是一个框子——指其约束性,也是一个架子——指其支撑性。是一个基本概念上的结构,用于去解决或者处理复杂的问题。框架这个广泛的定义使用的十分流行,尤其在软件概念。框架也能用于机械结构。
信息
注    音
ㄎㄨㄤˋ ㄐㄧㄚˋ
拼    音
kuàng jià
英    译
Framework
释    义
联结而成的结构
中文名
框架
应用领域
软件、机械结构等
框架释义
1、释义:(1)建筑工程中,由梁、柱等联结而成的结构;(2)比喻事物的组织、结构。2、土木工程中,由梁、柱等构件刚性连结而成的结构体系。3、比喻事物的组织、结构:这部长篇小说已经有了一个大致的框架。
收起全文
精华内容
参与话题
问答
  • 批处理框架spring batch基础知识介绍

    万次阅读 多人点赞 2019-01-10 12:29:27
    spring batch简介 Spring Batch架构介绍 Spring Batch核心概念介绍 什么是Job 什么是JobInstance 什么是JobParameters 什么是JobExecution 什么是Step 什么是StepExecution 什么是ExecutionContext 什么是...

    Table of Contents

    spring batch简介

    Spring Batch架构介绍

    Spring Batch核心概念介绍

    什么是Job

    什么是JobInstance

    什么是JobParameters

     什么是JobExecution

    什么是Step

    什么是StepExecution

    什么是ExecutionContext

     什么是JobRepository

    什么是JobLauncher

    什么是Item Reader

    什么是Item Writer

    什么是Item Processor

    chunk 处理流程

    skip策略和失败处理

    批处理操作指南

    批处理原则

    如何默认不启动job

    在读数据时内存不够

     


    spring batch简介

    spring batch是spring提供的一个数据处理框架。企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作。 这些业务运营包括:

    • 无需用户交互即可最有效地处理大量信息的自动化,复杂处理。 这些操作通常包括基于时间的事件(例如月末计算,通知或通信)。
    • 在非常大的数据集中重复处理复杂业务规则的定期应用(例如,保险利益确定或费率调整)。
    • 集成从内部和外部系统接收的信息,这些信息通常需要以事务方式格式化,验证和处理到记录系统中。 批处理用于每天为企业处理数十亿的交易。

    Spring Batch是一个轻量级,全面的批处理框架,旨在开发对企业系统日常运营至关重要的强大批处理应用程序。 Spring Batch构建了人们期望的Spring Framework特性(生产力,基于POJO的开发方法和一般易用性),同时使开发人员可以在必要时轻松访问和利用更高级的企业服务。 Spring Batch不是一个schuedling的框架。

    Spring Batch提供了可重用的功能,这些功能对于处理大量的数据至关重要,包括记录/跟踪,事务管理,作业处理统计,作业重启,跳过和资源管理。 它还提供更高级的技术服务和功能,通过优化和分区技术实现极高容量和高性能的批处理作业。 Spring Batch可用于两种简单的用例(例如将文件读入数据库或运行存储过程)以及复杂的大量用例(例如在数据库之间移动大量数据,转换它等等) 上)。 大批量批处理作业可以高度可扩展的方式利用该框架来处理大量信息。

    Spring Batch架构介绍

    一个典型的批处理应用程序大致如下:

    • 从数据库,文件或队列中读取大量记录。
    • 以某种方式处理数据。
    • 以修改之后的形式写回数据。

    其对应的示意图如下:

     

    spring batch的一个总体的架构如下:

    Figure 2.1: Batch Stereotypes

    在spring batch中一个job可以定义很多的步骤step,在每一个step里面可以定义其专属的ItemReader用于读取数据,ItemProcesseor用于处理数据,ItemWriter用于写数据,而每一个定义的job则都在JobRepository里面,我们可以通过JobLauncher来启动某一个job。

    Spring Batch核心概念介绍

    下面是一些概念是Spring batch框架中的核心概念。

    什么是Job

    Job和Step是spring batch执行批处理任务最为核心的两个概念。

    其中Job是一个封装整个批处理过程的一个概念。Job在spring batch的体系当中只是一个最顶层的一个抽象概念,体现在代码当中则它只是一个最上层的接口,其代码如下:

    
    /**
     * Batch domain object representing a job. Job is an explicit abstraction
     * representing the configuration of a job specified by a developer. It should
     * be noted that restart policy is applied to the job as a whole and not to a
     * step.
     */
    public interface Job {
    
    	String getName();
    
    
    	boolean isRestartable();
    
    
    	void execute(JobExecution execution);
    
    
    	JobParametersIncrementer getJobParametersIncrementer();
    
    
    	JobParametersValidator getJobParametersValidator();
    
    }

    在Job这个接口当中定义了五个方法,它的实现类主要有两种类型的job,一个是simplejob,另一个是flowjob。在spring batch当中,job是最顶层的抽象,除job之外我们还有JobInstance以及JobExecution这两个更加底层的抽象。

    一个job是我们运行的基本单位,它内部由step组成。job本质上可以看成step的一个容器。一个job可以按照指定的逻辑顺序组合step,并提供了我们给所有step设置相同属性的方法,例如一些事件监听,跳过策略。

    Spring Batch以SimpleJob类的形式提供了Job接口的默认简单实现,它在Job之上创建了一些标准功能。一个使用java config的例子代码如下:

    @Bean
    public Job footballJob() {
        return this.jobBuilderFactory.get("footballJob")
                         .start(playerLoad())
                         .next(gameLoad())
                         .next(playerSummarization())
                         .end()
                         .build();
    }

    这个配置的意思是:首先给这个job起了一个名字叫footballJob,接着指定了这个job的三个step,他们分别由方法,playerLoad,gameLoad, playerSummarization实现。

    什么是JobInstance

    我们在上文已经提到了JobInstance,他是Job的更加底层的一个抽象,他的定义如下:

    public interface JobInstance {
    	/**
    	 * Get unique id for this JobInstance.
    	 * @return instance id
    	 */
    	public long getInstanceId();
    	/**
    	 * Get job name.
    	 * @return value of 'id' attribute from <job>
    	 */
    	public String getJobName();	
    }
    

    他的方法很简单,一个是返回Job的id,另一个是返回Job的名字。

    JobInstance指的是job运行当中,作业执行过程当中的概念。Instance本就是实例的意思。

    比如说现在有一个批处理的job,它的功能是在一天结束时执行行一次。我们假定这个批处理job的名字为'EndOfDay'。在这个情况下,那么每天就会有一个逻辑意义上的JobInstance, 而我们必须记录job的每次运行的情况。

    什么是JobParameters

    在上文当中我们提到了,同一个job每天运行一次的话,那么每天都有一个jobIntsance,但他们的job定义都是一样的,那么我们怎么来区别一个job的不同jobinstance了。 不妨先做个猜想,虽然jobinstance的job定义一样,但是他们有的东西就不一样,例如运行时间。

    spring batch中提供的用来标识一个jobinstance的东西是:JobParameters。 JobParameters对象包含一组用于启动批处理作业的参数,它可以在运行期间用于识别或甚至用作参考数据。我们假设的运行时间,就可以作为一个JobParameters。

    例如, 我们前面的'EndOfDay'的job现在已经有了两个实例,一个产生于1月1日,另一个产生于1月2日,那么我们就可以定义两个JobParameter对象:一个的参数是01-01, 另一个的参数是01-02。 因此,识别一个JobInstance的方法可以定义为:

    因此,我么可以通过Jobparameter来操作正确的JobInstance

     什么是JobExecution

    JobExecution指的是单次尝试运行一个我们定义好的Job的代码层面的概念。 job的一次执行可能以失败也可能成功。只有当执行成功完成时,给定的与执行相对应的JobInstance才也被视为完成。

    还是以前面描述的EndOfDay的job作为示例,假设第一次运行01-01-2019的JobInstance结果是失败。 那么此时如果使用与第一次运行相同的Jobparameter参数(即01-01-2019)作业参数再次运行,那么就会创建一个对应于之前jobInstance的一个新的JobExecution实例,JobInstance仍然只有一个。

    JobExecution的接口定义如下:

    public interface JobExecution {
    	/**
    	 * Get unique id for this JobExecution.
    	 * @return execution id
    	 */
    	public long getExecutionId();
    	/**
    	 * Get job name.
    	 * @return value of 'id' attribute from <job>
    	 */
    	public String getJobName(); 
    	/**
    	 * Get batch status of this execution.
    	 * @return batch status value.
    	 */
    	public BatchStatus getBatchStatus();
    	/**
    	 * Get time execution entered STARTED status. 
    	 * @return date (time)
    	 */
    	public Date getStartTime();
    	/**
    	 * Get time execution entered end status: COMPLETED, STOPPED, FAILED 
    	 * @return date (time)
    	 */
    	public Date getEndTime();
    	/**
    	 * Get execution exit status.
    	 * @return exit status.
    	 */
    	public String getExitStatus();
    	/**
    	 * Get time execution was created.
    	 * @return date (time)
    	 */
    	public Date getCreateTime();
    	/**
    	 * Get time execution was last updated updated.
    	 * @return date (time)
    	 */
    	public Date getLastUpdatedTime();
    	/**
    	 * Get job parameters for this execution.
    	 * @return job parameters  
    	 */
    	public Properties getJobParameters();
    	
    }
    

    每一个方法的注释已经解释的很清楚,这里不再多做解释。只提一下BatchStatus,JobExecution当中提供了一个方法getBatchStatus用于获取一个job某一次特地执行的一个状态。BatchStatus是一个代表job状态的枚举类,其定义如下:

    public enum BatchStatus {STARTING, STARTED, STOPPING, 
    			STOPPED, FAILED, COMPLETED, ABANDONED }

    这些属性对于一个job的执行来说是非常关键的信息,并且spring batch会将他们持久到数据库当中. 在使用Spring batch的过程当中spring batch会自动创建一些表用于存储一些job相关的信息,用于存储JobExecution的表为batch_job_execution,下面是一个从数据库当中截图的实例:

    什么是Step

    每一个Step对象都封装了批处理作业的一个独立的阶段。 事实上,每一个Job本质上都是由一个或多个步骤组成。 每一个step包含定义和控制实际批处理所需的所有信息。 任何特定的内容都由编写Job的开发人员自行决定。 一个step可以非常简单也可以非常复杂。 例如,一个step的功能是将文件中的数据加载到数据库中,那么基于现在spring batch的支持则几乎不需要写代码。 更复杂的step可能具有复杂的业务逻辑,这些逻辑作为处理的一部分。 与Job一样,Step具有与JobExecution类似的StepExecution,如下图所示:

     

    什么是StepExecution

    StepExecution表示一次执行Step, 每次运行一个Step时都会创建一个新的StepExecution,类似于JobExecution。 但是,某个步骤可能由于其之前的步骤失败而无法执行。 且仅当Step实际启动时才会创建StepExecution。

    一次step执行的实例由StepExecution类的对象表示。 每个StepExecution都包含对其相应步骤的引用以及JobExecution和事务相关的数据,例如提交和回滚计数以及开始和结束时间。 此外,每个步骤执行都包含一个ExecutionContext,其中包含开发人员需要在批处理运行中保留的任何数据,例如重新启动所需的统计信息或状态信息。下面是一个从数据库当中截图的实例:

    什么是ExecutionContext

    ExecutionContext即每一个StepExecution 的执行环境。它包含一系列的键值对。我们可以用如下代码获取ExecutionContext

    ExecutionContext ecStep = stepExecution.getExecutionContext();
    ExecutionContext ecJob = jobExecution.getExecutionContext();

     什么是JobRepository

    JobRepository是一个用于将上述job,step等概念进行持久化的一个类。 它同时给Job和Step以及下文会提到的JobLauncher实现提供CRUD操作。 首次启动Job时,将从repository中获取JobExecution,并且在执行批处理的过程中,StepExecution和JobExecution将被存储到repository当中。

    @EnableBatchProcessing注解可以为JobRepository提供自动配置。

    什么是JobLauncher

    JobLauncher这个接口的功能非常简单,它是用于启动指定了JobParameters的Job,为什么这里要强调指定了JobParameter,原因其实我们在前面已经提到了,jobparameter和job一起才能组成一次job的执行。下面是代码实例:

    public interface JobLauncher {
    
    public JobExecution run(Job job, JobParameters jobParameters)
                throws JobExecutionAlreadyRunningException, JobRestartException,
                       JobInstanceAlreadyCompleteException, JobParametersInvalidException;
    }

    上面run方法实现的功能是根据传入的job以及jobparamaters从JobRepository获取一个JobExecution并执行Job。

    什么是Item Reader

    ItemReader是一个读数据的抽象,它的功能是为每一个Step提供数据输入。 当ItemReader以及读完所有数据时,它会返回null来告诉后续操作数据已经读完。Spring Batch为ItemReader提供了非常多的有用的实现类,比如JdbcPagingItemReader,JdbcCursorItemReader等等。

    ItemReader支持的读入的数据源也是非常丰富的,包括各种类型的数据库,文件,数据流,等等。几乎涵盖了我们的所有场景。

    下面是一个JdbcPagingItemReader的例子代码:

    @Bean
    public JdbcPagingItemReader itemReader(DataSource dataSource, PagingQueryProvider queryProvider) {
            Map<String, Object> parameterValues = new HashMap<>();
            parameterValues.put("status", "NEW");
    
            return new JdbcPagingItemReaderBuilder<CustomerCredit>()
                                               .name("creditReader")
                                               .dataSource(dataSource)
                                               .queryProvider(queryProvider)
                                               .parameterValues(parameterValues)
                                               .rowMapper(customerCreditMapper())
                                               .pageSize(1000)
                                               .build();
    }
    
    @Bean
    public SqlPagingQueryProviderFactoryBean queryProvider() {
            SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
    
            provider.setSelectClause("select id, name, credit");
            provider.setFromClause("from customer");
            provider.setWhereClause("where status=:status");
            provider.setSortKey("id");
    
            return provider;
    }

    JdbcPagingItemReader必须指定一个PagingQueryProvider,负责提供SQL查询语句来按分页返回数据。

    下面是一个JdbcCursorItemReader的例子代码:

     private JdbcCursorItemReader<Map<String, Object>> buildItemReader(final DataSource dataSource, String tableName,
                String tenant) {
    
            JdbcCursorItemReader<Map<String, Object>> itemReader = new JdbcCursorItemReader<>();
            itemReader.setDataSource(dataSource);
            itemReader.setSql("sql here");
            itemReader.setRowMapper(new RowMapper());
            return itemReader;
        }

     

    什么是Item Writer

    既然ItemReader是读数据的一个抽象,那么ItemWriter自然就是一个写数据的抽象,它是为每一个step提供数据写出的功能。写的单位是可以配置的,我们可以一次写一条数据,也可以一次写一个chunk的数据,关于chunk下文会有专门的介绍。ItemWriter对于读入的数据是不能做任何操作的。

    Spring Batch为ItemWriter也提供了非常多的有用的实现类,当然我们也可以去实现自己的writer功能。

    什么是Item Processor

    ItemProcessor对项目的业务逻辑处理的一个抽象, 当ItemReader读取到一条记录之后,ItemWriter还未写入这条记录之前,I我们可以借助temProcessor提供一个处理业务逻辑的功能,并对数据进行相应操作。如果我们在ItemProcessor发现一条数据不应该被写入,可以通过返回null来表示。ItemProcessor和ItemReader以及ItemWriter可以非常好的结合在一起工作,他们之间的数据传输也非常方便。我们直接使用即可。

     

    chunk 处理流程

    spring batch提供了让我们按照chunk处理数据的能力,一个chunk的示意图如下:

    它的意思就和图示的一样,由于我们一次batch的任务可能会有很多的数据读写操作,因此一条一条的处理并向数据库提交的话效率不会很高,因此spring batch提供了chunk这个概念,我们可以设定一个chunk size,spring batch 将一条一条处理数据,但不提交到数据库,只有当处理的数据数量达到chunk size设定的值得时候,才一起去commit.

    java的实例定义代码如下:

    在上面这个step里面,chunk size被设为了10,当ItemReader读的数据数量达到10的时候,这一批次的数据就一起被传到itemWriter,同时transaction被提交。

    skip策略和失败处理

    一个batch的job的step,可能会处理非常大数量的数据,难免会遇到出错的情况,出错的情况虽出现的概率较小,但是我们不得不考虑这些情况,因为我们做数据迁移最重要的是要保证数据的最终一致性。spring batch当然也考虑到了这种情况,并且为我们提供了相关的技术支持,请看如下bean的配置:

    我们需要留意这三个方法,分别是skipLimit(),skip(),noSkip(),

    skipLimit方法的意思是我们可以设定一个我们允许的这个step可以跳过的异常数量,假如我们设定为10,则当这个step运行时,只要出现的异常数目不超过10,整个step都不会fail。注意,若不设定skipLimit,则其默认值是0.

    skip方法我们可以指定我们可以跳过的异常,因为有些异常的出现,我们是可以忽略的。

    noSkip方法的意思则是指出现这个异常我们不想跳过,也就是从skip的所以exception当中排除这个exception,从上面的例子来说,也就是跳过所有除FileNotFoundException的exception。那么对于这个step来说,FileNotFoundException就是一个fatal的exception,抛出这个exception的时候step就会直接fail

    批处理操作指南

    本部分是一些使用spring batch时的值得注意的点

    批处理原则

    在构建批处理解决方案时,应考虑以下关键原则和注意事项。

    • 批处理体系结构通常会影响体系结构
    • 尽可能简化并避免在单批应用程序中构建复杂的逻辑结构
    • 保持数据的处理和存储在物理上靠得很近(换句话说,将数据保存在处理过程中)。
    • 最大限度地减少系统资源的使用,尤其是I / O. 在internal memory中执行尽可能多的操作。
    • 查看应用程序I / O(分析SQL语句)以确保避免不必要的物理I / O. 特别是,需要寻找以下四个常见缺陷:
      1. 当数据可以被读取一次并缓存或保存在工作存储中时,读取每个事务的数据。

      2. 重新读取先前在同一事务中读取数据的事务的数据。

      3. 导致不必要的表或索引扫描。

      4. 未在SQL语句的WHERE子句中指定键值。

    • 在批处理运行中不要做两次一样的事情。 例如,如果需要数据汇总以用于报告目的,则应该(如果可能)在最初处理数据时递增存储的总计,因此您的报告应用程序不必重新处理相同的数据。

    • 在批处理应用程序开始时分配足够的内存,以避免在此过程中进行耗时的重新分配。

    • 总是假设数据完整性最差。 插入适当的检查和记录验证以维护数据完整性。

    • 尽可能实施校验和以进行内部验证。 例如,对于一个文件里的数据应该有一个数据条数纪录,告诉文件中的记录总数以及关键字段的汇总。

    • 在具有真实数据量的类似生产环境中尽早计划和执行压力测试。

    • 在大批量系统中,数据备份可能具有挑战性,特别是如果系统以24-7在线的情况运行。 数据库备份通常在在线设计中得到很好的处理,但文件备份应该被视为同样重要。 如果系统依赖于文件,则文件备份过程不仅应该到位并记录在案,还应定期进行测试。

    如何默认不启动job

    在使用java config使用spring batch的job时,如果不做任何配置,项目在启动时就会默认去跑我们定义好的批处理job。那么如何让项目在启动时不自动去跑job呢?

    spring batch的job会在项目启动时自动run,如果我们不想让他在启动时run的话,可以在application.properties中添加如下属性:

    spring.batch.job.enabled=false
    

    在读数据时内存不够

    在使用spring batch做数据迁移时,发现在job启动后,执行到一定时间点时就卡在一个地方不动了,且log也不再打印,等待一段时间之后,得到如下错误:

    红字的信息为:Resource exhaustion event:the JVM was unable to allocate memory from the heap.

    翻译过来的意思就是项目发出了一个资源耗尽的事件,告诉我们java虚拟机无法再为堆分配内存。

    造成这个错误的原因是: 这个项目里的batch job的reader是一次性拿回了数据库里的所有数据,并没有进行分页,当这个数据量太大时,就会导致内存不够用。解决的办法有两个:

    • 调整reader读数据逻辑,按分页读取,但实现上会麻烦一些,且运行效率会下降
    • 增大service内存

    更多信息请参考博客:spring batch使用reader读数据的内存容量问题

     

    展开全文
  • 小疯手把手带你整合SpringMVC+Spring+MyBatis三大框架,俗称SSM,用它完全代替传统的SSH框架,把它们最优雅的一面发挥出来。整合配置结束后,会有一个应用实例“图书管理系统”带给大家,希望能快速上手这个框架

    本文发表于2016年6月,写于作者学生时期。文中使用到的技术和框架可能不是当下最佳实践,甚至很不“优雅”。但对于刚接触JavaEE和Spring的同学来说,还是能有很多收获的,大牛轻拍= =

    我们看招聘信息的时候,经常会看到这一点,需要具备SSH框架的技能;而且在大部分教学课堂中,也会把SSH作为最核心的教学内容。
    但是,我们在实际应用中发现,SpringMVC可以完全替代Struts,配合注解的方式,编程非常快捷,而且通过restful风格定义url,让地址看起来非常优雅。
    另外,MyBatis也可以替换Hibernate,正因为MyBatis的半自动特点,我们程序猿可以完全掌控SQL,这会让有数据库经验的程序猿能开发出高效率的SQL语句,而且XML配置管理起来也非常方便。
    好了,如果你也认同我的看法,那么下面我们一起来做整合吧!

    在写代码之前我们先了解一下这三个框架分别是干什么的?
    相信大以前也看过不少这些概念,我这就用大白话来讲,如果之前有了解过可以跳过这一大段,直接看代码!

    1. SpringMVC:它用于web层,相当于controller(等价于传统的servlet和struts的action),用来处理用户请求。举个例子,用户在地址栏输入http://网站域名/login,那么springmvc就会拦截到这个请求,并且调用controller层中相应的方法,(中间可能包含验证用户名和密码的业务逻辑,以及查询数据库操作,但这些都不是springmvc的职责),最终把结果返回给用户,并且返回相应的页面(当然也可以只返回json/xml等格式数据)。springmvc就是做前面和后面过程的活,与用户打交道!!

    2. Spring:太强大了,以至于我无法用一个词或一句话来概括它。但与我们平时开发接触最多的估计就是IOC容器,它可以装载bean(也就是我们java中的类,当然也包括service dao里面的),有了这个机制,我们就不用在每次使用这个类的时候为它初始化,很少看到关键字new。另外spring的aop,事务管理等等都是我们经常用到的。

    3. MyBatis:如果你问我它跟鼎鼎大名的Hibernate有什么区别?我只想说,他更符合我的需求。第一,它能自由控制sql,这会让有数据库经验的人(当然不是说我啦捂脸)编写的代码能搞提升数据库访问的效率。第二,它可以使用xml的方式来组织管理我们的sql,因为一般程序出错很多情况下是sql出错,别人接手代码后能快速找到出错地方,甚至可以优化原来写的sql。


    SSM框架整合配置

    好了,前面bb那么多,下面我们真正开始敲代码了~

    首先我们打开IED,我这里用的是eclipse(你们应该也是用的这个,对吗?),创建一个动态web项目,建立好相应的目录结构(重点!)

    项目结构图

    (打了马赛克是因为这里还用不到,你们不要那么污好不好?)

    我说一下每个目录都有什么用吧(第一次画表格,我发现markdown的表格语法很不友好呀~)
    这个目录结构同时也遵循maven的目录规范~

    文件名 作用
    src 根目录,没什么好说的,下面有main和test。
    - main 主要目录,可以放java代码和一些资源文件。
    - - java 存放我们的java代码,这个文件夹要使用Build Path -> Use as Source Folder,这样看包结构会方便很多,新建的包就相当于在这里新建文件夹咯。
    - - resources 存放资源文件,譬如各种的spring,mybatis,log配置文件。
    - - - mapper 存放dao中每个方法对应的sql,在这里配置,无需写daoImpl。
    - - - spring 这里当然是存放spring相关的配置文件,有dao service web三层。
    - - - sql 其实这个可以没有,但是为了项目完整性还是加上吧。
    - - - webapp 这个貌似是最熟悉的目录了,用来存放我们前端的静态资源,如jsp js css。
    - - - - resources 这里的资源是指项目的静态资源,如js css images等。
    - - - - WEB-INF 很重要的一个目录,外部浏览器无法访问,只有羡慕内部才能访问,可以把jsp放在这里,另外就是web.xml了。你可能有疑问了,为什么上面java中的resources里面的配置文件不妨在这里,那么是不是会被外部窃取到?你想太多了,部署时候基本上只有webapp里的会直接输出到根目录,其他都会放入WEB-INF里面,项目内部依然可以使用classpath:XXX来访问,好像IDE里可以设置部署输出目录,这里扯远了~
    - test 这里是测试分支。
    - - java 测试java代码,应遵循包名相同的原则,这个文件夹同样要使用Build Path -> Use as Source Folder,这样看包结构会方便很多。
    - - resources 没什么好说的,好像也很少用到,但这个是maven的规范。

    我先新建好几个必要的,并为大家讲解一下每个包的作用,顺便理清一下后台的思路~

    包结构图

    包名 名称 作用
    dao 数据访问层(接口) 与数据打交道,可以是数据库操作,也可以是文件读写操作,甚至是redis缓存操作,总之与数据操作有关的都放在这里,也有人叫做dal或者数据持久层都差不多意思。为什么没有daoImpl,因为我们用的是mybatis,所以可以直接在配置文件中实现接口的每个方法。
    entity 实体类 一般与数据库的表相对应,封装dao层取出来的数据为一个对象,也就是我们常说的pojo,一般只在dao层与service层之间传输。
    dto 数据传输层 刚学框架的人可能不明白这个有什么用,其实就是用于service层与web层之间传输,为什么不直接用entity(pojo)?其实在实际开发中发现,很多时间一个entity并不能满足我们的业务需求,可能呈现给用户的信息十分之多,这时候就有了dto,也相当于vo,记住一定不要把这个混杂在entity里面,答应我好吗?
    service 业务逻辑(接口) 写我们的业务逻辑,也有人叫bll,在设计业务接口时候应该站在“使用者”的角度。额,不要问我为什么这里没显示!IDE调皮我也拿它没办法~
    serviceImpl 业务逻辑(实现) 实现我们业务接口,一般事务控制是写在这里,没什么好说的。
    web 控制器 springmvc就是在这里发挥作用的,一般人叫做controller控制器,相当于struts中的action。

    还有最后一步基础工作,导入我们相应的jar包,我使用的是maven来管理我们的jar,所以只需要在pom.xml中加入相应的依赖就好了,如果不使用maven的可以自己去官网下载相应的jar,放到项目WEB-INF/lib目录下。关于maven的学习大家可以看慕课网的视频教程,这里就不展开了。我把项目用到的jar都写在下面,版本都不是最新的,大家有经验的话可以自己调整版本号。另外,所有jar都会与项目一起打包放到我的github上,喜欢的给个star吧~

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<groupId>com.soecode.ssm</groupId>
    	<artifactId>ssm</artifactId>
    	<packaging>war</packaging>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>ssm Maven Webapp</name>
    	<url>http://github.com/liyifeng1994/ssm</url>
    	<dependencies>
    		<!-- 单元测试 -->
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>4.11</version>
    		</dependency>
    
    		<!-- 1.日志 -->
    		<!-- 实现slf4j接口并整合 -->
    		<dependency>
    			<groupId>ch.qos.logback</groupId>
    			<artifactId>logback-classic</artifactId>
    			<version>1.1.1</version>
    		</dependency>
    
    		<!-- 2.数据库 -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<version>5.1.37</version>
    			<scope>runtime</scope>
    		</dependency>
    		<dependency>
    			<groupId>c3p0</groupId>
    			<artifactId>c3p0</artifactId>
    			<version>0.9.1.2</version>
    		</dependency>
    
    		<!-- DAO: MyBatis -->
    		<dependency>
    			<groupId>org.mybatis</groupId>
    			<artifactId>mybatis</artifactId>
    			<version>3.3.0</version>
    		</dependency>
    		<dependency>
    			<groupId>org.mybatis</groupId>
    			<artifactId>mybatis-spring</artifactId>
    			<version>1.2.3</version>
    		</dependency>
    
    		<!-- 3.Servlet web -->
    		<dependency>
    			<groupId>taglibs</groupId>
    			<artifactId>standard</artifactId>
    			<version>1.1.2</version>
    		</dependency>
    		<dependency>
    			<groupId>jstl</groupId>
    			<artifactId>jstl</artifactId>
    			<version>1.2</version>
    		</dependency>
    		<dependency>
    			<groupId>com.fasterxml.jackson.core</groupId>
    			<artifactId>jackson-databind</artifactId>
    			<version>2.5.4</version>
    		</dependency>
    		<dependency>
    			<groupId>javax.servlet</groupId>
    			<artifactId>javax.servlet-api</artifactId>
    			<version>3.1.0</version>
    		</dependency>
    
    		<!-- 4.Spring -->
    		<!-- 1)Spring核心 -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-core</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-beans</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-context</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<!-- 2)Spring DAO层 -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-jdbc</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-tx</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<!-- 3)Spring web -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-web</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-webmvc</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    		<!-- 4)Spring test -->
    		<dependency>
    			<groupId>org.springframework</groupId>
    			<artifactId>spring-test</artifactId>
    			<version>4.1.7.RELEASE</version>
    		</dependency>
    
    		<!-- redis客户端:Jedis -->
    		<dependency>
    			<groupId>redis.clients</groupId>
    			<artifactId>jedis</artifactId>
    			<version>2.7.3</version>
    		</dependency>
    		<dependency>
    			<groupId>com.dyuproject.protostuff</groupId>
    			<artifactId>protostuff-core</artifactId>
    			<version>1.0.8</version>
    		</dependency>
    		<dependency>
    			<groupId>com.dyuproject.protostuff</groupId>
    			<artifactId>protostuff-runtime</artifactId>
    			<version>1.0.8</version>
    		</dependency>
    
    		<!-- Map工具类 -->
    		<dependency>
    			<groupId>commons-collections</groupId>
    			<artifactId>commons-collections</artifactId>
    			<version>3.2</version>
    		</dependency>
    	</dependencies>
    	<build>
    		<finalName>ssm</finalName>
    	</build>
    </project>
    
    

    下面真的要开始进行编码工作了,坚持到这里辛苦大家了~

    第一步:我们先在spring文件夹里新建spring-dao.xml文件,因为spring的配置太多,我们这里分三层,分别是dao service web。

    1. 读入数据库连接相关参数(可选)
    2. 配置数据连接池
    3. 配置连接属性,可以不读配置项文件直接在这里写死
    4. 配置c3p0,只配了几个常用的
    5. 配置SqlSessionFactory对象(mybatis)
    6. 扫描dao层接口,动态实现dao接口,也就是说不需要daoImpl,sql和参数都写在xml文件上

    spring-dao.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" xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd">
    	<!-- 配置整合mybatis过程 -->
    	<!-- 1.配置数据库相关参数properties的属性:${url} -->
    	<context:property-placeholder location="classpath:jdbc.properties" />
    
    	<!-- 2.数据库连接池 -->
    	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    		<!-- 配置连接池属性 -->
    		<property name="driverClass" value="${jdbc.driver}" />
    		<property name="jdbcUrl" value="${jdbc.url}" />
    		<property name="user" value="${jdbc.username}" />
    		<property name="password" value="${jdbc.password}" />
    
    		<!-- c3p0连接池的私有属性 -->
    		<property name="maxPoolSize" value="30" />
    		<property name="minPoolSize" value="10" />
    		<!-- 关闭连接后不自动commit -->
    		<property name="autoCommitOnClose" value="false" />
    		<!-- 获取连接超时时间 -->
    		<property name="checkoutTimeout" value="10000" />
    		<!-- 当获取连接失败重试次数 -->
    		<property name="acquireRetryAttempts" value="2" />
    	</bean>
    
    	<!-- 3.配置SqlSessionFactory对象 -->
    	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<!-- 注入数据库连接池 -->
    		<property name="dataSource" ref="dataSource" />
    		<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
    		<property name="configLocation" value="classpath:mybatis-config.xml" />
    		<!-- 扫描entity包 使用别名 -->
    		<property name="typeAliasesPackage" value="com.soecode.lyf.entity" />
    		<!-- 扫描sql配置文件:mapper需要的xml文件 -->
    		<property name="mapperLocations" value="classpath:mapper/*.xml" />
    	</bean>
    
    	<!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
    	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<!-- 注入sqlSessionFactory -->
    		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    		<!-- 给出需要扫描Dao接口包 -->
    		<property name="basePackage" value="com.soecode.lyf.dao" />
    	</bean>
    </beans>
    

    因为数据库配置相关参数是读取配置文件,所以在resources文件夹里新建一个jdbc.properties文件,存放我们4个最常见的数据库连接属性,这是我本地的,大家记得修改呀~还有喜欢传到github上“大头虾们”记得删掉密码,不然别人就很容易得到你服务器的数据库配置信息,然后干一些羞羞的事情,你懂的!!

    jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3307/ssm?useUnicode=true&characterEncoding=utf8
    jdbc.username=root
    jdbc.password=
    

    友情提示:配置文件中的jdbc.username,如果写成username,可能会与系统环境中的username变量冲突,所以到时候真正连接数据库的时候,用户名就被替换成系统中的用户名(有得可能是administrator),那肯定是连接不成功的,这里有个小坑,我被坑了一晚上!!

    因为这里用到了mybatis,所以需要配置mybatis核心文件,在recources文件夹里新建mybatis-config.xml文件。

    1. 使用自增主键
    2. 使用列别名
    3. 开启驼峰命名转换 create_time -> createTime

    mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    	<!-- 配置全局属性 -->
    	<settings>
    		<!-- 使用jdbc的getGeneratedKeys获取数据库自增主键值 -->
    		<setting name="useGeneratedKeys" value="true" />
    
    		<!-- 使用列别名替换列名 默认:true -->
    		<setting name="useColumnLabel" value="true" />
    
    		<!-- 开启驼峰命名转换:Table{create_time} -> Entity{createTime} -->
    		<setting name="mapUnderscoreToCamelCase" value="true" />
    	</settings>
    </configuration>
    

    第二步:刚弄好dao层,接下来到service层了。在spring文件夹里新建spring-service.xml文件。

    1. 扫描service包所有注解 @Service
    2. 配置事务管理器,把事务管理交由spring来完成
    3. 配置基于注解的声明式事务,可以直接在方法上@Transaction

    spring-service.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" 
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:tx="http://www.springframework.org/schema/tx"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd
    	http://www.springframework.org/schema/tx
    	http://www.springframework.org/schema/tx/spring-tx.xsd">
    	<!-- 扫描service包下所有使用注解的类型 -->
    	<context:component-scan base-package="com.soecode.lyf.service" />
    
    	<!-- 配置事务管理器 -->
    	<bean id="transactionManager"
    		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    		<!-- 注入数据库连接池 -->
    		<property name="dataSource" ref="dataSource" />
    	</bean>
    
    	<!-- 配置基于注解的声明式事务 -->
    	<tx:annotation-driven transaction-manager="transactionManager" />
    </beans>
    

    第三步:配置web层,在spring文件夹里新建spring-web.xml文件。

    1. 开启SpringMVC注解模式,可以使用@RequestMapping,@PathVariable,@ResponseBody等
    2. 对静态资源处理,如js,css,jpg等
    3. 配置jsp 显示ViewResolver,例如在controller中某个方法返回一个string类型的"login",实际上会返回"/WEB-INF/login.jsp"
    4. 扫描web层 @Controller

    spring-web.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"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:mvc="http://www.springframework.org/schema/mvc" 
    	xsi:schemaLocation="http://www.springframework.org/schema/beans
    	http://www.springframework.org/schema/beans/spring-beans.xsd
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd
    	http://www.springframework.org/schema/mvc
    	http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
    	<!-- 配置SpringMVC -->
    	<!-- 1.开启SpringMVC注解模式 -->
    	<!-- 简化配置: 
    		(1)自动注册DefaultAnootationHandlerMapping,AnotationMethodHandlerAdapter 
    		(2)提供一些列:数据绑定,数字和日期的format @NumberFormat, @DateTimeFormat, xml,json默认读写支持 
    	-->
    	<mvc:annotation-driven />
    	
    	<!-- 2.静态资源默认servlet配置
    		(1)加入对静态资源的处理:js,gif,png
    		(2)允许使用"/"做整体映射
    	 -->
    	 <mvc:default-servlet-handler/>
    	 
    	 <!-- 3.配置jsp 显示ViewResolver -->
    	 <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    	 	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
    	 	<property name="prefix" value="/WEB-INF/jsp/" />
    	 	<property name="suffix" value=".jsp" />
    	 </bean>
    	 
    	 <!-- 4.扫描web相关的bean -->
    	 <context:component-scan base-package="com.soecode.lyf.web" />
    </beans>
    

    第四步:最后就是修改web.xml文件了,它在webappWEB-INF下。

    web.xml

    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                          http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    	version="3.1" metadata-complete="true">
    	<!-- 如果是用mvn命令生成的xml,需要修改servlet版本为3.1 -->
    	<!-- 配置DispatcherServlet -->
    	<servlet>
    		<servlet-name>seckill-dispatcher</servlet-name>
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    		<!-- 配置springMVC需要加载的配置文件
    			spring-dao.xml,spring-service.xml,spring-web.xml
    			Mybatis - > spring -> springmvc
    		 -->
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<param-value>classpath:spring/spring-*.xml</param-value>
    		</init-param>
    	</servlet>
    	<servlet-mapping>
    		<servlet-name>seckill-dispatcher</servlet-name>
    		<!-- 默认匹配所有的请求 -->
    		<url-pattern>/</url-pattern>
    	</servlet-mapping>
    </web-app>
    
    

    我们在项目中经常会使用到日志,所以这里还有配置日志xml,在resources文件夹里新建logback.xml文件,所给出的日志输出格式也是最基本的控制台s呼出,大家有兴趣查看logback官方文档

    logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="true">
    	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    		<!-- encoders are by default assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    		<encoder>
    			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    		</encoder>
    	</appender>
    
    	<root level="debug">
    		<appender-ref ref="STDOUT" />
    	</root>
    </configuration>
    

    到目前为止,我们一共写了7个配置文件,我们一起来看下最终的配置文件结构图

    配置文件结构图


    SSM框架应用实例(图书管理系统)

    一开始想就这样结束教程,但是发现其实很多人都还不会把这个SSM框架用起来,特别是mybatis部分。那我现在就以最常见的“图书管理系统”中【查询图书】和【预约图书】业务来做一个demo吧!

    首先新建数据库名为ssm,再创建两张表:图书表book和预约图书表appointment,并且为book表初始化一些数据,sql如下。

    schema.sql

    -- 创建图书表
    CREATE TABLE `book` (
      `book_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '图书ID',
      `name` varchar(100) NOT NULL COMMENT '图书名称',
      `number` int(11) NOT NULL COMMENT '馆藏数量',
      PRIMARY KEY (`book_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='图书表'
    
    -- 初始化图书数据
    INSERT INTO `book` (`book_id`, `name`, `number`)
    VALUES
    	(1000, 'Java程序设计', 10),
    	(1001, '数据结构', 10),
    	(1002, '设计模式', 10),
    	(1003, '编译原理', 10)
    
    -- 创建预约图书表
    CREATE TABLE `appointment` (
      `book_id` bigint(20) NOT NULL COMMENT '图书ID',
      `student_id` bigint(20) NOT NULL COMMENT '学号',
      `appoint_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '预约时间' ,
      PRIMARY KEY (`book_id`, `student_id`),
      INDEX `idx_appoint_time` (`appoint_time`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='预约图书表'
    

    entity包中添加两个对应的实体,图书实体Book.java和预约图书实体Appointment.java

    Book.java

    package com.soecode.lyf.entity;
    
    public class Book {
    
    	private long bookId;// 图书ID
    
    	private String name;// 图书名称
    
    	private int number;// 馆藏数量
    
    	// 省略构造方法,getter和setter方法,toString方法
    
    }
    

    Appointment.java

    package com.soecode.lyf.entity;
    
    import java.util.Date;
    
    /**
     * 预约图书实体
     */
    public class Appointment {
    
    	private long bookId;// 图书ID
    
    	private long studentId;// 学号
    
    	private Date appointTime;// 预约时间
    
    	// 多对一的复合属性
    	private Book book;// 图书实体
    	
    	// 省略构造方法,getter和setter方法,toString方法
    
    }
    

    dao包新建接口BookDao.javaAppointment.java

    BookDao.java

    package com.soecode.lyf.dao;
    
    import java.util.List;
    
    import com.soecode.lyf.entity.Book;
    
    public interface BookDao {
    
    	/**
    	 * 通过ID查询单本图书
    	 * 
    	 * @param id
    	 * @return
    	 */
    	Book queryById(long id);
    
    	/**
    	 * 查询所有图书
    	 * 
    	 * @param offset 查询起始位置
    	 * @param limit 查询条数
    	 * @return
    	 */
    	List<Book> queryAll(@Param("offset") int offset, @Param("limit") int limit);
    
    	/**
    	 * 减少馆藏数量
    	 * 
    	 * @param bookId
    	 * @return 如果影响行数等于>1,表示更新的记录行数
    	 */
    	int reduceNumber(long bookId);
    }
    
    

    AppointmentDao.java

    package com.soecode.lyf.dao;
    
    import org.apache.ibatis.annotations.Param;
    
    import com.soecode.lyf.entity.Appointment;
    
    public interface AppointmentDao {
    
    	/**
    	 * 插入预约图书记录
    	 * 
    	 * @param bookId
    	 * @param studentId
    	 * @return 插入的行数
    	 */
    	int insertAppointment(@Param("bookId") long bookId, @Param("studentId") long studentId);
    
    	/**
    	 * 通过主键查询预约图书记录,并且携带图书实体
    	 * 
    	 * @param bookId
    	 * @param studentId
    	 * @return
    	 */
    	Appointment queryByKeyWithBook(@Param("bookId") long bookId, @Param("studentId") long studentId);
    
    }
    

    提示:这里为什么要给方法的参数添加@Param注解呢?是因为该方法有两个或以上的参数,一定要加,不然mybatis识别不了。上面的BookDao接口的queryById方法和reduceNumber方法只有一个参数book_id,所以可以不用加 @Param注解,当然加了也无所谓~


    注意,这里不需要实现dao接口不用编写daoImpl, mybatis会给我们动态实现,但是我们需要编写相应的mapper。
    mapper目录里新建两个文件BookDao.xmlAppointmentDao.xml,分别对应上面两个dao接口,代码如下。

    BookDao.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.soecode.lyf.dao.BookDao">
    	<!-- 目的:为dao接口方法提供sql语句配置 -->
    	<select id="queryById" resultType="Book" parameterType="long">
    		<!-- 具体的sql -->
    		SELECT
    			book_id,
    			name,
    			number
    		FROM
    			book
    		WHERE
    			book_id = #{bookId}
    	</select>
    	
    	<select id="queryAll" resultType="Book">
    		SELECT
    			book_id,
    			name,
    			number
    		FROM
    			book
    		ORDER BY
    			book_id
    		LIMIT #{offset}, #{limit}
    	</select>
    	
    	<update id="reduceNumber">
    		UPDATE book
    		SET number = number - 1
    		WHERE
    			book_id = #{bookId}
    		AND number > 0
    	</update>
    </mapper>
    

    AppointmentDao.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.soecode.lyf.dao.AppointmentDao">
    	<insert id="insertAppointment">
    		<!-- ignore 主键冲突,报错 -->
    		INSERT ignore INTO appointment (book_id, student_id)
    		VALUES (#{bookId}, #{studentId})
    	</insert>
    	
    	<select id="queryByKeyWithBook" resultType="Appointment">
    		<!-- 如何告诉MyBatis把结果映射到Appointment同时映射book属性 -->
    		<!-- 可以自由控制SQL -->
    		SELECT
    			a.book_id,
    			a.student_id,
    			a.appoint_time,
    			b.book_id "book.book_id",
    			b.`name` "book.name",
    			b.number "book.number"
    		FROM
    			appointment a
    		INNER JOIN book b ON a.book_id = b.book_id
    		WHERE
    			a.book_id = #{bookId}
    		AND a.student_id = #{studentId}
    	</select>
    </mapper>
    

    mapper总结namespace是该xml对应的接口全名,selectupdate中的id对应方法名,resultType是返回值类型,parameterType是参数类型(这个其实可选),最后#{...}中填写的是方法的参数,看懂了是不是很简单!!我也这么觉得~ 还有一个小技巧要交给大家,就是在返回Appointment对象包含了一个属性名为book的Book对象,那么可以使用"book.属性名"的方式来取值,看上面queryByKeyWithBook方法的sql。


    dao层写完了,接下来test对应的package写我们测试方法吧。
    因为我们之后会写很多测试方法,在测试前需要让程序读入spring-dao和mybatis等配置文件,所以我这里就抽离出来一个BaseTest类,只要是测试方法就继承它,这样那些繁琐的重复的代码就不用写那么多了~

    BaseTest.java

    package com.soecode.lyf;
    
    import org.junit.runner.RunWith;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    /**
     * 配置spring和junit整合,junit启动时加载springIOC容器 spring-test,junit
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    // 告诉junit spring配置文件
    @ContextConfiguration({ "classpath:spring/spring-dao.xml", "classpath:spring/spring-service.xml" })
    public class BaseTest {
    
    }
    
    

    因为spring-serviceservice层的测试中会时候到,这里也一起引入算了!

    新建BookDaoTest.javaAppointmentDaoTest.java两个dao测试文件。

    BookDaoTest.java

    package com.soecode.lyf.dao;
    
    import java.util.List;
    
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.soecode.lyf.BaseTest;
    import com.soecode.lyf.entity.Book;
    
    public class BookDaoTest extends BaseTest {
    
    	@Autowired
    	private BookDao bookDao;
    
    	@Test
    	public void testQueryById() throws Exception {
    		long bookId = 1000;
    		Book book = bookDao.queryById(bookId);
    		System.out.println(book);
    	}
    
    	@Test
    	public void testQueryAll() throws Exception {
    		List<Book> books = bookDao.queryAll(0, 4);
    		for (Book book : books) {
    			System.out.println(book);
    		}
    	}
    
    	@Test
    	public void testReduceNumber() throws Exception {
    		long bookId = 1000;
    		int update = bookDao.reduceNumber(bookId);
    		System.out.println("update=" + update);
    	}
    
    }
    

    BookDaoTest测试结果

    testQueryById
    testQueryById

    testQueryAll
    testQueryAll

    testReduceNumber
    testReduceNumber

    AppointmentDaoTest.java

    package com.soecode.lyf.dao;
    
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.soecode.lyf.BaseTest;
    import com.soecode.lyf.entity.Appointment;
    
    public class AppointmentDaoTest extends BaseTest {
    
    	@Autowired
    	private AppointmentDao appointmentDao;
    
    	@Test
    	public void testInsertAppointment() throws Exception {
    		long bookId = 1000;
    		long studentId = 12345678910L;
    		int insert = appointmentDao.insertAppointment(bookId, studentId);
    		System.out.println("insert=" + insert);
    	}
    
    	@Test
    	public void testQueryByKeyWithBook() throws Exception {
    		long bookId = 1000;
    		long studentId = 12345678910L;
    		Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
    		System.out.println(appointment);
    		System.out.println(appointment.getBook());
    	}
    
    }
    

    AppointmentDaoTest测试结果

    testInsertAppointment
    testInsertAppointment

    testQueryByKeyWithBook
    testQueryByKeyWithBook


    嗯,到这里一切到很顺利那么我们继续service层的编码吧可能下面开始信息里比较大,大家要做好心理准备~

    首先,在写我们的控制器之前,我们先定义几个预约图书操作返回码的数据字典,也就是我们要返回给客户端的信息。我们这类使用枚举类,没听过的小伙伴要好好恶补一下了(我也是最近才学到的= =)

    预约业务操作返回码说明

    返回码 说明
    1 预约成功
    0 库存不足
    -1 重复预约
    -2 系统异常

    新建一个包叫enums,在里面新建一个枚举类AppointStateEnum.java,用来定义预约业务的数据字典,没听懂没关系,我们直接看代码吧~是不是感觉有模有样了!

    AppointStateEnum.java

    package com.soecode.lyf.enums;
    
    /**
     * 使用枚举表述常量数据字典
     */
    public enum AppointStateEnum {
    
    	SUCCESS(1, "预约成功"), NO_NUMBER(0, "库存不足"), REPEAT_APPOINT(-1, "重复预约"), INNER_ERROR(-2, "系统异常");
    
    	private int state;
    
    	private String stateInfo;
    
    	private AppointStateEnum(int state, String stateInfo) {
    		this.state = state;
    		this.stateInfo = stateInfo;
    	}
    
    	public int getState() {
    		return state;
    	}
    
    	public String getStateInfo() {
    		return stateInfo;
    	}
    
    	public static AppointStateEnum stateOf(int index) {
    		for (AppointStateEnum state : values()) {
    			if (state.getState() == index) {
    				return state;
    			}
    		}
    		return null;
    	}
    
    }
    

    接下来,在dto包下新建AppointExecution.java用来存储我们执行预约操作的返回结果。

    AppointExecution.java

    package com.soecode.lyf.dto;
    
    import com.soecode.lyf.entity.Appointment;
    import com.soecode.lyf.enums.AppointStateEnum;
    
    /**
     * 封装预约执行后结果
     */
    public class AppointExecution {
    
    	// 图书ID
    	private long bookId;
    
    	// 秒杀预约结果状态
    	private int state;
    
    	// 状态标识
    	private String stateInfo;
    
    	// 预约成功对象
    	private Appointment appointment;
    
    	public AppointExecution() {
    	}
    
    	// 预约失败的构造器
    	public AppointExecution(long bookId, AppointStateEnum stateEnum) {
    		this.bookId = bookId;
    		this.state = stateEnum.getState();
    		this.stateInfo = stateEnum.getStateInfo();
    	}
    
    	// 预约成功的构造器
    	public AppointExecution(long bookId, AppointStateEnum stateEnum, Appointment appointment) {
    		this.bookId = bookId;
    		this.state = stateEnum.getState();
    		this.stateInfo = stateEnum.getStateInfo();
    		this.appointment = appointment;
    	}
    	
    	// 省略getter和setter方法,toString方法
    
    }
    
    

    接着,在exception包下新建三个文件
    NoNumberException.java
    RepeatAppointException.java
    AppointException.java
    预约业务异常类(都需要继承RuntimeException),分别是无库存异常、重复预约异常、预约未知错误异常,用于业务层非成功情况下的返回(即成功返回结果,失败抛出异常)。

    NoNumberException.java

    package com.soecode.lyf.exception;
    
    /**
     * 库存不足异常
     */
    public class NoNumberException extends RuntimeException {
    
    	public NoNumberException(String message) {
    		super(message);
    	}
    
    	public NoNumberException(String message, Throwable cause) {
    		super(message, cause);
    	}
    
    }
    
    

    RepeatAppointException.java

    package com.soecode.lyf.exception;
    
    /**
     * 重复预约异常
     */
    public class RepeatAppointException extends RuntimeException {
    
    	public RepeatAppointException(String message) {
    		super(message);
    	}
    
    	public RepeatAppointException(String message, Throwable cause) {
    		super(message, cause);
    	}
    
    }
    
    

    AppointException.java

    package com.soecode.lyf.exception;
    
    /**
     * 预约业务异常
     */
    public class AppointException extends RuntimeException {
    
    	public AppointException(String message) {
    		super(message);
    	}
    
    	public AppointException(String message, Throwable cause) {
    		super(message, cause);
    	}
    
    }
    
    

    咱们终于可以编写业务代码了,在service包下新建BookService.java图书业务接口。

    BookService.java

    package com.soecode.lyf.service;
    
    import java.util.List;
    
    import com.soecode.lyf.dto.AppointExecution;
    import com.soecode.lyf.entity.Book;
    
    /**
     * 业务接口:站在"使用者"角度设计接口 三个方面:方法定义粒度,参数,返回类型(return 类型/异常)
     */
    public interface BookService {
    
    	/**
    	 * 查询一本图书
    	 * 
    	 * @param bookId
    	 * @return
    	 */
    	Book getById(long bookId);
    
    	/**
    	 * 查询所有图书
    	 * 
    	 * @return
    	 */
    	List<Book> getList();
    
    	/**
    	 * 预约图书
    	 * 
    	 * @param bookId
    	 * @param studentId
    	 * @return
    	 */
    	AppointExecution appoint(long bookId, long studentId);
    
    }
    

    service.impl包下新建BookServiceImpl.java使用BookService接口,并实现里面的方法。

    BookServiceImpl

    package com.soecode.lyf.service.impl;
    
    import java.util.List;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.soecode.lyf.dao.AppointmentDao;
    import com.soecode.lyf.dao.BookDao;
    import com.soecode.lyf.dto.AppointExecution;
    import com.soecode.lyf.entity.Appointment;
    import com.soecode.lyf.entity.Book;
    import com.soecode.lyf.enums.AppointStateEnum;
    import com.soecode.lyf.exception.AppointException;
    import com.soecode.lyf.exception.NoNumberException;
    import com.soecode.lyf.exception.RepeatAppointException;
    import com.soecode.lyf.service.BookService;
    
    @Service
    public class BookServiceImpl implements BookService {
    
    	private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    	// 注入Service依赖
    	@Autowired
    	private BookDao bookDao;
    
    	@Autowired
    	private AppointmentDao appointmentDao;
    
    
    	@Override
    	public Book getById(long bookId) {
    		return bookDao.queryById(bookId);
    	}
    
    	@Override
    	public List<Book> getList() {
    		return bookDao.queryAll(0, 1000);
    	}
    
    	@Override
    	@Transactional
    	/**
    	 * 使用注解控制事务方法的优点: 1.开发团队达成一致约定,明确标注事务方法的编程风格
    	 * 2.保证事务方法的执行时间尽可能短,不要穿插其他网络操作,RPC/HTTP请求或者剥离到事务方法外部
    	 * 3.不是所有的方法都需要事务,如只有一条修改操作,只读操作不需要事务控制
    	 */
    	public AppointExecution appoint(long bookId, long studentId) {
    		try {
    			// 减库存
    			int update = bookDao.reduceNumber(bookId);
    			if (update <= 0) {// 库存不足
    				//return new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);//错误写法				
    				throw new NoNumberException("no number");
    			} else {
    				// 执行预约操作
    				int insert = appointmentDao.insertAppointment(bookId, studentId);
    				if (insert <= 0) {// 重复预约
    					//return new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);//错误写法
    					throw new RepeatAppointException("repeat appoint");
    				} else {// 预约成功
    					Appointment appointment = appointmentDao.queryByKeyWithBook(bookId, studentId);
    					return new AppointExecution(bookId, AppointStateEnum.SUCCESS, appointment);
    				}
    			}
    		// 要先于catch Exception异常前先catch住再抛出,不然自定义的异常也会被转换为AppointException,导致控制层无法具体识别是哪个异常
    		} catch (NoNumberException e1) {
    			throw e1;
    		} catch (RepeatAppointException e2) {
    			throw e2;
    		} catch (Exception e) {
    			logger.error(e.getMessage(), e);
    			// 所有编译期异常转换为运行期异常
    			//return new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);//错误写法
    			throw new AppointException("appoint inner error:" + e.getMessage());
    		}
    	}
    
    }
    
    

    下面我们来测试一下我们的业务代码吧~因为查询图书的业务不复杂,所以这里只演示我们最重要的预约图书业务!!

    BookServiceImplTest.java

    package com.soecode.lyf.service.impl;
    
    import static org.junit.Assert.fail;
    
    import org.junit.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.soecode.lyf.BaseTest;
    import com.soecode.lyf.dto.AppointExecution;
    import com.soecode.lyf.service.BookService;
    
    public class BookServiceImplTest extends BaseTest {
    
    	@Autowired
    	private BookService bookService;
    
    	@Test
    	public void testAppoint() throws Exception {
    		long bookId = 1001;
    		long studentId = 12345678910L;
    		AppointExecution execution = bookService.appoint(bookId, studentId);
    		System.out.println(execution);
    	}
    
    }
    
    

    BookServiceImplTest测试结果

    testAppointtestAppoint

    首次执行是“预约成功”,如果再次执行的话,应该会出现“重复预约”,哈哈,我们所有的后台代码都通过单元测试啦~~是不是很开心~


    咱们还需要在dto包里新建一个封装json返回结果的类Result.java,设计成泛型。

    Result.java

    package com.soecode.lyf.dto;
    
    /**
     * 封装json对象,所有返回结果都使用它
     */
    public class Result<T> {
    
    	private boolean success;// 是否成功标志
    
    	private T data;// 成功时返回的数据
    
    	private String error;// 错误信息
    
    	public Result() {
    	}
    
    	// 成功时的构造器
    	public Result(boolean success, T data) {
    		this.success = success;
    		this.data = data;
    	}
    
    	// 错误时的构造器
    	public Result(boolean success, String error) {
    		this.success = success;
    		this.error = error;
    	}
    
    	// 省略getter和setter方法
    }
    
    

    最后,我们写web层,也就是controller,我们在web包下新建BookController.java文件。

    BookController.java

    package com.soecode.lyf.web;
    
    import java.util.List;
    
    import org.apache.ibatis.annotations.Param;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.soecode.lyf.dto.AppointExecution;
    import com.soecode.lyf.dto.Result;
    import com.soecode.lyf.entity.Book;
    import com.soecode.lyf.enums.AppointStateEnum;
    import com.soecode.lyf.exception.NoNumberException;
    import com.soecode.lyf.exception.RepeatAppointException;
    import com.soecode.lyf.service.BookService;
    
    @Controller
    @RequestMapping("/book") // url:/模块/资源/{id}/细分 /seckill/list
    public class BookController {
    
    	private Logger logger = LoggerFactory.getLogger(this.getClass());
    
    	@Autowired
    	private BookService bookService;
    
    	@RequestMapping(value = "/list", method = RequestMethod.GET)
    	private String list(Model model) {
    		List<Book> list = bookService.getList();
    		model.addAttribute("list", list);
    		// list.jsp + model = ModelAndView
    		return "list";// WEB-INF/jsp/"list".jsp
    	}
    
    	@RequestMapping(value = "/{bookId}/detail", method = RequestMethod.GET)
    	private String detail(@PathVariable("bookId") Long bookId, Model model) {
    		if (bookId == null) {
    			return "redirect:/book/list";
    		}
    		Book book = bookService.getById(bookId);
    		if (book == null) {
    			return "forward:/book/list";
    		}
    		model.addAttribute("book", book);
    		return "detail";
    	}
    
    	//ajax json
    	@RequestMapping(value = "/{bookId}/appoint", method = RequestMethod.POST, produces = {
    			"application/json; charset=utf-8" })
    	@ResponseBody
    	private Result<AppointExecution> appoint(@PathVariable("bookId") Long bookId, @RequestParam("studentId") Long studentId) {
    		if (studentId == null || studentId.equals("")) {
    			return new Result<>(false, "学号不能为空");
    		}
    		//AppointExecution execution = bookService.appoint(bookId, studentId);//错误写法,不能统一返回,要处理异常(失败)情况
    		AppointExecution execution = null;
    		try {
    			execution = bookService.appoint(bookId, studentId);
    		} catch (NoNumberException e1) {
    			execution = new AppointExecution(bookId, AppointStateEnum.NO_NUMBER);
    		} catch (RepeatAppointException e2) {
    			execution = new AppointExecution(bookId, AppointStateEnum.REPEAT_APPOINT);
    		} catch (Exception e) {
    			execution = new AppointExecution(bookId, AppointStateEnum.INNER_ERROR);
    		}
    		return new Result<AppointExecution>(true, execution);
    	}
    
    }
    
    

    因为我比较懒,所以我们就不测试controller了,好讨厌写前端,呜呜呜~

    到此,我们的SSM框架整合配置,与应用实例部分已经结束了,我把所有源码和jar包一起打包放在了我的GitHub上,需要的可以去下载,喜欢就给个star吧,这篇东西写了两个晚上也不容易啊。

    完整代码下载地址:https://github.com/liyifeng1994/ssm


    2017-02-28更新(感谢网友EchoXml发现):
    修改预约业务代码,失败时抛异常,成功时才返回结果,控制层根据捕获的异常返回相应信息给客户端,而不是业务层直接返回错误结果。上面的代码已经作了修改,而且错误示范也注释保留着,之前误人子弟了,还好有位网友前几天提出质疑,我也及时做了修改。

    2017-03-30更新(感谢网友ergeerge1建议):
    修改BookController几处错误
    1.detail方法不是返回json的,故不用加@ResponseBody注解
    2.appoint方法应该加上@ResponseBody注解
    3.另外studentId参数注解应该是@RequestParam
    4.至于controller测试,测试appoint方法可不必写jsp,用curl就行,比如
    curl -H “Accept: application/json; charset=utf-8” -d “studentId=1234567890” localhost:8080/book/1003/appoint

    展开全文
  • 初识Spring Boot框架

    万次阅读 多人点赞 2016-12-19 20:42:04
    前面的铺垫文章已经连着写了六篇了,主要是介绍了Spring和SpringMVC框架,小伙伴们在学习的过程中大概也发现了这两个框架需要我们手动配置的地方非常多,不过做JavaEE开发的小伙伴们肯定也听说过“约定大于配置”...

    关注公众号【江南一点雨】,专注于 Spring Boot+微服务以及前后端分离等全栈技术,定期视频教程分享,关注后回复 Java ,领取松哥为你精心准备的 Java 干货!

    前面的铺垫文章已经连着写了六篇了,主要是介绍了Spring和SpringMVC框架,小伙伴们在学习的过程中大概也发现了这两个框架需要我们手动配置的地方非常多,不过做JavaEE开发的小伙伴们肯定也听说过“约定大于配置”这样一句话,就是说系统,类库,框架应该假定合理的默认值,而非要求提供不必要的配置,可是使用Spring或者SpringMVC的话依然有许多这样的东西需要我们进行配置,这样不仅徒增工作量而且在跨平台部署时容易出问题。OK,由于这些已经存在的问题,Spring Boot应运而生,使用Spring Boot可以让我们快速创建一个基于Spring的项目,而让这个Spring项目跑起来我们只需要很少的配置就可以了。Spring Boot主要有如下核心功能:

    1.独立运行的Spring项目

    Spring Boot可以以jar包的形式来运行,运行一个Spring Boot项目我们只需要通过java -jar xx.jar类运行。非常方便。

    2.内嵌Servlet容器

    Spring Boot可以内嵌Tomcat,这样我们无需以war包的形式部署项目。

    3.提供starter简化Maven配置

    使用Spring或者SpringMVC我们需要添加大量的依赖,而这些依赖很多都是固定的,这里Spring Boot 通过starter能够帮助我们简化Maven配置。

    4.自动配置Spring
    5.准生产的应用监控
    6.无代码生成和xml配置

    OK,关于SpringBoot更详细的优缺点小伙伴们也可以自行搜索,我这里不再罗列,我们还是来看看代码。
    #项目创建
    初次接触,我们先来看看如何创建一个Spring Boot项目,这里以IntelliJ IDEA为例,其他的IDE工具小伙伴们自行搜索创建方式:

    首先创建一个项目,创建时选择Spring Initializr,然后Next,如下图:

    这里写图片描述

    填写项目信息,如下图:

    这里写图片描述

    填写项目使用到的技术,上面的Spring Boot版本建议选择最新的稳定版,下面勾选上Web就可以了,如下图:

    这里写图片描述

    最后一步,填写工程名字点击finish:

    这里写图片描述
    OK,第一次创建时系统会去下载需要的依赖等,耗时稍长,以后每次都会很快创建好。
    OK,项目创建成功之后接下来我们来看看这个东西要怎么样去运行。首先我们看到在项目创建成功之后,在项目的根目录下会有一个artifactId+Application命名规则的入口类,如下图:
    这里写图片描述,就是这个Test19SpringBoot2Application类,这是我们整个项目的入口类,这个类有一个@SpringBootApplication注解,这是整个Spring Boot的核心注解,它的目的就是开启Spring Boot的自动配置。OK,那么我在这个类上再添加一个@RestController注解,使之变为一个Controller,然后里边提供一个地址转换方法,如下:

    @RestController
    @SpringBootApplication
    public class Test19SpringBoot2Application {
    
    	public static void main(String[] args) {
    		SpringApplication.run(Test19SpringBoot2Application.class, args);
    	}
    
    	@RequestMapping(value = "/",produces = "text/plain;charset=UTF-8")
    	String index(){
    		return "Hello Spring Boot!";
    	}
    }
    

    然后点击项目启动按钮运行,在IntelliJ中就是这个按钮:
    这里写图片描述
    启动成功之后我们就可以直接在浏览器中访问了,如下:
    这里写图片描述
    OK,至此,我们一个简单的Spring Boot工程已经创建出来了,并且成功的从浏览器中访问到了,但是为什么它最终会跑起来呢?想必小伙伴们还有许多疑问,我们来分析下。
    #入口类和@SpringBootApplication注解

    上文说过,我们新建一个Project系统都会帮我们创建一个名为artifactId+Application的入口类,这个类中有一个main方法,这个main方法就是一个标准的Java应用程序的入口方法。而这里的@SpringBootApplication则是一个组合注解,我们可以看看它的源码:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan(
        excludeFilters = {@Filter(
        type = FilterType.CUSTOM,
        classes = {TypeExcludeFilter.class}
    )}
    )
    public @interface SpringBootApplication {
    
    }
    

    我们可以看到它组合了@SpringBootConfiguration、@EnableAutoConfiguration以及@ComponentScan,我们在开发的过程中如果不使用@SpringBootApplication,则可以组合使用这三个注解。这三个注解中,@SpringBootConfiguration实际上就是我们前面几篇博客提到的@Configuration注解,表明这个类是一个配置类,@EnableAutoConfiguration则表示让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置,最后一个@ComponentScan的作用我也不赘述了,唯一要注意的是如果我们使用了@SpringBootApplication注解的话,系统会去入口类的同级包以及下级包中去扫描实体类,因此我们建议入口类的位置在groupId+arctifactID组合的包名下。

    #关闭特定的自动配置
    在上面一小节中我们看到@ComponentScan注解是有一个过滤器的,如果我们只想要@SpringBootApplication去扫描特定的类而不是全部类,那么就可以关闭自动配置,如下:

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    

    #定制Banner
    ##修改Banner
    我们在启动Spring Boot项目的时候,在控制台会默认输出一个启动图案,如下:
    这里写图片描述
    当然,这个图案如果你需要的话是可以自己修改的,修改方式很简单:

    1.在src/main/resources下新建一个banner.txt文档
    2.通过http://patorjk.com/software/taag网站生成需要的字符,将字符拷贝到步骤1所创建的txt文档中,比如我这里为Hello Sang!生成字符,如下:

    这里写图片描述
    点击左下角的选择和拷贝按钮,将这个字符拷贝到txt文档中,然后再启动项目,这个时候控制台输出的文本就会自动改变,如下:
    这里写图片描述
    ##关闭Banner
    可以修改当然也可以关闭,关闭Banner需要我们稍微修改一下main方法中的代码,如下:

    public static void main(String[] args) {
    		SpringApplicationBuilder builder = new SpringApplicationBuilder(Test19SpringBoot2Application.class);
    		//修改Banner的模式为OFF
    		builder.bannerMode(Banner.Mode.OFF).run(args);
    	}
    

    OK,如此修改之后当我们再次启动Project的时候就看不到Banner了。

    #Spring Boot的配置文件
    Spring Boot使用一个全局的配置文件application.properties或者application.yml,配置文件放在src/main/resources目录下。properties是我们常见的一种配置文件,Spring Boot不仅支持properties这种类型的配置文件,也支持yaml语言的配置文件,我这里以properties类型的配置文件为例来看几个案例。

    1.修改Tomcat默认端口和默认访问路径

    Tomcat默认端口是8080,我将之改为8081,默认访问路径是http://localhost:8080,我将之改为http://localhost:8081/helloboot,我们来看看这两个需求要怎么样通过简单的配置来实现。
    很简单,在application.properties文件中添加如下代码:

    server.context-path=/helloboot
    server.port=8081
    

    然后再启动Project,在浏览器中就得这样来访问了:
    这里写图片描述

    常规属性配置

    在前面的博客( Spring常用配置 )中我们介绍了如何在使用Spring容器框架下注入properties文件里的值。如果我们使用了Spring Boot,这项工作将会变得更加简单,我们只需要在application.properties中定义属性,然后在代码中直接使用@Value注入即可。
    如下:

    book.author=罗贯中
    book.name=三国演义
    book.pinyin=sanguoyanyi
    

    我这里专门设置了中文,因为中文不做特殊处理会乱码,处理方式为继续在application.properties中添加如下代码:

    server.tomcat.uri-encoding=UTF-8
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.enabled=true
    spring.http.encoding.force=true
    spring.messages.encoding=UTF-8
    

    然后 在IntelliJ IDEA中依次点击File -> Settings -> Editor -> File Encodings
    将Properties Files (*.properties)下的Default encoding for properties files设置为UTF-8,将Transparent native-to-ascii conversion前的勾选上。(参考【Springboot 之 解决IDEA读取properties配置文件的中文乱码问题】【Springboot 之 解决IDEA读取properties配置文件的中文乱码问题】)。
    然后在变量中通过@Value直接注入就行了,如下:

    	@Value(value = "${book.author}")
    	private String bookAuthor;
    	@Value("${book.name}")
    	private String bookName;
    	@Value("${book.pinyin}")
    	private String bookPinYin;
    

    修改index方法,使之返回这些值:

    @RequestMapping(value = "/",produces = "text/plain;charset=UTF-8")
    	String index(){
    		return "Hello Spring Boot! The BookName is "+bookName+";and Book Author is "+bookAuthor+";and Book PinYin is "+bookPinYin;
    	}
    

    然后在浏览器中访问,结果如下:
    这里写图片描述

    很简单吧。

    类型安全的配置

    刚刚说的这种方式我们在实际项目中使用的时候工作量略大,因为每个项目要注入的变量的值太多了,这种时候我们可以使用基于类型安全的配置方式,就是将properties属性和一个Bean关联在一起,这样使用起来会更加方便。我么来看看这种方式怎么实现。

    1.在src/main/resources文件夹下创建文件book.properties

    文件内容如下:

    book.name=红楼梦
    book.author=曹雪芹
    book.price=28
    

    2.创建Book Bean,并注入properties文件中的值

    代码如下:

    @Component
    @ConfigurationProperties(prefix = "book",locations = "classpath:book.properties")
    public class BookBean {
        private String name;
        private String author;
        private String price;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getAuthor() {
            return author;
        }
    
        public void setAuthor(String author) {
            this.author = author;
        }
    
        public String getPrice() {
            return price;
        }
    
        public void setPrice(String price) {
            this.price = price;
        }
    }
    

    prefix是指前缀,location指定要注入文件的位置。

    3.添加路径映射

    在Controller中添加如下代码注入Bean:

    @Autowired
    	private BookBean bookBean;
    

    添加路径映射:

    @RequestMapping("/book")
    	public String book() {
    		return "Hello Spring Boot! The BookName is "+bookBean.getName()+";and Book Author is "+bookBean.getAuthor()+";and Book price is "+bookBean.getPrice();
    	}
    

    运行效果如下:

    #日志配置
    默认情况下Spring Boot使用Logback作为日志框架,也就是我们前面几篇博客中用到的打印日志方式,当然如果有需要我们可以手动配置日志级别以及日志输出位置,相比于我们在Spring容器中写的日志输出代码,这里的配置简直就是小儿科了,只需要在application.properties中添加如下代码:

    logging.file=/home/sang/workspace/log.log
    logging.level.org.springframework.web=debug
    

    上面表示配置日志输出位置,下面配置日志级别。

    #Profile配置问题
    Spring常用配置 这篇文章中,我们已经介绍了Profile的作用,已经如何在Spring框架下使用Profile,但是当时小伙伴们看到了还是稍微有点麻烦,在Spring Boot 中系统提供了更为简洁的方式。全局Profile配置我们使用application-{profile}.properties来定义,然后在application.properties中通过spring.profiles.active来指定使用哪个Profile。OK,那么接下来我们来看一个简单的案例。

    1.在src/main/resources文件夹下定义不同环境下的Profile配置文件,文件名分别为application-prod.properties和application-dev.properties,这两个前者表示生产环境下的配置,后者表示开发环境下的配置,如下:

    这里写图片描述

    application-prod.properties:

    server.port=8081
    

    application-dev.properties:

    server.port=8080
    

    然后在application.properties中进行简单配置,如下:

    spring.profiles.active=dev
    

    这个表示使用开发环境下的配置。然后运行项目,我们得通过8080端口才可以访问:
    这里写图片描述
    如果想换为生产环境,只需要把spring.profiles.active=dev改为spring.profiles.active=prod即可,当然访问端口这是也变为8081了,如下:
    这里写图片描述

    本案例下载地址:
    本项目GitHub地址

    以上。

    参考资料:
    《JavaEE开发的颠覆者 Spring Boot实战》第五章、第六章

    展开全文
  • Spring入门第一讲——Spring框架的快速入门

    万次阅读 多人点赞 2017-04-08 00:27:34
    Spring是一个开源框架Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架Spring是一个开源框架Spring是于2003年...

    Spring的概述

    什么是Spring?

    我们可以从度娘上看到这样有关Spring的介绍:
    在这里插入图片描述
    说得更加详细一点,Spring是一个开源框架,Spring是于2003年兴起的一个轻量级的Java开发框架,由Rod Johnson在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为J2EE应用程序开发提供集成的框架。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring的核心是控制反转(IoC)和面向切面(AOP)。简单来说,Spring是一个分层的JavaSE/EEfull-stack(一站式)轻量级开源框架。
    为什么说Spring是一个一站式的轻量级开源框架呢?EE开发可分成三层架构,针对JavaEE的三层结构,每一层Spring都提供了不同的解决技术。
    在这里插入图片描述
    从对Spring的简要介绍中,我们知道了Spring的核心有两部分:

    • IoC:即控制反转。举例来说,在之前的操作中,比方说有一个类,我们想要调用类里面的方法(不是静态方法),就要创建该类的对象,使用对象调用方法来实现。但对于Spring来说,Spring创建对象的过程,不是在代码里面实现的,而是交给Spring来进行配置实现的;
    • AOP:即面向切面编程。之前,讲Struts2框架的拦截器时,我们就已稍微讲了一下,在Spring后续的学习过程中,我们会着重来讲它,但是本文并不会过多阐述它。

    为什么学习Spring?

    在度娘上查看有关Spring的介绍时,我们可以找到如下一系列Spring的优点,这就是我们要学习Spring框架的原因。
    在这里插入图片描述

    Spring的快速入门

    Spring的版本

    需要说明的是,本系列入门Spring的教程使用的版本是spring-framework-4.2.4.RELEASE。

    IoC概述

    什么是IoC?

    IoC即Inversion of Control,反应过来就是控制反转。啥是控制反转啊?控制反转指的就是将对象的创建权反转给(交给)了Spring,其作用是实现了程序的解耦合。也可这样解释:获取对象的方式变了,对象创建的控制权不是"使用者",而是"框架"或者"容器"。用更通俗的话来说,IoC就是指对象的创建,并不是在代码中用new操作new出来的,而是通过Spring进行配置创建的。

    Spring的IoC的底层实现原理

    这里先给出结论:Spring的IoC的底层实现原理是工厂设计模式+反射+XML配置文件。 就拿持久层(也即dao层,data access object,数据访问对象)的开发来说,官方推荐做法是先创建一个接口,然后再创建接口对应的实现类。所以,这里,我会以dao层的开发为例来证明Spring的IoC的底层实现原理就是工厂设计模式+反射+XML配置文件。
    首先,创建一个Userdao接口。

    public interface UserDao {
    	public void add();
    }
    

    然后,再创建Userdao接口的一个实现类(UserDaoImpl.java)。

    public class UserDaoImpl implements UserDao {
        public void add() {
    	    balabala......
        }
    }
    

    接着,我们在service层中调用dao层,核心代码如下:

    // 接口 实例变量 = new 实现类
    UserDao dao = new UserDaoImpl();
    dao.add();
    

    这时我们便可发现一个缺点:service层和dao层耦合度太高了,即接口和实现类有耦合(它俩之间的联系过于紧密),一旦切换底层实现类,那么就需要修改源代码,这真的不是一个好的程序设计,好的程序设计应当满足OCP原则(也即开闭原则),即在尽量不修改程序源代码的基础上对程序进行扩展。说到这里,我就不得不稍微讲一下面向对象设计的七大原则了,它不必强记,重在理解。
    在这里插入图片描述
    出现的这个问题该如何解决呢?解决方法是使用工厂设计模式进行解耦合操作。所以,我们需要创建一个工厂类,在工厂类中提供一个方法,返回实现类的对象。

    public class BeanFactory {
        // 提供返回实现类对象的方法
        public static UserDao getUserDao() {
            return new UserDaoImpl();
        }
    }
    

    这样,在service层中调用dao层的核心代码就变为了下面的样子。

    UserDao dao = BeanFactory.getUserDao();
    dao.add();
    

    如若这样做,会发现又产生了一个缺点:现在接口和实现类之间是没有耦合了,但是service层和工厂类耦合了。如果真正想实现程序之间的解耦合,那么就需要使用到工厂设计模式+反射+XML配置文件了。所以,我们这里提供一个XML配置文件,并且该配置文件中有如下配置信息。

    <bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl" />
    

    然后再来创建一个工厂类,在工厂类中提供一个返回实现类对象的方法,但并不是直接new实现类,而是使用SAX解析配置文件,根据标签bean中的id属性值得到对应的class属性值,使用反射创建实现类对象。

    public class BeanFactory {
        public static Object getBean(String id) {
            // 1.使用SAX解析得到配置文件内容
            // 直接根据id值userDao得到class属性值
            String classvalue = "class属性值";
            // 2.使用反射得到对象
            Class clazz = Class.forName(classvalue);
            UserDaoImpl userDaoImpl = (UserDaoImpl)lazz.newInstance();
            return userDaoImpl;
        }
    }
    

    以上就是Spring的IoC的底层实现原理。

    Spring的IoC入门

    下载Spring的开发包

    Spring的官网是https://spring.io/,Spring的开发包的下载地址是https://repo.spring.io/libs-release-local/org/springframework/spring/,上面说过,我下载的是spring-framework-4.2.4.RELEASE这个版本的Spring。解压缩之后,可发现Spring开发包的目录结构如下。
    在这里插入图片描述

    创建web项目,引入Spring的开发包

    首先创建一个动态web项目,例如spring_demo01,然后导入Spring框架相关依赖jar包,要导入哪些jar包呢?这是一个问题。
    在这里插入图片描述
    由于我们只是初次入门Spring,所以也只是使用到了Spring的基本功能,从上图红框框中的部分可知,我们需要使用到下面的这4个jar包。
    在这里插入图片描述
    除此之外,还要导入Spring支持的日志jar包,也就是下面两个jar包。
    在这里插入图片描述
    关于以上两个jar包,我稍微做一下解释,com.springsource.org.apache.commons.logging-1.1.1.jar它里面都是一些接口,有接口,那当然要有实现类了,恰好,com.springsource.org.apache.log4j-1.2.15.jar里面就是那些接口的实现类。使用Log4j,我们可以查看到当前运行程序中对象创建的过程,也可以看到更详细的信息,Log4j适合使用在程序调试中。
    导入完日志相关的jar包之后,我们还要导入日志记录文件,即在src目录下引入Log4j的配置文件(log4j.properties),该文件内容如下:

    ### direct log messages to stdout ###
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.err
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
    
    ### direct messages to file mylog.log ###
    log4j.appender.file=org.apache.log4j.FileAppender
    log4j.appender.file.File=c\:mylog.log
    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
    
    ### set log levels - for more verbose logging change 'info' to 'debug' ###
    # error warn info debug trace
    log4j.rootLogger= info, stdout
    

    对以上日志记录文件,我会粗略讲解一下。
    在这里插入图片描述
    从上图可知,log4j.rootLogger就是用于设置日志的输出级别,那么日志的输出级别有几种呢?有如下5种。
    在这里插入图片描述

    创建接口和实现类

    首先,在src目录下创建一个com.meimeixia.spring.demo01包,并在该包下创建一个名为UserDao的接口。

    package com.meimeixia.spring.demo01;
    
    /**
     * 用户管理的dao层的接口
     * @author liayun
     *
     */
    public interface UserDao {
    
    	public void save();
    	
    }
    

    然后,在com.meimeixia.spring.demo01包下创建UserDao接口的一个实现类——UserDaoImpl.java。

    package com.meimeixia.spring.demo01;
    
    /**
     * 用户管理的dao层接口的实现类
     * @author liayun
     *
     */
    public class UserDaoImpl implements UserDao {
    
    	@Override
    	public void save() {
    		System.out.println("UserDaoImpl中的save方法执行了......");
    	}
    	
    }
    

    将实现类交给Spring管理

    首先,我们需要创建Spring的配置文件,Spring配置文件的名称和位置没有固定要求,一般建议把该文件放到src目录下面,名称可随便写,官方建议写成applicationContext.xml。然后我们还需要在配置文件中引入约束,Spring学习阶段的约束是schema约束。那么问题来了,这个约束又该怎么写呢?可参考docs\spring-framework-reference\html目录下的xsd-configuration.html文件,在其内容最后面找到如下内容。
    在这里插入图片描述
    将其复制黏贴到配置文件applicationContext.xml中,这样applicationContext.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">
            
    </beans>
    

    最后,咱还要将实现类交给Spring来管理,即在配置文件中配置对象的创建。

    <?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">
            
    	<!-- Spring入门的配置 -->
    	<bean id="userDao" class="com.meimeixia.spring.demo01.UserDaoImpl"></bean>
    	
    </beans>
    

    编写测试类

    我们要在Spring中写代码来实现获取applicationContext.xml文件中配置的对象,这段代码不要求大家重点掌握,只是用在测试中而已。这段代码主要用来解析Spring配置文件得到对象,但这个过程不需要我们写代码来实现了,Spring已经封装了一个对象来帮我们进行了这些操作,这个对象叫ApplicationContext,它就能实现这个功能。于是,我们需要在com.meimeixia.spring.demo01包下创建一个SpringDemo01的单元测试类。

    package com.meimeixia.spring.demo01;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * Spring的入门
     * @author liayun
     *
     */
    public class SpringDemo01 {
    	
    	/*
    	 * 传统方式的调用
    	 */
    	@Test
    	public void demo01() {
    		UserDao userDao = new UserDaoImpl();
    		userDao.save();
    	}
    	
    	/*
    	 * Spring的方式的调用
    	 */
    	@Test
    	public void demo02() {
    		//先要创建Spring的工厂
    		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");// classpath就是类路径,src目录下的文件最终要编译到类路径下
    		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    		userDao.save();
    	}
    
    }
    

    然后,运行以上demo02单元测试方法,Eclipse控制台就会打印如下内容。
    在这里插入图片描述

    Spring IoC和DI的区别

    IoC上面我已经讲过了,它指的就是将对象的创建权反转给(交给)了Spring。那DI又是什么鬼呢?DI,即Dependency Injection,翻译过来就是依赖注入,它指的就是Spring在管理某个类的时候会将该类依赖的属性注入(设置)进来,也就是说在创建对象的过程中,向类里面的属性中设置值。注意:依赖注入不能单独存在,须在控制反转基础之上完成,用更通俗点的话来说,就是注入类里面的属性值,不能直接注入,须创建类的对象再完成注入。
    你还记得在面向对象设计的时候,类和类之间有几种关系吗?有3种,它们分别是:

    • 依赖,由于下图中B类的方法用到了A类,所以此时就可以说B类依赖于A类;
      在这里插入图片描述
    • 继承(is a),这种关系我们应该是见的要吐了;
      在这里插入图片描述
    • 聚合(has a),它有松散和紧密之分。例如,球队得有一个守门员,即使这个球队没有了这个守门员,它也还是一个球队,所以它是松散的;人得有一个脑袋,此时它就是紧密的。

    说了这么多,咱就通过一个案例来看看DI在程序的体现。现在,我们想要给UserDaoImpl这个实现类里面的某一个属性(例如String类型的name)设置值,该咋怎呢?首先,将UserDaoImpl这个实现类修改成下面这个样子。

    package com.meimeixia.spring.demo01;
    
    /**
     * 用户管理的dao层接口的实现类
     * @author liayun
     *
     */
    public class UserDaoImpl implements UserDao {
    	
    	private String name;
    	
    	//提供set方法
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	@Override
    	public void save() {
    		System.out.println("UserDaoImpl中的save方法执行了......" + name);
    	}
    	
    }
    

    如果使用传统的方式来为UserDaoImpl实现类的name属性设置值,那么SpringDemo01单元测试类中的demo01方法就应该写成下面这个样子。

    package com.meimeixia.spring.demo01;
    
    import org.junit.Test;
    
    /**
     * Spring的入门
     * @author liayun
     *
     */
    public class SpringDemo01 {
    	
    	/*
    	 * 传统方式的调用
    	 */
    	@Test
    	public void demo01() {
    		/*
    		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
    		 * 
    		 * 1. 不能面向接口编程了
    		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
    		 */
    		UserDaoImpl userDao = new UserDaoImpl();
    		userDao.setName("李二");
    		userDao.save();
    	}
    
    }
    

    这样写,就有两个缺点,一是不能面向接口编程了,二是咱还得手动调用对象的set方法,这必然就涉及到要改变程序的源代码了,这是我们不能接受的!
    如果使用了依赖注入,即在applicationContext.xml文件中为配置好的UserDaoImpl实现类的name属性注入一个值,那么情况就完全不同了,以上两个缺点也就不复存在了。

    <?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">
            
    	<!-- Spring入门的配置 -->
    	<bean id="userDao" class="com.meimeixia.spring.demo01.UserDaoImpl">
    		<!-- DI:依赖注入 -->
    		<property name="name" value="李二" />
    	</bean>
    	
    </beans>
    

    此时,SpringDemo01单元测试类中的demo02方法依然不变,如下。

    package com.meimeixia.spring.demo01;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * Spring的入门
     * @author liayun
     *
     */
    public class SpringDemo01 {
    	
    	/*
    	 * 传统方式的调用
    	 */
    	@Test
    	public void demo01() {		
    		/*
    		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
    		 * 
    		 * 1. 不能面向接口编程了
    		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
    		 */
    		UserDaoImpl userDao = new UserDaoImpl();
    		userDao.setName("李二");
    		userDao.save();
    	}
    	
    	/*
    	 * Spring的方式的调用
    	 */
    	@Test
    	public void demo02() {
    		//先要创建Spring的工厂
    		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    		userDao.save();
    	}
    
    }
    

    运行以上demo02单元测试方法,Eclipse控制台就会打印出如下内容。
    在这里插入图片描述

    Spring的工厂类

    Spring工厂类的结构图

    在这里插入图片描述
    从上图可以看出ApplicationContext(接口)继承自BeanFactory(接口)。

    BeanFactory:老版本的工厂类

    BeanFactory是老版本的工厂类,稍微了解一下就好,这个类在实际开发中我们并不需要用到。需要说明的一点是,它只有在调用getBean方法的时候,才会生成Spring所管理的类的实例。

    ApplicationContext:新版本的工厂类

    ApplicationContext是新版本的工厂类,它在加载配置文件的时候,就会将Spring所管理的类都实例化。ApplicationContext这个接口有两个实现类,如下图所示。
    在这里插入图片描述
    ClassPathXmlApplicationContext这个实现类前面用过了,下面咱就来用一下FileSystemXmlApplicationContext这个实现类。首先,拷贝一份applicationContext.xml文件到C盘下,其内容做一点点修改。
    在这里插入图片描述
    然后,在SpringDemo01单元测试类中编写如下一个demo03方法。

    package com.meimeixia.spring.demo01;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.context.support.FileSystemXmlApplicationContext;
    
    /**
     * Spring的入门
     * @author liayun
     *
     */
    public class SpringDemo01 {
    	
    	/*
    	 * 传统方式的调用
    	 */
    	@Test
    	public void demo01() {	
    		/*
    		 * 我想给这个类里面的某一个属性设置值,挺麻烦的!
    		 * 
    		 * 1. 不能面向接口编程了
    		 * 2. 你还得手动调用set方法,也得去改变程序的源代码
    		 */
    		UserDaoImpl userDao = new UserDaoImpl();
    		userDao.setName("李二");
    		userDao.save();
    	}
    	
    	/*
    	 * Spring的方式的调用
    	 */
    	@Test
    	public void demo02() {
    		//先要创建Spring的工厂
    		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    		userDao.save();
    	}
    	
    	/*
    	 * 加载磁盘上的配置文件
    	 */
    	@Test
    	public void demo03() {
    		//先要创建Spring的工厂
    		ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\applicationContext.xml");
    		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    		userDao.save();
    	}
    
    }
    

    最后,运行以上demo03单元测试方法,Eclipse控制台就会打印出如下内容。
    在这里插入图片描述

    展开全文
  • 微服务框架Spring Boot入门

    千人学习 2017-12-26 09:56:38
    Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
  • 史上最简单的 SpringCloud 教程 | 终章

    万次阅读 多人点赞 2017-04-12 23:14:39
    本文出自方志朋的博客 错过了这一篇,你可能再也学...Spring Boot做为下一代 web 框架Spring Cloud 作为最新最火的微服务的翘楚,你还有什么理由拒绝。赶快上船吧,老船长带你飞。终章不是最后一篇,它是一个...
  • SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)

    万次阅读 多人点赞 2015-03-19 11:44:55
    使用SSM(Spring、SpringMVC和Mybatis)已经有三个多月了,项目在技术上已经没有什么难点了,基于现有的技术就可以实现想要的功能,当然肯定有很多可以改进的地方。之前没有记录SSM整合的过程,这次刚刚好基于自己的...
  • 大数据批处理框架Spring Batch+spring boot+quartz

    万次阅读 多人点赞 2018-01-03 19:20:40
    大数据时代,数据的收集、处理、...Spring Batch是一个轻量级的综合性批处理框架,可以应用于企业级大数据量处理系统。Spring Batch可以提供大量的,可重复的数据处理功能,包括日志/跟踪(tracing),事务管理,任务处理
  • Java企业应用开发框架Spring框架简介

    千次阅读 2017-07-12 15:56:05
    Spring是一个开源的轻量级的Java企业应用开发框架,其初衷是为了替代当时非常笨重的Java EE(当时还称为J2EE)组件技术EJB(Enterprice Java Beans),让Java EE开发更加简单灵活。 Spring起源于Rod Jahnson 2002...
  • Spring Actionscript 是众多围绕依赖注入提供解决方案的Flex控制反转框架之一 AS3 下经典的IOC框架Spring ActionScript、Parsley、Flicc和Swiz,由于我对JAVA spring IOC机制较为熟悉,所以选择了 Spring ...
  • Delphi 开源框架 Spring4D

    千次阅读 2014-04-08 09:33:23
    https://bitbucket.org/sglienke/spring4d ...Spring4D is an open-source code library for Embarcadero Delphi 2010 and higher. It consists of a number of of different modules that cont
  • .NET 开源框架 Spring.NET

    千次阅读 2011-06-29 16:37:00
    Spring.NET 是一个应用程序框架,其目的是协助开发人员创建企业级的 .NET 应用程序。它提供了很多方面的功能,比如依赖注入、面向方面编程(AOP)、数据访问抽象及 ASP.NET 扩展等等。Spring.NET 以 Java 版的 ...
  • 在 WEB 环境下使用 Spring ①. 需要额外加入的 jar 包: spring-web-4.0.0.RELEASE.jar spring-webmvc-4.0.0.RELEASE.jar ②. Spring 的配置文件, 和非 WEB 环境没有什么不同 ③. 需要在 web....
  • RestTemplate类是spring-web模块中进行HTTP访问的REST客户端核心类。RestTemplate请求使用阻塞式IO,适合低并发的应用场景。 1. RestTemplate类提供了3个构造函数 RestTemplate() ...org.springframework.h...
  • spring的execute(String callString, CallableStatementCallback<T> action)和execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)的区别是什么,用法上有哪些特别之处呢?在官方文档可以...
  • 如今微服务架构讨论的如火如荼。但在企业架构里除了大量的OLTP交易外,...是时候和我们一起来了解下批处理的世界哪些优秀的框架和设计了,今天我将以Spring Batch为例,和大家一起探秘批处理的世界。 初识批处理典型
  • JAVA:轻量级框架Spring

    千次阅读 2017-06-13 22:10:27
    目前主流的javawe项目,常会用到SSM(Spring+Spring MVC+Mybatis)、SSH(Struts2+Spring+Hibernate)框架来搭建项目的主体框架,本篇介绍搭建SSM创建的maven项目的实施流程。一、SSM框架介绍SSM框架是指:Spring+...
  • 互联网技术飞快发展,微服务势必会成为一种趋势。大名鼎鼎的spring-boot在微服务构建方面表现出色,再者因为是spring家族的产品,很快就得以流行
  • 背景:最近在实战中顺手搭建了一个Spring Cloud+Nacos+Sentinel+全局异常捕获+Mybatis plus+Hutool+WXjava 做为后端微服务框架 Spring Cloud简介 相信这个大家都很熟悉就不做详细介绍:Spring Cloud是一系列框架的...
  • Web框架spring boot/Flask几个概念

    千次阅读 2017-09-03 16:33:16
    Web框架spring boot/Flask几个概念 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板...
  • 轻量级JavaEE开发框架Spring知识总结

    千次阅读 2015-10-23 11:29:31
    强大的Spring框架由Rod Johnson开发,2004年发布了Spring框架的第一版。Spring是一个从实际开发中抽取出来的框架,因此它完成了大量开发中的通用步骤,留给开发者的仅仅是与特定应用相关的部分,从而大大提高了企业...
  • 如今微服务架构讨论的如火如荼。但在企业架构里除了大量的OLTP交易外,还存在...针对OLTP,业界有大量的开源框架、优秀的架构设计给予支撑;但批处理领域的框架确凤毛麟角。是时候和我们一起来了解下批处理的世界
  • ![![![图片说明](https://img-ask.csdn.net/upload/201705/15/1494852653_516978.png)图片说明](https://img-ask.csdn.net/upload/201705/15/1494852642_685218.png)图片说明]...
  • [org.springframework.web.servlet.PageNotFound] - No mapping found for HTTP request with URI [/test/Images/image_01.jpg] in DispatcherServlet with name 'SpringMVC' SSM项目,找不到图片文件的URI,...
  • 体会:Java语言应用越来越广泛, 相对应的技术框架也在不断的优化与完善中,尤其是一些主流框架ssh与ssm效率很高;Spring是一个轻型容器,其核心是bean工厂,用以构造我们所需要的Model;在这基础上面提供了Aop...
  • 对于android开发的朋友来说,如果你有JavaEE的...扯了这么多,那么作为android开发的第三方框架spring-android能为我们android开发者带来什么呢? 如果你还不清楚spring-android是什么,可以先看一下这篇文章:第
  • 微服务Java框架Spring Boot最流行的 16 条实践解读,值得收藏! Spring Boot 是最流行的用于开发微服务的 Java 框架。这些内容是基于我的个人经验和一些熟知的 Spring Boot 专家的文章。 在本文中,我将重点介绍 ...
  • spring学习之spring框架介绍

    千次阅读 2016-10-21 01:18:21
    spring学习之spring框架介绍开始spring如果你是刚使用spring的开发人员,可以先使用spring-boot来创建一个spring应用。他基于spring框架。主张约定优于配置。被设计成为尽可能快的运行。 参考代码及文档 快速学习...
  • SpringCloud面试题

    万次阅读 多人点赞 2018-12-12 00:29:07
    一.SpringCloud面试题口述 1.SpringCloud和Dubbo ...Dubbo是阿里系的分布式服务治理框架 从技术维度上,其实SpringCloud远远的超过Dubbo,Dubbo本身只是实现了服务治理,而SpringCloud现在以及有21个子项目以...

空空如也

1 2 3 4 5 ... 20
收藏数 2,168,744
精华内容 867,497
关键字:

框架