Spring-2

DI 依赖注入

所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入

IoC 实际上有 2 种实现 DL 依赖查找和 DI 依赖注入

依赖注入通常有三种方法:接口注入、设置注入和构造器注入。Spring 种实际上提供支持的是设置器注入和构造器注入

接口注入 doGet(HttpServletRequest request,HttpServletResponse response)

设置器注就是通过调用受管 bean 所提供的 setXXX 方法注入依赖对象

<bean id="userService" class="com.yan2.biz.UserServImpl">
    <property name="userDao" ref="userDao"/> 如果 userDao 属性为引用类型则
使用 ref 传入两外一个受管 bean;如果 userDao 为 8 种简单类型机器包装类
或者 String 类型,可以使用 value 直接赋值
bean>

要求 UserServImpl 类中针对 userDao 属性必须有对应的 set 方法,否则报错

变种:引入 p 名空间可以简化配置写法,p 命名空间没有 schemalocation


<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:p="http://www.springframework.org/schema/p"
     xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="userDao" class="com.yan2.dao.UserDaoImpl"/>
    <bean id="userService" class="com.yan2.biz.UserServImpl" 
p:userDao-ref="userDao"/> 语法规则为 p:属性名称=值,如果引用类型则为 p:
属性名称-ref=受管 bean 的名称
beans>

构造器注入

设置器主要要求必须有无参构造器,如果没有对应的无参构造器则只能使用构造器注入。依赖对象是通过带参

构造器完成注入

public class UserServImpl {
    private IUserDao userDao;
    public void create(){
        System.out.println("业务处理");
        userDao.save();
    }
    public UserServImpl(IUserDao userDao){ 由于没有无参构造器则不能使用设置器注入
        this.userDao=userDao;
    }
}
<bean id="userService" class="com.ma3.biz.UserServImpl">
    <constructor-arg ref="userDao"/>
bean>

Spring-2_第1张图片

如果参数是简单类型或包装类或 String 类型,可以使用 value 进行赋值;如果是其它引用类型则需要使用 ref指定其它受管 bean。如果有二义性默认是按照配置顺序进行赋值,如果修改默认则需要通过 name 指定对应的参数名称,或者使用 index 指定对应的序号,或者使用 type 指定对应的类型

总结

在构造期即创建一个完整、合法的对象,对于这条 Java 设计原则,Type3 无疑是最好的响应者

1、避免了繁琐的 setter 方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,更加易读

2、由于没有 setter 方法,依赖关系在构造时由容器一次性设定,因此组件在被创建之后即处于相对“不变”的稳定状态

3、同样由于关联关系仅在构造函数中表达,组件中的依赖关系处于黑盒之中。对上层屏蔽不必要的信息

4、通过构造子注入,意味着可以在构造函数中决定依赖关系的注入顺序但是实际开发中使用设置器注入较多,因为清晰方便直接

自动装配

装配就是 Spring 在 Bean 与 Bean 之间建立依赖关系的行为。

自动装配功能可以让 Spring 容器依据某种规则,为指定的 Bean 从应用的上下文中查找它所依赖的 Bean,并自动建立 Bean 之间的依赖关系。而这一过程是在完全不使用任何元素的情况下进行的。

Spring 框架式默认不支持自动装配的,要想使用自动装配,则需要对 Spring XML 配置文件中元素的autowire 属性进行设置。

<bean id="userDao" class="com.yan4.dao.UserDaoImpl"/>
<bean id="userService" class="com.yan4.biz.UserServImpl" autowire="byName"/>

Spring 共提供 5 种自动装配规则

1、byName 按名称自动装配。根据的 Java 类中对象属性的名称,在整个应用的上下文中查找。若某个 Bean的 id 或 name 属性值与这个对象属性的名称相同,则获取这个 Bean,并与当前的 Java 类 Bean 建立关联关系。

2、byType 按类型自动装配。根据 Java 类中的对象属性的类型,在整个应用的上下文中查找。若某个 Bean 的class 属性值与这个对象属性的类型相匹配,则获取这个 Bean,并与当前的 Java 类的 Bean 建立关联关系。

