烂笔头

扬帆起航,再出发!!!

0%

说明

  • 分析vue作为一个MVVM框架的基本实现原理
    • 数据代理
    • 模板解析
    • 数据绑定
  • 不直接看vue.js的源码
  • 剖析github上网友模仿vue实现的mvvm库,地址:https://github.com/DMQ/mvvm

AOP概述

什么是AOP

AOP:全称是Aspect Oriented Programming 即:面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用行,同时提高了开发的效率。

AOP的作用及优势

  • 作用:
    在程序运行期间,不修改源码对已有方法进行增强
  • 优势:
    • 减少重复代码
    • 提高开发效率
    • 维护方便

Spring中的AOP

说明:Spring中的AOP就是通过配置的方式实现动态代理功能。

AOP相关术语

  • 连接点(Joinpoint):所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
  • 切入点(Pointcut):所谓切入点是指我们要对那些Joinpoint进行拦截的定义
  • 通知/增强(Advice):
    • 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知
    • 通知的类型:前置通知(业务方法调用前)、后置通知(业务方法调用后)、异常通知(catch代码中)、最终通知(final代码中)、环绕通知(业务方法的调用)。
  • 引介(Introduction):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field。
  • Target(目标对象):代理的目标对象。
  • 织入(Weaving):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  • 切面(Aspect):是切入点和通知(引介)的结合。

JUnit无法实现数据注入的原因

  • 应用程序的入口
    • main方法
  • junit单元测试中,没有main方法也能执行
    • junit集成了一个main方法,该方法就会判断当前测试类中那些方法有@Test注解,junit就让有Test注解的方法执行
  • junit不会管我们是否采用Spring框架
    • 在执行测试方法时,junit根本不知道我们是不是使用了Spring框架,所以也不会为我们读取配置文件/配置类创建IOC容器
  • 由以上三点可知:
    • 当测试方法执行时,没有IOC容器,就算写了Autowrited注解,也无法实现注入,

Spring整合JUnit解决方案

  • 导入Spring整合junit的jar包
  • 使用junit提供的注解把原有的main方法替换了,替换成Spring提供的
    • @RunWith
  • 告知Spring的运行器,Spring的IOC创建时基于xml还是注解的,并说明位置
    • @ContextConfiguration
      • locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
      • classes:指定注解类所在地位置

项目GitHub地址

注解+XML方式

1.在pom.xml中导入相应jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>

<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>

2、在resources目录下创建bean.xml配置文件,并指定Spring在创建容器时要扫描的包,及将项目中依赖的外部jar包中的类及其依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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">

<!--告诉spring在创建容器时要扫描的包-->
<context:component-scan base-package="io.github.lonelyMrZhang"></context:component-scan>

<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://192.168.31.254:3306/coupling_code"></property>
<property name="user" value="root"></property>
<property name="password" value="2486"></property>
</bean>
</beans>

3、在对应的类或方法上标记相应的注解

  • 用于创建对象的使用@Component、@Controller、@Service、@Repository中对应的注解
  • 用于注入数据的使用@Autowired[@Qualifier]、@Resource、@Value中对应的注解
  • 用于改变作用范围的@Scope
  • 和生命周期相关 @PreDestroy、@PostConstruct

各种注解的作用详解

项目GitHub地址

全注解方式

完全去除XML文件需要用到的其他注解

  • @Configuration
    • 作用:指定当前类是一个配置类
    • 细节:当配置类作AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。在@ComponentScan({“io.github.lonelyMrZhang”,”config”})(见下一个)注解中指定扫描其他配置类时,在其他配置类上应该标上Configuration注解,Spring只有看到Configuration注解才会将这个类当作配置类
  • @ComponentScan
    • 作用:用于通过注解指定Spring在创建容器时要扫描的包
    • 属性:
      • value:它和basePackages的作用相同,都是用于指定创建容器时要扫描的包。使用此注解就等于在xml配置文件中配置了:<context:component-scan base-package="io.github.lonelyMrZhang"></context:component-scan>
  • @Bean
    • 作用:用于把当前方法的返回值作为bean对象注入Spring的IOC容器中
    • 属性:
      • name:用于指定bean的id,默认为当情方法名称
    • 细节:
      • 当我们使用注解配置方法时,如果方法有参数,Spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired注解的作用时一样的
  • @Import:
    • 作用:用于导入其他的配置类
    • 属性:
      • value:用于指定其他配置类的字节码
    • 说明:当我们使用import的注解之后,有import注解的类是父配置类,导入的时子配置类
  • @PropertySource
    • 作用:用于指定properties文件的位置
    • 属性:
      • value:指定文件的名称和路径
      • 关键字:classpath,类路径下

