ssm

**Spring-day01**
成恒 / chengheng@tedu.cn

## 框架

Spring, Spring MVC, Mybatis

## 什么是MVC

MVC:Model(模型), View(视图), Controller(控制器)

    UserController    <--->    UserModel(UserService, UserDao)

模拟用户注册:

    // Dao:Data Access Object,数据访问对象
    // 一般指持久层,多半是操作数据库的
    public class JdbcUserDao {
        
        public void insert() {
            // 将用户数据插入到数据表中
        }

        public boolean checkUsernameExists(String username) {
            // 检查用户名是否被注册
        }
    }

    // Service:业务逻辑,如何保障数据按照设计者制定的规则来访问
    public class UserService {
        private JdbcUserDao dao = new JdbcUserDao();

        // 注册
        public void reg() {
            if (dao.checkUsernameExists() == true) {
                // 注册失败,用户名被占用
            } else {
                dao.insert(); // 调用Dao完成插入数据
            }
        }
    }

如果`JdbcUserDao`需要被替换为以下`MybatisUserDao`:

    public class MybatisUserDao {

        public void insert() {
            // 将用户数据插入到数据表中
        }

        public boolean checkUsernameExists(String username) {
            // 检查用户名是否被注册
        }
    }

则,`UserService`就必须调整:

    public class UserService {
        private MybatisUserDao dao = new MybatisUserDao();
        // ... 其它可以不变
    }

但是,如果一个项目中,曾经在许多代码中都出现了`private JdbcUserDao dao = new JdbcUserDao();`,那么,在替换过程中,就需要执行多次的替换!

所以,可以先设计一个`Dao`的接口,例如:

    public interface IUserDao {
        void insert();
        boolean checkUsernameExists(String username);
    }

则`JdbcUserDao`和`MybatisUserDao`都应该也都可以实现这个接口,基于多态的使用原则,原来的

    private JdbcUserDao dao = new JdbcUserDao();

可以调整为:

    private IUserDao dao = new JdbcUserDao();

则后续需要更换`JdbcUserDao`时,就可以少替换一半的代码!

然后,各个`IUserDao`的实现类,我们希望不由他人(指程序员)随意创建,而是基于特定的方式获取:

    public class UserDaoFactory {
        public IUserDao getInstance() {
            return new MybatisUserDao();
        }
    }

则以上代码可以进一步优化为:

    private IUserDao dao = new UserDaoFactory().getInstance();

在整个项目中,需要用上`User`相关的持久层对象时,都使用以上语法即可,即使后续需要替换为一个新的实现类,也只需要修改`UserDaoFactory`类中的方法的返回值即可,只需修改这1处!!!

最后,一般不会因为创建1个对象,就创建1个工厂,所以会调整为:

    public abstract class UserDaoFactory {

        public static IUserDao getInstance() {
            return new MybatisUserDao();
        }

    }

当需要对象时:

    private IUserDao dao = UserDaoFactory.getInstance();

## Spring

Spring框架主要解决了创建类的对象和管理类的对象的问题,使用Spring框架后,可以不必再使用`new`语法创建对象。

Spring也称之为“Spring容器”,存放着由它创建出来的所有对象,当开发过程中需要某个对象时,直接从容器中获取即可!

### 1. 第1次使用Spring

1 创建`Maven Project`,创建过程中勾选`Create a simple project`,填写`Group Id`和`Artifact Id`,前者可以使用项目的包名,后者使用项目的名称(显示在Eclipse左侧的项目名),然后把`packaging`选为`war`项目,然后自动生成`web.xml`。

2 从FTP下载`application.zip`文件,并解压,将得到的xml文件重命名为`applicationContext.xml`(非必要操作),将文件复制到项目的`src\main\resources`下。

3 在`pom.xml`中添加对`spring-webmvc`的依赖(注意区分大小写),使用的版本只需要是`3.2`或以上版本均可(通常不推荐使用`5.x`版本),当Maven更新成功后,会在项目的库的Maven分支下显示8个jar包。

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>
    </dependencies>