3、constructor 与 byType 模式相似,不同之处在与它应用于构造器参数,如果在容器中没有找到与构造器参数类型一致的 Bean,那么将抛出异常。

4、default 表示默认采用上一级元素设置的自动装配规则 default-autowire 进行装配。

5、no 默认值,表示不使用自动装配,Bean 的依赖关系必须通过元素的 ref属性来定义。

@Autowired 注解

除了 bean 配置文件中提供的自动装配模式之外,还可以使用@Autowired 注解在 bean 类中指定自动装配。要在 bean 类中使用@Autowired 自动注入,必须首先使用以下配置在 spring 应用程序中启用自动注入。

@Component("userService")
public class UserServImpl {
    @Autowired
    private IUserDao userDao;
    public void create(){
        System.out.println("业务处理");
        userDao.save();
    }
}

为了简化配置可以引入 context 命名空间,然后启用注解配置或者使用配置文件中的 AutowiredAnnotationBeanPostProcessor bean 定义可以实现相同的目的。

在属性上使用@Autowired 时,等效于在配置文件中通过 byType 自动注入

在 bean 的构造函数上使用@Autowired 时,它也等同于在配置文件中通过 constructor 进行自动装配。

如果有两个或多个相同类类型的 bean 情况下,spring 将无法选择正确的 bean 来注入属性,因此将需要使用@Qualifier 注解来帮助容器。

@Component("userService")
public class UserServImpl {
    @Autowired
    @Qualifier("userDao")
    private IUserDao userDao;
}

Bean 的生命周期 【面试】

1、Spring 启动,查找并加载需要被 Spring 管理的 bean,进行 Bean 的实例化。Spring 扫描 class 得到BeanDefinition,根据得到的 BeanDefinition 去生成 bean,首先根据 class 推断构造方法,根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)

2、Bean 实例化后对将 Bean 的引入和值注入到 Bean 的属性中。填充原始对象中的属性依赖注入,如果原始对象中的某个方法被 AOP 了,那么则需要根据原始对象生成一个代理对象

3、如果 Bean 实现了 BeanNameAware 接口的话,Spring 将 Bean 的 Id 传递给 setBeanName()方法。

4、如果 Bean 实现了 BeanFactoryAware 接口的话,Spring 将调用 setBeanFactory()方法,将 BeanFactory 容器实例传入。

5、如果 Bean 实现了 ApplicationContextAware 接口的话,Spring 将调用 Bean 的 setApplicationContext()方法,将 bean 所在应用上下文引用传入进来。

6、如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的 postProcessBeforeInitialization()方法。

7、如果 Bean 实现了 InitializingBean 接口,Spring 将调用他们的 afterPropertiesSet()方法。类似的,如果 bean使用 init-method 声明了初始化方法,该方法也会被调用

8、如果 Bean 实现了 BeanPostProcessor 接口,Spring 就将调用他们的 postProcessAfterInitialization()方法。

9、此时 Bean 已经准备就绪,可以被应用程序使用了。把最终生成的代理对象放入单例池,源码中叫做singletonObjects 中,下次 getBean 时就直接从单例池拿即可。

10、受管 Bean 将一直驻留在应用上下文中,直到应用上下文被销毁。如果 bean 实现了 DisposableBean 接口,Spring 将调用它的 destory()接口方法,同样,如果 bean 使用了 destory-method 声明销毁方法,该方法也会被调用。

Spring-2_第2张图片

@Lazy 注解用于描述类,其目的是告诉 spring 框架此类支持延迟加载

@PostConstruct 注解用于描述 bean 对象生命周期方法中的初始化方法,此方法会在对象的构造方法之后执行

@PreDestroy 注解用于描述 Bean 对象生命周期方法中的销毁方法,此方法会在对象销毁之前执行

配置:

<bean id="userService" class="com.ma4.biz.UserServImpl" init-method="aaa 
初始化完成后立即执行的方法,该方法在整个 bean 的生命周期中运行且只运行一次" 
destroy-method="bbb 在对象销毁前需要执行的方法"/>

循环依赖问题【高频面试】

循环依赖分为三种,自身依赖于自身、互相循环依赖、多组循环依赖。但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。所以 Spring 提供了除了构造函数注入和原型注入外的,setter 循环依赖注入解决方案。

