个人技术分享

Spring基础

1. 什么是Spring

1.1 Spring的概述

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 Spring的核心

  • 三大核心

    • IOC:Inverse Of Control: 反转控制

    • AOP:Aspect Oriented Programming:面向切面编程

    • DI:依赖注入 他是IOC的具体实现

1.3 Spring的发展历程

  • 1997 年 IBM 提出了 EJB 的思想

  • 1998 年,SUN 制定开发标准规范 EJB1.0

  • 1999 年,EJB1.1 发布

  • 2001 年,EJB2.0 发布

  • 2003 年,EJB2.1 发布

  • 2006 年,EJB3.0 发布

Rod Johnson(spring 之父)

2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)

扩展

EJB是Enterprise Java Beans技术的简称, 又被称为企业Java Beans。这种技术最早是由美国计算公司研发出来的。EJB技术的诞生标志着Java Beans的运行正式从客户端领域扩展到服务器领域。在电子商务领域运用EJB技术可以简化应用系统的开发, 这是由该技术的结构和特点所决定的

EJB (Enterprise Java Beans) 是基于分布式事务处理的企业级应用程序的组件。Sun公司发布的文档中对EJB的定义是:EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构

spring是轻量级的 EJB是重量级的 轻量级的 操作简单 容易部署 轻量级的更安全

1.4 Spring的优势

  • 方便解耦,简化开发 (IOC)

    通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。

  • AOP 编程的支持

    通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。

  • 声明式事务的支持

    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

  • 方便程序的测试

    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

  • 方便集成各种优秀框架

    Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。

  • 降低JavaEE API的使用难度

    Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。

  • Java源码是经典学习范例

    Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以 及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例

2. IOC的概念和作用

2.1 程序的耦合

耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。

2.2 耦合的分类

  • 内容耦合

    当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。

  • 公共耦合

    两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。

  • 外部耦合

    一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。

  • 控制耦合

    一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。

  • 标记耦合

    若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。

  • 数据耦合

    模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。

  • 非直接耦合

    两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

总结:

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

内聚与耦合:

内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。

耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。

内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。

2.3 耦合的解决方案

一款健康的软件最好都应该是 高内聚 低耦合 。耦合简单来说就是程序之间依赖的关系,包括类之间、方法之间的依赖关系,

解耦:

降低程序之间的依赖关系,但是仅仅降低程序之间的依赖关系,并不能完全消除。实际开发中应该做到编译时不依赖,运行时才依赖。

解决类之间的依赖关系思路:

  1. 使用反射创建对象,尽量避免使用new关键字

  2. 通过读取配置文件来获取要创建对象的全限定类名、

解耦的解决方案:

  1. 通过反射降低耦合度

  2. 通过工厂模式降低耦合度

  3. 通过IOC容器降低耦合度

2.3.1 通过反射解耦

反射的核心在于不直接new对象,而是通过程序运行的时候加载使用Class类加载对象。如使用jdbc连接数据库操作 举例

public class AccountTest {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //注册驱动
       // DriverManager.registerDriver(new com.mysql.jdbc.Driver());//说明耦合度比较高
        Class.forName("com.mysql.jdbc.Driver");
        //获取连接对象
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","123456");
        //获取PreparedStatement对象
        PreparedStatement pst = conn.prepareStatement("select * from account");
        //执行sql语句
        ResultSet rs = pst.executeQuery();
        //遍历结果集
        while(rs.next()){
            System.out.println(rs.getString("name"));
        }
        //关流
        rs.close();
        pst.close();
        conn.close();
    }
}

重点在于Class.forName("com.mysql.jdbc.Driver");加载mysql驱动对象,反射的方式可以解耦 但是写法比较复杂 也易出错

2.3.2 通过工厂模式解耦

工厂模式是一种设计模式,java常见的设计模式有很多,比如单例模式,工厂模式,建造者模式等等。工厂模式可以理解为编写一个创建对象的工厂,每次都通过工厂来创建,这样避免重复创建对象。

第一步:在resource包下创建bean.properties配置文件

student=com.demo.bean.Student

第二步:编写BeanFactory工厂类