4 创建测试用的类型,例如创建`cn.tedu.spring.entity.User`类,内容可以为空。

    public class User {
    }

5 创建测试运行的类,例如`cn.tedu.spring.test.Test`类,创建时,勾选中`main`方法,例如:

    public class Test {

        public static void main(String[] args) {
            // 传统做法:直接new对象即可(只要存在默认构造方法)
            User user = new User();
            // 测试输出
            System.out.println(user);
        }
    
    }

6 如果希望由Spring来创建User对象,并由Spring管理,首先,得让Spring知道它应该创建和管理谁,则需要编辑`applicationContext.xml`文件,在文件中添加配置:

    <!-- id:自定义名称,不可冲突,后续将根据这个名称从Spring容器中获取对象,通常使用类名且首字母小写作为id -->
    <!-- class:需要Spring创建并管理的类 -->
    <bean
        id="user" 
        class="cn.tedu.spring.entity.User">
    </bean>

7 在测试类(`Test.java`)的`main()`方法中:

    // 加载Spring配置文件,并获取Spring容器(名为ac的变量)
    ApplicationContext ac
        = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
    // 从Spring容器中获取所需的对象
    // getBean()方法的参数是XML中配置的id
    User user = (User) ac.getBean("user");
        
    // 测试输出
    System.out.println(user);

### 2. 使用Spring管理对象的配置方式

#### 2.1. [重要] 需要管理的类存在无参数的构造方法

例如以上的`User`类型,没有写构造方法,即使用默认构造,也就是公有的、无参数的构造方法,适用于:

    <bean
        id="user" 
        class="cn.tedu.spring.entity.User" />

满足这种使用方式的类,必须存在无参数的构造方法,至于有没有其它构造方法,或者构造方法使用的是哪种访问权限,这些是没有要求的!

#### 2.2. [了解] 需要管理的类存在静态工厂方法

以`Calendar`为例,存在`public static Calendar getInstance()`方法,这个方法就是`Calendar`类的静态工厂方法。

满足这种条件的类的配置方式:

    <!-- factory-method:工厂方法的名称 -->
    <bean 
        id="calendar"
        class="java.util.Calendar"
        factory-method="getInstance" />

#### 2.3. [了解] 需要管理的类存在实例工厂方法

假设存在以下2个类:

    public class Phone {

        public Phone(String name) {
        }

    }

    public class PhoneFactory {

        public Phone getInstance() {
            return new Phone();
        }

    }
    
由于`PhoneFactory`中的`getInstance()`方法没有使用`static`关键字,如果要调用这个方法,必须先创建`PhoneFactory`类的对象(实例),所以,这种情况就是满足实例工厂方法的。

对于这种情况,在Spring配置文件中,需要做2项配置:

    <!-- factory-bean:可以调用实例工厂方法的对象 -->
    <bean 
        id="phone"
        class="cn.tedu.spring.entity.Phone"
        factory-bean="phoneFactory"
        factory-method="getInstance" />

    <bean
        id="phoneFactory"
        class="cn.tedu.spring.entity.PhoneFactory" />

#### 小结 

以上介绍的3种方式中,仅第1种实用性较好,后面的2种仅了解即可。

### 由Spring管理的对象的生命周期

Spring管理的对象,会在加载Spring配置文件,获取Spring容器时,就已经创建对象!

由Spring管理的对象,默认是单例的!从生命周期来看,默认是常驻内存的!

在`<bean>`节点中,有名为`scope`的属性,常用取值有`singleton`(单例,默认值),`prototype`(非单例)………………

如果由Spring管理的对象是单例,默认情况下是饿汉式的,通过配置`lazy-init`属性可以设置是否懒加载,如果需要是懒汉式,则配置该属性且值为`true`。