1、构造器的循环依赖:这种依赖 spring 是处理不了的,直接抛出BeanCurrentlylnCreationException 异常。

2、单例模式下的 setter 循环依赖:通过三级缓存处理循环依赖,能处理。

3、非单例循环依赖:无法处理。原型 Prototype 的场景是不支持循环依赖的,通常会走到 AbstractBeanFactory类中下面的判断,抛出异常。

Spring 的 DefaultSingletonBeanRegistry 类中内部维护了三个 Map,也就是通常说的三级缓存

1、singletonObjects 一级缓存是单例池容器,缓存创建完成单例 Bean 的地方。

2、earlySingletonObjects 二级缓存映射 Bean 的早期引用,也就是说在这个 Map 里的 Bean 不是完整的,甚至还不能称之为 Bean,只是一个 Instance.

3、singletonFactories 三级缓存映射创建 Bean 的原始工厂事实上二级缓存就能解决循环依赖,三级缓存主要是解决 Spring AOP 的特性。

三级缓存和二级缓存的区别

二级缓存只需要存储 beanName 和提前暴露的 bean 的实例的映射关系即可;三级缓存不仅需要提前暴露的bean 进行返回,还要对该 bean 做 BeanPostProcessor 后置处理;三级缓存将暴露的 bean 处理完之后,将暴露的 bean 转移到二级缓存,同时删除三级缓存的数据;三级缓存才是解决循环依赖的根本。

Spring 是如何通过三级缓存来解决问题的

对于单例对象来说,在 Spring 的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在 Cache 中,Spring 大量运用了 Cache 的手段,在循环依赖问题的解决过程中甚至使用了三级缓存。

singletonObjects 指单例对象的 cache,singletonFactories 指单例对象工厂的 cache,earlySingletonObjects 指提前曝光的单例对象的 cache。以上三个 cache 构成了三级缓存,Spring 就用这三级缓存巧妙的解决了循环依赖问题。

Spring-2_第3张图片

执行流程:A 对象依赖了 B 对象,B 对象依赖了 A 对象。

1、getBean(A)先去单例池获取,如果单例池不存在则到二级缓存获取,二级缓存不存在再到三级缓存中取,此时返回为空,开始加载 A

2、singletonsCurrentlyInCreation(A)将 A 放入正在创建的 Map 中

3、new A(); 实例化 A

4、提前暴露 A,将 A 放入三级缓存,addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName,mbd, bean));

5、设置属性 populateBean(beanName, mbd, instanceWrapper);

6、发现 A 依赖 B,需要先创建 B

7、getBean(B)

8、先去单例池获取 B,单例池不存在,二级缓存获取,二级缓存不存在则三级缓存中取,此时返回为空,开始加载 B

9、将 B 放入 singletonsCurrentlyInCreation()的 Map 中

10、new B()实例化 B

11、将 B 放入三级缓存 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd,bean));

12、设置属性 populateBean(beanName, mbd, instanceWrapper);

13、发现 B 依赖 A

14、getBean(A)

15、发现三级缓存中存在 A,getEarlyBeanReference(A, mbd, bean)获取 A,同时把 A 放入二级缓存,删除三级缓存

16、执行 B 的 initializeBean 方法,执行 aop,获取增强以后的引用

17、B 创建完了,将 B 放入单例池冲

18、继续执行第 7 步,返回的 getBean(B)就是创建好的 B

19、接下来 A 初始化

20、因为 A 的三级缓存中的 getEarlyBeanReference(beanName, mbd, bean) 被 B 已经执行过了

21、A 就能从二级缓存中获取自己的引用

22、如果发现引用变了,此时 A 就指向二级缓存中的引用

23、将 A 放出单例池中

24、删除二级缓存和三级缓存

整合Spring和Servlet

DAO 使用 JDBC

通过模板类进行整合,模板类由 Spring 框架提供,只需进行配置即可

1、依赖:spring-jdbc 和连接池 druid、数据库驱动 mysql-connect-java

2、引入了 IoC、DI 后对象的创建完全交给 Spring 负责,只需要进行配置即可,所以在 Spring 的核心配置文件 applicationContext.xml 中进行配置