在注解+XML的项目基础上,进行以下操作:

1.删除resources下的bean.xml类,并创建jdbcConfig.properties数据库配置文件

1
2
3
4
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.31.254:3306/coupling_code
jdbc.user=root
jdbc.password=2486

properties文件中的数据可以使用@Value注解通过EL表达式取出

1
2
@Value("${jdbc.driver}")
private String driver;

2.创建config包下创建SpringConfiguration配置类,并标注@Configuration注解告诉Spring容器这是一个配置类、标注@ComponentScan(“io.github.lonelyMrZhang”)注解告诉Spring创建容器时要扫描的包、标注@PropertySource(“classpath:jdbcConfig.properties”)注解加载properties配置文件,如果还有其他配置类可以使用@Import(JdbcConfiguration.class)注解包含进来,不过要在该类上标注@Configuration注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;

/**
* @description: 配置类,作用和bean.xml一样
* @author: lonely.mr.zhang
* @date: 2020/6/16 11:59 上午
*/
//@Configuration
@Configuration
//@ComponentScan({"io.github.lonelyMrZhang","config"})
@ComponentScan("io.github.lonelyMrZhang")
@Import(JdbcConfiguration.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {

}

项目GitHub地址

1.在pom.xml中引入相应jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>

<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!--连接池-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>

2.在resources目录下创建bean.xml配置文件,并注入service、dao中相应类的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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">

<!--配置service-->
<bean id="accountService" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置AccountDao-->
<bean id="accountDao" class="io.github.lonelyMrZhang.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="queryRunner"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://192.168.31.254:3306/coupling_code"></property>
<property name="user" value="root"></property>
<property name="password" value="2486"></property>
</bean>
</beans>

注意<property>标签是基于set方法进行注入的,所以在.java文件中必须要有相应的set方法。

项目GitHub地址

基于xml标签的配置:

1
2
3
4
<bean id="accountService" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl"
scope="" init-method="" destroy-method="">
<property name="" value="" | ref=""></property>
</bean>

启用注解创建SpringIOC容器

  • 在bean.xml中引入context名称空间和约束
    1
    2
    3
    4
    5
    6
    7
    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">
    </beans>
  • 告知Spring在创建容器时要扫描的包
    1
    2
    <context:component-scan base-package="io.github.lonelyMrZhang"></context:component-scan>
    <!--该包及其下的子包中的类都会被扫描-->

IOC常用注解分类

  • 用于创建对象的
    • 他们的作用就和在xml配置文件中编写一个<bean>标签实现的功能是一样的
    • @Component:
      • 作用:用于把当前类对象存入Spring容器中
      • 属性:
        • value:用于指定bean的id,当我们不写时,它的默认值是当前类名,且首字母改小写
    • @Controller:一般用在表现层
    • @Service:一般用在业务层
    • @Repository:一般用在持久层
    • 以上三个注解的作用和属性与Component一模一样,他们三个是Spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
  • 用于注入数据的
    • 他们的作用就和在xml文件中的bean标签中写一个<property>标签的作用是一样的
    • @Autowired:
      • 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配(包含向上转型),就可以注入成功,如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错,如果有多个匹配,将使用属性名和容器中key进行匹配,如果没有匹配则报错。
      • 出现位置:可以是变量上,也可以是方法上
      • 细节:在使用注解注入时,set方法就不是必须的了
    • @Qualifier :
      • 作用:在按照类型注入的基础之上再按照名称注入,它在类成员注入时不能单独使用,但是在给方法参数注入时可以
      • 属性:
        • value:用于指定注入bean的id
    • @Resource:
      • 作用:直接按照bean的id注入,可以独立使用
      • 属性:
        • name:用于指定bean的id
    • 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现,另外,集合类型的注入只能通过xml来实现。
    • @Value:
      • 作用:用于注入基本类型和String类型的数据
      • 属性:
        • value:用于指定数据的值,它可以使用spring中的spel(el表达式),eg:${表达式}
  • 用于改变作用范围的
    • 他们的作用就和在bean标签中使用scope书香实现的功能是一样的
    • @Scope:
      • 作用:用于指定bean的作用范围
      • 属性:
        • value:指定范围的值,常用取值:singleton prototype
  • 和生命周期相关 了解
    • 他们的作用就和在bean标签中使用init-method和destroy-method的作用是一样的
    • @PreDestroy:
      • 作用:用于指定销毁方法
    • @PostConstruct:
      • 作用:用于指定初始化方法

项目GitHub地址

创建Bean的三种方式

  • 使用默认构造函数:在Spring的配置文件中使用bean标签,配以id和class属性后且没有其他属性和标签时,采用的就是默认的构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。

    1
    <bean id="accountService" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl"></bean>
  • 使用工厂类中的方法创建对象,并存入Spring容器。

    1
    2
    3
    4
    <!--     1、先创建工厂类-->
    <bean id="instanceFactory" class="io.github.lonelyMrZhang.factory.InstanceFactory"></bean>
    <!-- 2、再在要创建对象的bean标签中,指定要可以创建对象的工厂,及使用工厂中的那个方法进行创建-->
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
  • 使用工厂中的静态方法创建对象,并存入Spring容器中。

    1
    <bean id="accountService"  class="io.github.lonelyMrZhang.factory.StaticFactory" factory-method="getAccountService"></bean>

Bean对象的作用范围

Spring Bean的作用范围可以通过bean标签的scope属性进行调节:

  • scope的作用:用于指定bean的作用范围
  • scope的取值:
    • singleton;单例的(默认值)
    • prototype:多例的
    • request:作用web应用的请求范围
    • session:作用web应用的会话范围
    • global-session:作用集群web环境的会话范围(全局会话范围),当不是集群环境时,它就是seesion,例如:
      • 1、用户登陆时由A服务器响应。响应内容包括验证码
      • 2、用户之后请求其他页面时,都会携带验证码,但是之后的请求不一定是A服务器响应,可能是另一台B服务器响应,此时B服务器也知道之前用户登陆时返回的验证码,这个验证码就存储在全局 global-session中。

Bean对象的生命周期

  • 单例对象:

    • 出生:当容器创建时对象出生
    • 存活:只要容器还在,对象一直存活
    • 死亡:容器销毁,对象死亡
    • 总结:单例对象的生命周期和容器相同
  • 多例对象:

    • 出生:当我们使用对象时,Spring为我们创建
    • 存活:对象只要在使用过程中就一直活着
    • 死亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收器回收

项目GitHub地址

依赖注入(Dependency Injection)

IOC的作用:降低程序间的依赖关系(耦合)

依赖关系的管理:以后都交给Spring来维护在当前类需要用到其他类的对象,由Spring为我们提供,我们只需要在配置文件中说明依赖关系的维护。

依赖注入:依赖关系的维护,就称之为依赖注入。

  • 能注入的数据:
    • 基本类型和string
    • 其他bean类型(在配置文件中或注解配置过的bean)
    • 复杂类型/集合类型
  • 注入的方式:
    • 使用构造函数提供
    • 使用set方法提供
    • 使用注解提供

构造函数注入

  • 使用的标签:constructor-arg

  • 标签出现的位置:bean标签的内部

  • 标签中的属性:

    • type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
    • index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引位置从0开始
    • name:用于指定给构造函数中指定名称的参数赋值
    • ==========以上三个用于指定给构造函数中的那个参数赋值=========
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他bean类型数据(Spring IOC容器中出现过的bean对象)
  • 优势:

    • 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
  • 弊端:

    • 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也必须提供
    1
    2
    3
    4
    5
    6
    7
    <bean id="accountService" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl">
    <constructor-arg name="name" value="张三"></constructor-arg>
    <constructor-arg name="age" value="20"></constructor-arg>
    <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

    <bean id="now" class="java.util.Date"></bean>

    set方法注入 更常用

  • 涉及的标签:property

  • 出现的位置:bean标签内部

  • 标签的属性:

    • name:用于指定注入时所调用的set方法名称,set方法的名字跟属性的名称没有关系
    • value:用于提供基本类型和String类型的数据
    • ref:用于指定其他bean类型数据(Spring IOC容器中出现过的bean对象)
  • 优势:

    • 创建对象时没有明确的限制,可以直接使用默认构造函数
  • 弊端:

    • 如果某个成员必须有值,则set方法无法保证一定注入
1
2
3
4
5
<bean id="accountService2" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl2">
<property name="userName" value="李四"></property>
<property name="age" value="30"></property>
<property name="birthday" ref="now"></property>
</bean>

复杂类型的注入/集合类型的注入

  • 用于给List结构集合注入的标签:list、array、set
  • 用于给Map结构集合注入的标签:map、props

结构相同,标签可以互换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<bean id="accountService3" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">CCC</prop>
<prop key="testD">DDD</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="AAA"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
</bean>

Spring源码下载地址

Spring中基于XML的IOC环境搭建

  1. pom.xml中引入Spring依赖
    1
    2
    3
    4
    5
    6
    7
    8
    <dependencies>
    <!--Spring依赖-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
    </dependency>
    </dependencies>

2.在resources目录下创建配置文件bean.xml(名称可改变)
3.导入约束 在Spring源码中的 spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/index.html中点击core,然后搜索xmlns,将搜到的约束拷贝。

1
2
3
4
5
<?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">

4、将对象的创建交给Spring管理,在bean.xml中添加如下<bean>标签

1
2
3
<bean id="accountService" class="io.github.lonelyMrZhang.service.impl.AccountServiceImpl"></bean>

<bean id="accountDao" class="io.github.lonelyMrZhang.dao.impl.AccountDaoImpl"></bean>

5、获取Spring的IOC核心容器,并根据id获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.github.lonelyMrZhang.ui;

import io.github.lonelyMrZhang.dao.IAccountDao;
//import io.github.lonelyMrZhang.factory.BeanFactory;
import io.github.lonelyMrZhang.service.IAccountService;
import io.github.lonelyMrZhang.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* @description: 模拟表现层,调用业务层
* @author: lonely.mr.zhang
* @date: 2020/6/12 12:53 上午
*/
public class Client {

/**
* 获取Spring的IOC核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1、获取核心容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2、根据id获取对象
IAccountService accountService = (IAccountService)ac.getBean("accountService");
IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);

System.out.println(accountService);
System.out.println(accountService);
}
}

ApplicationContext 三个常用实现类之间的区别

IDEA中光标停留在要查看的类上 按下ctrl + h便可查看该类的所有实现类:

ApplicationContext实现类

  • ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在类路径下加载不了。比较常用。
  • FileSystemXmlApplicationContext:可以加载磁盘任意路径下的有权限的配置文件
  • AnnotationConfigApplicationContext:读取注解创建文件。

IOC容器构建的两种方式

  • 懒汉模式
    通过BeanFactory构建Spring容器时,采用延迟加载的策略创建对象,即:什么时候根据id获取对象,什么时候真正的创建对象。适用多例模式

  • 饿汉模式
    通过ApplicationContext构建Spring核心容器时,采用立即加载的策略创建对象,即:只要一读取完配置文件马上就要创建配置文件中配置的对象。适用单例模式

项目GitHub地址

Spring概述

Spring是什么?

Spring是分层的 java SE/EE应用 全栈轻量级开源框架,以IOC(控制反转)和AOP(切面编程)为内核,提供了展现层Spring MVC 和持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的java EE企业应用开源框架。Spring官网

Spring优势

方便解藕、简化开发

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

AOP编程的支持

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

声明式事务的支持

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

方便程序的测试

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

方便集成各种优秀框架

Spring可以降低各种框架的使用难度,提供了对各种优秀框架的直接支持。

降低java EE api的使用难度

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

源码是经典学习的案例

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

Spring体系结构

Spring体系结构图

程序的耦合及解藕

曾经项目中的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.github.lonelyMrZhang;

import java.sql.*;

/**
* @description: 程序耦合事例
* @author: lonely.mr.zhang
* @date: 2020/6/11 8:42 下午
*/
public class JdbcDemo1 {
public static void main(String[] args) throws SQLException {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2、获取连接
Connection con = DriverManager.getConnection("jdbc:mysql://192.168.31.254:3306/coupling_code", "root", "2486");

// 3、获取操作数据库的预处理对象
PreparedStatement preparedStatement = con.prepareStatement("select * from account");

// 4、执行sql,得到结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 5、遍历结果
while (resultSet.next()){
System.out.println(resultSet.getString("name"));
}
// 6、释放资源
resultSet.close();
preparedStatement.close();
con.close();
}
}

项目GitHub地址

JdbcDemo1类依赖com.mysql.jdbc.Driver()类,如果没有Driver的jar包,代码在编译时将会报错。

耦合:程序间的依赖关系,包括类之间的依赖、方法间的依赖

解藕:降低程序间的依赖关系,我们在实际开发中应该做到编译器不依赖,运行期才依赖。

将第一步注册驱动改为下面通过反射的方式注册便可降低耦合

Class.forName("com.mysql.jdbc.Driver");

总结:

1、使用反射来创建对象,避免使用new关键字
2、通过读取配置文件来获取要创建的对象的全限定类名

工厂模式解藕

分析:要解藕的创建对象需要以下两步:

1、需要一个配置文件来配置我们的service和dao,配置文件的内容:唯一标识ID=全限定类名(KV健值对)
2、通过读取配置文件中配置,反射创建对象

在代码中创建BeanFactory类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package io.github.lonelyMrZhang.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
* @description: 创建Bean对象的工厂
* @author: lonely.mr.zhang
* @date: 2020/6/12 4:20 下午
*/
public class BeanFactory {

/**
* 分析:
* Bean: 在计算机英语中有可重用组件的含义
* javaBean:用java语言编写的可重用组件,javaBean > 类
*
* BeanFactory就是用来创建service和dao对象的
*
* 要解藕的创建对象需要以下两步:
* 1、需要一个配置文件来配置我们的service和dao,配置文件的内容:唯一标识ID=全限定类名(KV健值对)
* 2、通过读取配置文件中配置,反射创建对象
*
* 配置文件可以是xml也可以是properties
*/

private static Properties properties;
//初始化配置文件对象
static {
try {
properties = new Properties();
//避免使用 new File()的方式,因为文件路径不好控制
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化properties失败!");
}
}

/**
* 根据beanName获取Bean对象
*
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = properties.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}

}

在需要创建对象的地方通过BeanFactory工厂创建,如下所示:

1
2
//        IAccountService accountService = new AccountServiceImpl();
IAccountService accountService = (IAccountService) BeanFactory.getBean("accountService");

通过以上方式就可降低代码间的耦合程度,当AccountServiceImpl实现类不存在时,只会产生运行时异常,而不会在编译器就出错。

上述方案的Bean工厂获取对象时每次都会创建一个新的实例,而在我们平时的开发过程中,service、dao对象都为单例模式模式,所以我们可以将BeanFactory设计为单例模式从而进一步优化。见链接代码。

项目GitHub地址

IOC的概念

IOC(控制反转)把创建对象的权利交给框架,是框架的重要特征,包括依赖注入(DI)和依赖查找(Dependency Lookup),IOC的作用就是削减程序中的耦合。