如果由Spring管理的对象是单例,还可以配置2个生命周期方法,分别是`init-method`和`destroy-method`,分别用于表示初始化方法和销毁方法,初始化方法的执行时间取决于是否单例和是否懒加载(非单例时,每次获取对象时执行;懒汉式单例时,第1次获取对象时执行;饿汉式单例时,加载Spring配置文件时执行),而销毁方法会在关闭Spring容器(调用`ac.close()`方法)时被调用。

注意:如果由Spring管理的对象不是单例的,其初始化的生命周期方法也是可以执行,但是,没有“只执行1次”的特性,不便于控制,所以,一般情况下,非单例的类型,并不配置生命周期方法。

 

 


## 其它

## 1. 生命周期

生命周期描述的是某个事务从无到有,再到无的历程,通常讨论生命周期问题,关注的可能是中间的历程,或者整个生命周期存在的持续时长。

以`Servlet`为例,关注其生命周期具体表现为了解一系列的方法,例如:

    init()    service()    doGet()/doPost()    destory()

那么,学习`Servlet`的生命周期的主要意义在于了解:有哪些生命周期方法,它们会在什么时候被调用,进而,我们才知道应该重写哪些方法,并且哪些方法中编写什么样的代码!

 

## 2. 如果从Maven服务器下载的jar包有问题

1 确定本地jar包的位置

在Eclipse的设置中,找到`Maven` -> `User Settings`,在右侧找到`Local Repository`,对应的路径值,就是本地jar的文件夹,通常名为`m2`或`.m2`

2 关闭Eclipse

3 删除本地jar包的文件夹

4 重新打开Eclipse

5 对项目点右键,选择`Maven` -> `Update Project`,并在弹出的窗口中勾选`Force ...`选项,强制更新

 

## 3. 内存

专业领域中的描述,内存指的是内部存储器,主要包括:RAM / Cache / ROM。

RAM = Random Access Memory,具体的表现就是内存条,也是手机行业中描述的“运行内存”。

RAM是CPU可以直接交互的唯一硬件!也是CPU与其它硬件交换数据的桥梁!

RAM一旦断电,所有数据会消失!

正在执行的程序与数据都在RAM中!

 

### 4. static关键字

`static`关键字表达为“静态的”,具体的特性是:唯一,常驻。

例如:

    public class Something {
        public static String name="不知道";
    }

然后:

    Something s1 = new Something();
    Something s2 = new Something();
    s1.name = "hello";

并且:

    System.out.println(s1.name);
    System.out.println(s2.name);

所以,被`static`修饰的成员具有唯一的特性,在访问时,也不应该通过对象去访问,因为它不归属于任何一个对象,而是应该通过类的名称去引用,例如`Something.name`。

而常驻,表示它常驻内存!因为正在执行的程序和数据必须在内存中,所以:

    public class Test {
        private int i = 10;

        public static void main(String[] args) {
            System.out.println(i);
        }
    }

以上代码是错误的,不可以在`main()`方法中直接使用`i`变量,因为`main()`方法被`static`修饰,就是常驻内存的,而`i`没有使用`static`修饰,默认并不存在于内存中,所以无法直接访问!也就是通常表现的规则:`static`成员不可以直接访问非`static`成员!

如果在`main()`方法中一定要使用到这个`i`,可以为`i`添加`static`修饰,或者,在`main()`方法中,先`new`这个类的对象,例如`Test test = new Test();`,然后通过`new`出来的对象去引用`i`,例如`System.out.println(test.i);`也是可以的!

 

### 5. 单例模式

单例模式实现某个类在同一时间只允许有1个实例,不可能同时存在多个实例。

单例模式分为饿汉式单例和懒汉式单例。

饿汉式的单例模式表现为:

    public class King {
        private static King king = new King();
        
        private King() {
        }
        
        public static King getInstance() {
            return king;
        }
    
    }