<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="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    bean>
beans>

3、定义 DAO 接口和对应的实现类,如果有必要还需要定义对应的实体类

public interface IUserDao {
    public int insert(User user);
}
public class UserDaoImpl implements IUserDao{
    private JdbcTemplate jdbcTemplate; //需要添加对应的 set 方法
    @Override
    public int insert(User user) {
        return jdbcTemplate.update("insert into tb_users(username,password)
values(?,?)",user.getUsername(),user.getPassword());
    }
}

4、配置 DAO

<bean id="userDao" class="com.ma.dao.UserDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
bean>

5、测试

public class Test1 {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserDao userDao = ac.getBean("userDao", IUserDao.class);
        User user=new User();
        user.setUsername("zhangsan");
        user.setPassword("123456");
        int res=userDao.insert(user);
        System.out.println(res);
    }
}

Spring 整合 Servlet 问题就是在 Servlet 中如何访问受管 bean?

1、添加依赖:spring-web 和 servlet-api,由于服务器中一般包含了 servet-api 相关的 jar 包,所以这里的依赖的 scope 应该设置为 provider,这个 servlet-api 依赖不会打包到应用中

Spring-2_第4张图片

2、在 src/main 目录下创建 webapp/WEB-INF 文件夹,添加 web 应用的核心配置文件

web.xml

在应用启动时需要通过 xxx 加载 spring 的配置文件,并创建对应的 IoC/DI 容器,再将容器存储到 application对象中,供所有用户共享使用


<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_4_0.xsd" version="4.0"                >
    
    <context-param> 上下文参数,供 ContextLoaderListener 使用
        <param-name>contextConfigLocationparam-name>
        <param-value>classpath:applicationContext.xmlparam-value>
    context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
    listener>
web-app>

在 web 应用中使用 WebApplicationContext 引用 IoC 容器,WebApplicationContext 接口是 ApplicationContext接口的子接口,主要添加了针对 web 应用的相关方法

3、定义控制器测试

public class MyController extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取 applicationContext 对象    
        ApplicationContext ac= 
WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());通过工具类获取
IoC 容器的引用,参数为 application 对象,获取 application 对象的方法由 HttpServlet 父类提供
        Date now=ac.getBean("now", Date.class);
        System.out.println(now);
    }
}

针对 servlet 配置

<servlet>
    <servlet-name>testservlet-name>
    <servlet-class>com.ma.action.MyControllerservlet-class>
servlet>
<servlet-mapping>
    <servlet-name>testservlet-name>
    <url-pattern>/test.dourl-pattern>
servlet-mapping>

web 应用组件

1、Servlet 通过映射的 url 地址的访问触发执行,或者页面跳转触发执行。3 生命周期方法 init-service-destroy

2、Filter 通过映射的 url 地址的访问触发执行,就是当访问某个地址时,会在执行目标程序之前或者之后进行一些额外的处理,当然也可以生成响应替代目标访问结果。实际上就是一种 AOP 编程

3、Listener 通过特定事件触发执行

监听 request 对象:

监听创建和销毁 ServletRequestListener

监听针对 request 的 attribute 的增删改操作 ServletRequestAttributeListener

监听 session 对象

创建和销毁 HttpSessionListener

针对 session 的 attribute 的增删改操作 HttpSessionAttributeListener

针对 session 的激活钝化操作 HttpSessionActivationListener

针对 bean 的绑定操作 HttpSessionBindingListener(×)

监听 application 对象

创建和销毁 ServletContextListener

针对 application 的 attribute 的增删改操作 ServletContextAttributeListener

Maven说明

Maven 是基于项目对象模型 POM 的自动化构建工具、依赖管理工具和项目信息管理工具,以通过一小段描述信息来管理项目的构建、报告和文档,可以简化和标准化项目建设过程,自动化处理编译、打包、文档、团队协作,并能够和其他任务的无缝连接

GAV 坐标

GroupID 是项目组织唯一的标识符,实际对应 JAVA 的包的结构

ArtifactID 就是项目的唯一的标识符,实际对应项目的名称,就是项目根目录的名称

Version 是项目的版本号:版本号定义约定:<主版本>.<次版本>.<增量版本>-<里程碑版本>