public class BeanFactory {
    private static Properties pro;
​
    static {
        try {
            pro = new Properties();
            //读取配置文件
            InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            pro.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
​
    //获取对象的方法
    public static Object getBean(String beanName) {
        Object obj = null;
        try {
            obj = Class.forName(pro.getProperty(beanName)).newInstance();//每次通过构造方法来创建对象
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}
​

第三步:测试类

 @Test
    public void factoryTest(){
        //通过工厂获取对象
        Student student = (Student) BeanFactory.getBean("student");
        System.out.println(student);
        student.study();
    }

3. Spring的IOC容器

IOC控制反转:把创建对象的权力交给框架。它包含了依赖注入和依赖查找。 作用:削减计算机程序之间的 耦合,解除我们程序之间的依赖关系。

3.1 SpringIOC入门案例

快速入门步骤

  1. 导入spring的依赖

  2. 创建业务类 Service Dao

  3. 创建spring的配置文件

  4. 在配置问文件中对bean进行配置

    1. bean的属性

      1. id 是一个唯一标识,后期用于根据这个id从 ioc容器中 取出对象

      2. class 是类的全路径类名 (包名 + 类名)

  5. 创建测试类

    1. 获取ioc容器

    2. 从容器中获取对象

3.1.1. 导入spring的依赖
    <dependencies>
       <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
3.1.2. 创建配置文件并配置
<?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">
​
    <bean id="student" class="com.doyens.bean.Student"></bean>
</beans>
3.1.4.创建测试类并测试
 @org.junit.Test
    public void factoryTest(){
        //通过工厂获取对象
        Student student = (Student) BeanFactory.getBean("student");
        System.out.println(student);
        student.study();
    }

3.2 获取对象的三种方式

  • Object getBean(String beanId);

    • 根据bean的id来获取 需要强转

  • T getBean(String beanId,Class<T> class)

    • 根据bean的id来获取 不需要强转

  • T getBean(Class<T> class)

    • 根据bean的类型来获取 不需要强转,需要保证ioc容器中只有唯一的一个该类型的bean,如果有多个该类型的bean,则会发生异常。不推荐

3.3 获取IOC容器的2种方式

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式

3.3.1 BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

//1.创建IOC容器
Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
​
//2.从ioc容器中获取对象
Student student = beanFactory.getBean("student", Student.class);
​
System.out.println("student = " + student);
3.3.2 ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

ApplicationContext的三个实现类:

  1. ClassPathXmlApplicationContext:

    用于加载类路径下的配置文件。

  2. FileSystemXmlApplicationContext:

    用于加载磁盘路径下的配置文件。

  3. AnnotationConfigApplicationContext:

    用于读取注解 创建IOC容器。

小提示:

以后在 Spring 环境下看到一个类或接口的名称中包含 ApplicationContext,那基本就可以断定,这个类或接口与 IOC 容器有关。

 @org.junit.Test
    public void applicaitonContextTest_01(){
        //1.获取IOC容器
        ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
        //2.从容器中获取对象
        Student student = app.getBean("student",Student.class);
        System.out.println(student);
        student.study();
    }
​
    @org.junit.Test
    public void applicaitonContextTest_02(){
        //1.获取IOC容器
        ApplicationContext app = new FileSystemXmlApplicationContext("D:\\beans.xml");
        //2.从容器中获取对象
        Student student = app.getBean("student",Student.class);
        System.out.println(student);
        student.study();
    }
3.3.3 BeanFactory 和 ApplicationContext的区别

BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。

区别:创建对象的时间点不一样。

  1. ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。

  2. BeanFactory:什么获取什么时候创建对象。

4. IOC中bean的管理

4.1 bean标签

作用:

用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

属性: id:给对象在容器中提供一个唯一标识。用于获取对象。 class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。 scope:指定对象的作用范围。

  • singleton :默认值,单例的.

  • prototype :多例的.

  • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.

  • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.

  • global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session.

  • init-method:用于指定对象的初始化方法。当对象创建时,调用的方法。 作用:用于加载资源

  • destroy-method:用于指定对象销毁的方法。当对象销毁时,调用的方法 作用:用于释放资源

4.2 bean的作用范围和生命周期

单例对象:

  • scope="singleton"

  • 一个应用只有一个对象的实例。它的作用范围就是整个引用。

生命周期:

  • 对象出生:当应用加载,创建容器时,对象就被创建了。

  • 对象活着:只要容器在,对象一直活着。

  • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

多例对象:

  • scope="prototype"

  • 每次访问对象时,都会重新创建对象实例。

生命周期:

  • 对象出生:当使用对象时,创建新的对象实例。

  • 对象活着:只要对象在使用中,就一直活着。

  • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

5. Spring_DI 依赖注入

DI 是(Dependency Injection)的简称 就是依赖注入 什么是依赖注入呢?

IOC容器可以降低程序间的依赖关系,但是并不能完全消除。 Controller---->Service----->Dao:将依赖关系交由框架来维护。 所以依赖关系的维护,就成为依赖管理。 DI:就是IOC的具体体现。以一些预先定义好的方式,接收来自容器中的资源。

  • 构造函数

  • setter方法 p命名空间

  • 注解

5.1 依赖注入的三种方式

5.2 通过构造函数注入数据

创建一个Student类 其中地址的属性是一个对象

​
/**
 *  类就是模板
 *  每一个student对象都必须要有name  age  sex  address
 */
public class Student {
    private String name;
    private int age;
    private String sex;
    private Address address;//地址中包含了省市区
​//省略set和get方法
public class Address {
    private String sheng;
    private String shi;
    private String qu;
}

在bean标签中 通过构方法的方式来给对象属性赋值

    <bean id="student2" class="com.zrrd.bean.Student">
        <constructor-arg name="name" value="李四"></constructor-arg>
        <constructor-arg name="age" value="30"></constructor-arg>
        <constructor-arg name="sex" value="男"></constructor-arg>
        <constructor-arg name="address" ref="address2"></constructor-arg>
    </bean>
​
   <bean id="address2" class="com.zrrd.bean.Address">
        <constructor-arg name="sheng" value="湖北省"></constructor-arg>
        <constructor-arg name="shi" value="武汉市"></constructor-arg>
        <constructor-arg name="qu" value="武昌区"></constructor-arg>
    </bean>

<constructor-arg>标签:

  • name:属性名字

  • value:对应的属性值

注意:

使用有参构造方式 必须给所有字段都赋值 如果少字段 会报错

5.3 通过set方法注入数据

     <bean  id="address" class="com.zrrd.bean.Address">
        <property name="sheng" value="贵州"></property>
        <property name="shi" value="六盘水"></property>
        <property name="qu" value="六枝"></property>
    </bean>
​
    <bean  id="address2" class="com.zrrd.bean.Address">
        <property name="sheng" value="河南"></property>
        <property name="shi" value="安阳"></property>
        <property name="qu" value="林州"></property>
    </bean>
​
​
    <!--配置student对象-->
    <bean id="student" class="com.zrrd.bean.Student">
        <property name="name" value="旺财"></property>
        <property name="age" value="32"></property>
        <property name="sex" value="男"></property>
        <property name="address" ref="address2"></property>
    </bean>

<<property>标签:

使用set方式给对象的属性赋值

属性

  • name:属性名称

  • value:对应的属性值

和有参构造的区别:

可以单独给某个属性赋值 不会报错

5.4 通过p命名空间注入数据

使用p命名空间配置一个地址对象,其实p命名空间注入数据也是走set方法

使用步骤

  1. 导入p命名空间 xmlns:p="http://www.springframework.org/schema/p"

  2. 在配置bean的时候 通过p:属性名来赋值

  3. 基本数据类型和string赋值:p:属性名="值"

  4. 其他bean赋值:p:属性名-ref = "其他bean的id"

 <!--通过p命名空间配置student对象-->
    <bean id="student" class="cn.doyens.domain.Student" p:name="张三" p:age="25" p:sex="男" p:address-ref="address1">
    </bean>

5.5 给复杂类型注入数据

配置一个teacher对象 通过set方法来赋值

给数组赋值:array; 给list集合赋值:list 给set集合赋值:set。 他们都属于单列的集合,标签是可以混用的

给map集合赋值:map

结论:结构相同 标签可以混用

public class Teacher {
    private String name;
    private int age;
    private String[] hobby;
    private List<String> studentList;
    private Map<Integer, Integer> money;
}
 

在bean标签中注入数据

<bean id="teacher" class="com.zrrd.bean.Teacher">
        <property name="name" value="郭老师"></property>
        <property name="age" value="18"></property>
        <!--给数组赋值-->
        <property name="hobby">
            <array>
                <value>学习</value>
                <value>唱歌</value>
                <value>打游戏</value>
            </array>
        </property>
        <!--给List集合赋值-->
        <property name="studentList">
            <list>
                <value>张三</value>
                <value>李四</value>
            </list>
        </property>
        <!--给Map集合赋值-->
        <property name="money">
            <map>
                <entry key="4" value="4000"></entry>
                <entry key="5" value="8000"></entry>
                <entry key="6" value="1200"></entry>
            </map>
        </property>
​
    </bean>


5.6 bean的外部注入和内部注入
如果一个bean的属性类型 是另外一个bean 可以通过 <property name="address" ref="address"></property> 引入过来注入数据。还可以直接在当前bean的内部直接注入

 <!--配置一个student对象-->
    <bean id="student2" class="com.zrrd.bean.Student">
        <property name="name" value="李四"></property>
        <property name="age" value="22"></property>
        <property name="sex" value="女"></property>
        <!--内部bean-->
        <property name="address">
            <bean class="com.zrrd.bean.Address">
                <property name="sheng" value="贵州省"></property>
                <property name="shi" value="贵阳市"></property>
                <property name="qu" value="白云区"></property>
            </bean>
        </property>
    </bean>

5.7 引入其他的配置文件

通过import标签来引入

<?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">
​
   <import resource="bean.xml"></import>
</beans>

6. 基于注解的IOC配置

6.1 什么是注解

Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。

和xml配置文件一样的。它 本身不能执行,仅仅是一个标记。框架检测到这个标记的位置,根据这个标记的位置和标记的功能来完成对象的代码。本质上也是执行java代码。

6.2 Spring中注解的分类

6.2.1 原始注解

作用:Spring原始注解主要是替代<Bean>的配置

注解 说明
@Component 使用在类上用于实例化Bean
@Controller 使用在web层类上用于实例化Bean
@Service 使用在service层类上用于实例化Bean
@Repository 使用在dao层类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入
@Resource 相当于@Autowired+@Qualifier,按照名称进行注入
@Value 注入普通属性
@Scope 标注Bean的作用范围
@PostConstruct 使用在方法上标注该方法是Bean的初始化方法
@PreDestroy 使用在方法上标注该方法是Bean的销毁方法
6.2.1.1 用于创建对象的
  • @Component

    • 用于替代bean标签 创建当前类的对象 并存入到IOC容器中

    • id默认就是类名,并且首字母小写

    • id也可以通过value属性来指定

    • 如果注解有且仅有一个value属性时 value可以省略不写

  • @Controller

    • 于注解控制器的 也就是表现层。 就是controller层的注解

  • @Service

    • 用于注解业务逻辑层 就是service层的注解

  • @Repository

    • 用于注解持久层 就是dao层

如果类不属于以上三层 那么就使用这个注解

四个注解的作用是一样的 仅仅是供程序员区分具体的类属于那一层。

注意:

使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。

<!--注解的组件扫描-->

<context:component-scan base-package="com.zrrd"></context:component-scan>

6.2.1.2 用于注入数据的

他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的

  • Autowired

    • 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功

    • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。

  • Qualifier

    • 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以

  • Resource

    • 作用:直接按照bean的id注入。它可以独立使用

    注意事项:

    @Resource javax.annotation包中的, 而Spring项目中缺少javax.annotation包的依赖, 所以我们把这个依赖加上就可以了

       
     <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.2</version>
        </dependency>

    以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。

    • 另外,集合类型的注入只能通过XML来实现。

6.2.1.3 改变作用范围的

他们的作用就和在bean标签中使用scope属性实现的功能是一样的

  • Scope

    • 作用:用于指定bean的作用范围

    • value:指定范围的取值。常用取值:singleton(默认值) prototype

    ​