所以,饿汉式的单例模式是在一开始就把对象创建好了,任何时候需要时,都可以随时使用!

相反,懒汉式的单例模式是一开始并没有创建对象,仅当需要时,才完成第1次创建:

    public class King {
        private static King king;
        
        private King() {
        }
        
        public static King getInstance() {
            if (king == null) {
                king = new King();
            }
            return king;
        }
    
    }

懒汉式的单例也称之为:延迟加载。

注:以上代码的饿汉式单例是不完整的,没有解决线程安全问题。

**Spring-day02**

## 1 注入属性的值(重要)

### 1.1 基本概念

以某个`User`类为例:

    public class User {

        public String name;

    }

如果在Spring的配置文件中进行配置,可以使得加载Spring配置文件时,就创建出它的对象,在此基础之上,还可以通过配置,使得该类中的`name`属性(也可以是其它属性)具有值,最终,在程序运行时,如果获取`User`的对象,其中的`name`属性是已经被赋值的!

### 1.2 通过SET方法注入(重要)

首先,需要为类中的属性(需要被注入的值属性)添加SET方法,可以通过Eclipse工具自动生成属性的SET方法,例如:

    public class User {

        public String name;

        public void setName(String name) {
            this.name = name;
        }

    }

然后,在Spring的配置文件中,将`<bean>`节点写成成对的标签,并添加`<property>`子节点:

    <!-- property节点:用于注入属性的值 -->
    <!-- name:属性名 -->
    <!-- value:属性值 -->
    <bean id="user"
        class="cn.tedu.spring.entity.User">
        <property name="name" value="David" />
    </bean>

如果有多个属性需要注入值,则每个属性都需要有SET方法,并且,在`<bean>`下使用多个`<property>`节点进行配置。

注意:在配置`<property>`节点时,其中的`name`属性用于指定属性名,其实,需要指定的是SET方法的名称中除了`set`部分以外的字符,例如在类中的属性名叫`age`,而SET方法的名称叫`setUserAge`,那么,在配置时,需要配置为`name="userAge"`。也就是说,Spring在工作时,会根据配置文件中的例如`userAge`名称,将首字母改为大写,并在左侧拼上`set`,得到`setUserAge`作为方法名称,然后调用方法,完成值的注入!不过,这个问题可以不用过多关注,只要保证每个SET/GET方法都是Eclipse这些开发工具生成的名称即可,因为这些工作生成SET/GET方法时也是使用这样的规则!这样的话,就把`name`属性视为设置的是属性名也可以!

以上做法适用于属性的类型是基本值(基本值:可以直接通过键盘输入的,例如字符串、数值等)的,如果某个属性的值不是基本值可以描述的,例如:

    public class User {
        public Date regTime;
    }

当添加了SET方法以后,在Spring的配置文件中,需要先配置出这个属性值的`<bean>`,然后 ,在注入值时,在`<property>`节点中使用`ref`属性进行配置:

    <bean id="user"
        class="cn.tedu.spring.entity.User">
        <property name="regTime" ref="date" />
    </bean>
    
    <bean id="date"
        class="java.util.Date" />

### 1.3 通过构造方法注入(不常用)

如果属性的值是通过构造方法注入的,则需要有带参数的构造方法,且不要求有SET方法,例如:

    public class Person {
        public String name;

        public Person(String name) {
            this.name = name;
        }
    }

然后,在Spring的配置文件中:

    <!-- constructor-arg节点:配置构造方法参数 -->
    <!-- index:参数索引,从0开始顺序编号 -->
    <!-- value:参数值 -->
    <bean id="person"
        class="cn.tedu.spring.entity.Person">
        <constructor-arg 
            index="0"
            value="Jackson" />
    </bean>

在配置`<constructor-arg>`节点时,`index`属性表示某个构造方法参数的索引,从0开始顺序编号。

与通过SET方式注入相同,如果是基本值,通过`value`属性来配置,如果是其它对象类型,通过`ref`属性来引用另一个bean的id。