主版本:表示了项目的重大架构变更 struts1 – struts2

次版本:表示较大范围的功能增加和变化 Nexus1.5 ---- Nexus1.4

增量版本:一般表示重大 Bug 修复

IDE 相关目录

Maven 讲求约定大于配置:项目约定主要是规范开发人员编程,统一项目风格,简化操作和代码量。

src/main/java - 存放项目.java 文件;

src/main/resources - 存放项目资源文件;

src/test/java - 存放测试类.java 文件;

src/test/resources - 存放测试资源文件;

target - 项目输出目录;

pom.xml - Maven 核心文件(Project Object Model)

打包方式

Spring-2_第5张图片

war 包表示 web 应用资源,jar 包表示 java 应用资源。所谓的打包实际上就是将相关的文件添加到一个压缩文件中,不同类型的包实际上就是包含不同的内容,例如 web 包是对应 web 应用,jar 对应的是的 java 应用maven 项目常用的有三种 scope(编译 compile 默认,测试 test,运行 runtime)

Spring-2_第6张图片

scope 用来表示与 classpath 的关系,总共有六种

1、compile: 编译依赖范围。对于编译、测试、运行三种都有效。典型的例子是 spring-core,在编译,测试和运行的时候都需要使用该依赖

2、test:测试依赖范围。只对于测试有效,不会打包部署。典型的例子就是 JUnit,它只有在编译测试代码及运行测试的时候才需要

3、provided:已提供依赖范围。对于编译和测试有效,但在运行时无效,不会打包部署。典型的例子是 servlet-api

4、runtime:运行时依赖范围。对于测试和运行都有效,但在编译主代码时无效,能够打包部署。典型的例子是 JDBC 驱动实现

5、system 范围依赖与 provided 类似,但是你必须显式的提供一个对于本地系统中 JAR 文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库的一部分。这样的构建应该是一直可用的,Maven 也不会在仓库中去寻找它。如果你将一个依赖范围设置成系统范围,你必须同时提供一个 systemPath 元素。

注意该范围是不推荐使用的(建议尽量去从公共或定制的 Maven 仓库中引用依赖)

6、import 导入。import 仅支持在中的类型依赖项上。它表示要在指定的 POM部分中用有效的依赖关系列表替换的依赖关系。该 scope 类型的依赖项实际上不会参与限制依赖项的可传递性

Maven仓库

maven 的仓库只有两大类:本地仓库和远程仓库,在远程仓库中又分成了 3 种中央仓库、私服和 其它公共库

Maven 的本地仓库

本地仓库,顾名思义,就是 Maven 在本地存储构件的地方。maven 的本地仓库,在安装 maven 后并不会创建,它是在第一次执行 maven 命令的时候才被创建。

maven 本地仓库的默认位置:无论是 Windows 还是 Linux,在用户的目录下都有一个.m2/repository/的仓库目录,这就是 Maven 仓库的默认位置【windows 下一般在 c 盘的”用户 User”目录下的”登录用户名”目录下】

更改本地仓库目录只需在自行安装的 Maven 工具的 conf/settings.xml 文件中 setting 节点下增加一行即可

D:\ma\repository

远程仓库

maven 的远程仓库有多种存在形式,中央仓库,其他远程仓库,镜像,私服

中央仓库

中 央 仓 库 是 默 认 的 远 程 仓 库 , 如 果 不 做 任 何 特 殊 配 置 那 么 将 会 从 中 央 仓 库 下 载 依 赖 , 这 在

$M2_HOME/lib/maven-model-builder-3.0.4.jar 里的 org/apache/maven/model/pom-4.0.0.xml 里做了指定

依赖冲突的解决方案

依赖依赖的基本规则:传递路径长度取最短原则,传递路径长度相等时,采取最先申明原则

如果发现依赖包不能达到目录,可以排除依赖,显式的申明

<dependency>
    <groupId>org.springframeworkgroupId>
    <artifactId>spring-context-supportartifactId>
    <version>5.3.27version>
    <exclusions>
        <exclusion>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-coreartifactId>
        exclusion>
    exclusions>
dependency>

你可能感兴趣的:(spring,java,servlet)