当有了这种方式后,无论某个类有没有默认构造方法(无参数的构造方法),都可以通过Spring的配置后,由Spring创建并管理对象。

### 1.4 实际应用

在实际应用中,需要注入值的,往往是功能相关的类,并不是实体类,例如:

    public class UserDao {

        public void insert(User user) {
        }

    }

    public class UserService {

        public UserDao userDao;

        public void reg(User user) {
            userDao.insert(user);
        }

    }

在以上代码中,`UserService`中需要`UserDao`的对象,则可以通过Spring的配置,使得`UserService`被创建出来时,就会为`UserDao userDao`这个属性注入值。

在实际应用中,更多的会使用接口来编程,例如先创建持久层接口,并约定抽象方法:

    public interface IUserDao {
        void insert();
    }

然后,使得原有的`UserDao`类实现以上接口:

    public class UserDao implements IUserDao {
        ...

最后,在`UserDao`的调用者,也就是在`UserService`类中,将声明和参数类型全部换成接口类型:

    public class UserService {
        private IUserDao userDao; // 上午写的时候,属性类型是UserDao

        // 上午写的时候,参数类型是UserDao
        public void setUserDao(IUserDao userDao) {
            ...
    }

经过这样的调整后,可以发现,在`UserService`这个类中,完全不体现是哪个类实现的`IUserDao`接口,从而使得`UserService`这个类与`UserDao`类没有太直接的关系,进而,在后续的开发中,如果`UserDao`不满足需求,需要另外写一个例如`UserDaoImpl`类来工作,也只需要让新的`UserDaoImpl`也实现`IUserDao`接口,并在Spring的配置文件中,把原来配置的`UserDao`换成`UserDaoImpl`即可,而`UserService`里不需要有任何调整,所以,总的来说,这种做法的好处就是“哪里有问题改哪里”。

### 1.5 自动装配

在Spring中“自动装配”表示自动的为所有属性注入值,而不需要在Spring的配置文件中去配置各个`<property>`节点!取而代之的是在对应的`<bean>`节点中配置`autowire`属性,例如:

    <bean id="userService"
        class="cn.tedu.spring.dao.UserDaoImpl"></bean>
        
    <bean id="userService"
        class="cn.tedu.spring.service.UserService"
        autowire="byType">
    </bean>
    
    <bean id="userController"
        class="cn.tedu.spring.controller.UserController"
        autowire="byName" />

关于`autowire`属性,常用取值有`byName`和`byType`。

当取值为`byName`的时候,表示**按照名称实现自动装配**,例如,有属性`private IUserService userService;`时,Spring将在容器中尝试找bean id为`userService`的对象,如果找到,则自动装配,**也就是说:属性的名称,与Spring管理的范围内的某个bean id是匹配的(本质上,还是SET方法中的名称需要与bean id是匹配的)!**当然,本质上还是通过SET方式注入的,所以,这个属性还是需要有SET方法。

当取值为`byType`的时候,表示**按照类型实现自动装配**,例如,有属性`private IUserService userSerivce;`时,Spring不再关注任何bean id,而是在Spring管理的范围查找有没有类型与`IUserService`匹配的对象,如果找到,则自动装配,**也就是说:属性的类型,与Spring管理的范围内的某个bean的类型是匹配的!**,需要注意的是:使用这种自动装配方式,必须保证匹配的类型只有1个,如果超过1个,则无法自动装配,程序会抛出异常!

其实,规范的程序中并不存在命名的相关问题,所以,使用`byName`肯定是最稳妥的做法!事实上,也几乎不会出现同一个接口有2个或更多个实现类同时共存的情况!也就不会出现`byType`的冲突问题!所以,用哪种其实无所谓!

另外,该属性还有其它取值,暂时不关注。

自动装配的特性是:能自动装配就会为对应的属性注入值,对于那些无法自动装配属性,则不处理!

由于类中的属性哪些能被装配值,又有哪些无法被装配值,无论是从Spring的配置文件中,还是Java源代码中,都是没有明确的表现的,特别是在完成项目时,可能涉及的类有很多,更加难以理解代码的运行,所以,不推荐在项目中使用这种做法完成值的注入!

尽管不推荐,但是,`byType`和`byName`的装配思想是需要理解的,后面用得着!

### 1.6 注入集合类型的值

使用Spring注入值时,还可以为List、Set、Map等集合类型的属性注入值!注入方式可以是:

    <!-- // Jack, Rose, LiLei, HanMM
            public List<String> names; -->
    <property name="names">
        <list>
            <value>Jack</value>
            <value>Rose</value>
            <value>LiLei</value>
            <value>HanMM</value>
        </list>
    </property>    
    <!-- // Beijing, Shanghai, Guangzhou, Shenzhen
            public Set<String> cities; -->
    <property name="cities">
        <set>
            <value>Beijing</value>
            <value>Shanghai</value>
            <value>Guangzhou</value>
            <value>Shenzhen</value>
        </set>
    </property>
    <!-- // username=admin, password=123456, from=Hangzhou
            public Map<String, String> session; -->
    <property name="session">
        <map>
            <entry key="username" value="admin" />
            <entry key="password" value="123456" />
            <entry key="from" value="Hangzhou" />
        </map>
    </property>

也可以是:

    <bean id="collectionEntity"
        class="cn.tedu.spring.entity.CollectionEntity">
        <property name="names" ref="n" />
    </bean>

    <util:list id="n">
        <value>Tom</value>
        <value>Jerry</value>
    </util:list>

由于一般很少直接在Spring的配置文件中确定一些值,所以,以上这些做法的实用性并不高!

关于`Properties`类型的配置可以是:

    <!-- // 数据库配置信息
        public Properties dbConfig;  -->
    <property name="dbConfig">
        <props>
            <prop key="username">root</prop>
            <prop key="password">root</prop>
            <prop key="initActive">2</prop>
            <prop key="maxSize">10</prop>
        </props>
    </property>

但是,这种做法并不好,因为,把数据库的配置写在XML文件中,如果客户修改,却不具备专业知识,可能破坏XML文件的格式,导致整个文件无法使用!所以,此类的配置通常会写在`*.properties`文件中,然后从这些文件中再读取:

    db.properties
        url=jdbc:mysql://localhost:3306/db?characterEncoding=utf8
        driver=com.mysql.jdbc.Driver
        username=root
        password=root
        initActive=2
        maxSize=10
        
然后,在Spring的配置文件中添加一个与`<bean>`同级的节点:

    <util:properties id="dbConfig"
        location="classpath:db.properties"></util:properties>

当Spring加载这个配置文件时,就会直接解析`db.properties`文件,并且,这个`<util:properties>`就是一个`Properties`类型的对象,所以,当需要注入值时,使用`ref`属性引用到这个`<util.properties>`的`id`即可:

    <!-- // 数据库配置信息
            public Properties dbConfig;  -->
    <property name="dbConfig" ref="dbConfig" />

这种做法,简单,实用,需要掌握!

 

## 其它

### 1 Eclipse中的常用快捷键

    Ctrl + Shift + F    格式化源代码        
    Ctrl + Shift + O    整理package        
    Alt + 上/下            向上/下移动光标所在行的代码,可以是多行
    Ctrl + Alt + 上/下    将光标所在行的代码向上/下复制,可以是多行
    Ctrl + D            删除光标所在行的代码,可以是多行
    Ctrl + 2, R            批量重命名代码中的某个名称,可以是类名、方法名、量的名称,操作之前需先选中    

### 2 代码中的命名规范

### 3 MVC中的访问流程

控制器层(Controller) <----> 业务层(Service) <----> 持久层(Dao)

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值