Spring 春天

一 Spring简介

1.1 为什么需要spring?

在软件设计中 ,有一种模式叫做 MVC 。称之为 Model 模型 View 视图 Controller 控制器。

一般spring用的latex模板_java

 

但是我们的javaweb项目并没有完全实现mvc中所有的内容。因为我们没有事件驱动。

而javaweb在发展的时候 ,其实为玩 Model 1 、Model 1 二代 、Model 2

Model1 时期整个请求的流程:

一般spring用的latex模板_spring_02

 

Model 1 二代时期整个请求的流程:

一般spring用的latex模板_java_03

 

Model2 时期整个请求的流程:

一般spring用的latex模板_java_04

 

servlet负责 获取请求、处理业务、调用DAO、共享数据、选择视图

JAVAWeb由Model2 演变成 三层架构 :

一般spring用的latex模板_依赖注入_05

 

在原始的三层架构中 ,我们都是使用正转进行操作的,例如:

一般spring用的latex模板_依赖注入_06

 

当我们需要使用某一个类的对象时,直接通过new去创建该类的对象,这种操作就称之为 正转操作。

但是正转操作是一种高耦合(多个类之间关联比较密切,会出现如果一个类修改,会导致一系列的类都需要进行修改)的表现。以前java代码都是比较笨重的,称为诟病的就是因为都在进行正转操作。

此时我们试想一下,如果StudentServiceImpl这个类升级了,换成了StudentServiceImpl2,此时我们需要去所有使用StudentServiceImpl的地方将其换成StudentServiceImpl2。

 

所以正转操作不是很好,我们可以使用 工厂模式 来解决这个问题。

例如: People Man Women 都使用到了 Food这个类

我们如果直接正转操作使用 food 此时将来如果food变成food2 此时改起来不好改,所以我们使用简单工厂模式。

一般spring用的latex模板_xml_07

 

还有一个核心问题没有解决,当我们将工厂中的food换成food2的时候 ,此时并没有实现一个效果,所有的类都改完成, 而是全部报错

一般spring用的latex模板_依赖注入_08

 

原因在于 工厂此时返回值为 Food2 而我们接受的类型为Food,此时类型不匹配。这个问题怎么解决?

可以创建一个接口,我们使用接口类型去接收实现类对象-----接口多态。

一般spring用的latex模板_依赖注入_09

 

我们就解决了正转的问题;创建对象的时候不再是直接new对象 ----控制正转

而是将创建对象的操作放到工厂中,需要使用对象的时候去工厂中获取----控制反转 IoC

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。使用对象的时候不再是直接new,而是将创建对象的权利交给框架中的核心容器,需要使用对象的时候直接从容器中获取。

 

IoC的思想早就出现了,但是没有一个具体的实现,大家都是各自根据这个思想去写代码(自己去创建工厂)。

后来有一个大师 Rod Johnson(Spring Framework创始人,著名作者。 Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位。),写了一个框架,将IoC思想具体的实现了,这个框架后来起了个名字叫做 Spring。(因为在这之前 大家都是用hibernate这个框架,这个框架中文名字叫做冬眠,并且这个框架比较笨重,耦合比较高。Rod Johnson写的框架就是用来解决类似于hibernate高耦合的问题 ,所以他将自己框架的名字命名为 Spring 告诉全世界 冬天过去了 春天来了)

 

1.2 spring产品

spring的官方网站:https://spring.io/

我们可以看到spring有很多产品 spring已经变成了一个生态圈。但是我们平时还是需要将spring理解成Spring Framework

一般spring用的latex模板_java_10

 

我们看一下spring-framework组成

一般spring用的latex模板_依赖注入_11

 

Test: spring自带的单元测试

Core Container: 核心容器。这是IoC思想的关键,Spring负责创建对象 并且存放到 核心容器中。

AOP:面向切面编程

Web:与javaweb相关的组件

Data Access : 与数据库操作/JDBC操作相关组件

1.3 spring项目的构建

A 创建maven工程

B 在maven工程中添加spring的pom依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.0.RELEASE</version>
</dependency>

C src/main下创建一个文件夹 sources 并且将文件夹的格式改成resources root 。在里面添加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
        https://www.springframework.org/schema/beans/spring-beans.xsd">



</beans>

D 创建一个测试类 测试spring的使用。

 

 

二 Spring的IoC操作

2.1 传统的正转操作

@Test
public  void test1() throws Exception{
      People people = new People();
      people.eat();
  }public class People {
     public  void  eat(){
         System.out.println("人类都要吃饭");
     }
 }

2.2 通过spring核心容器获取

A 在xml中配置当前类的信息(告诉spring 有哪些类的对象 需要spring核心容器去管理)

<bean   id="p1"  class="com.aaa.test.People"></bean>

B 使用spring的工厂去获取对应的bean 。 BeanFactory

@Test
public  void test1(){

     ClassPathResource resource = new ClassPathResource("application.xml");
     BeanFactory factory = new XmlBeanFactory(resource);

     People p1 = (People) factory.getBean("p1");
     p1.eat();

 }

原理思考:

一般spring用的latex模板_spring_12

 

2.3 ApplicationContext获取bean

@Test
public void test2() {
     ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
     People p1 = (People) context.getBean("p1");
     p1.eat();
 }

面试题: ApplicationContext 和 BeanFactory的区别?

BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;

ApplicationContext:应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;

1) 国际化(MessageSource)

2) 访问资源,如URL和文件(ResourceLoader)

3) 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层

4) 消息发送、响应机制(ApplicationEventPublisher)

5) AOP

并且二者之间对bean的初始化时机(创建对象的时机)是不一样的。

BeanFactory是遵循懒加载模式创建对象,也就是说BeanFactory被创建的时候并没有初始化bean,只有getBean的时候才去创建对象。

ApplicationContext是遵循饿汉模式创建对象,因为ApplicationContext对象被创建的时候,同时会初始化bean。

我们可以通过lazy-init设置成懒加载模式。

 

2.4 getBean()的三种方式

A 通过 id 或者 name的值获取bean

Object p1 = context.getBean("p1");

B 通过类型去获取

People bean = context.getBean(People.class);

确保bean的类型是唯一的 不然会报错:org.springframework.beans.factory.NoUniqueBeanDefinitionException

C 通过 id + 类型去获取

People bean = context.getBean("p1",People.class);

2.5 bean的属性配置

id : 当前bean的唯一标识符

class:配置类的全限定名

name:配置当前bean的名字,并且可以配置多个。以前使用的比较多,在spring3之前 name可以使用符号例如 : /stu 而id不可以。

 后来id也可以添加符号例如/ 所以通常使用id比较多。

lazy-init : 配置bean的初始化时机 是否是懒加载。设置成true代表懒加载。还可以全局设定在beans标签中设定default-lazy-init="true"

scope:作用域 默认是 singleton单例的 prototype多例的 request请求 session会话 global session 全局会话

ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

 People bean1 = context.getBean("p1",People.class);
 People bean2 = context.getBean("p1",People.class);

 System.out.println(bean1 == bean2);//true

有的类 对象被创建的时候需要调用初始化函数,对象被回收的时候需要调用销毁函数。现在对象时由spring核心容器去管理的。

所以这些函数的调用应该由spring负责。我们只需要通过配置告诉spring初始化函数是谁,销毁函数是谁。bean的生命周期函数

init-method:
destroy-method:
<bean id="p1" name="aa  bb  cc"  class="com.aaa.test.People" lazy-init="true"  scope="singleton" init-method="hehe" destroy-method="haha"></bean>
@Test
public void test2() {
     ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
     People bean1 = context.getBean("p1",People.class);
     context.close();
 }

注意,此时scope不能是多例的 否则销毁函数不会调用

作业:A 理论记住 B 测试:创建工程 添加依赖 创建配置文件 配置bean 通过ApplicationContext完成调用

 

三 DI依赖注入

3.1 对象关系----依赖关系

面向对象设计 对象间关系:依赖、关联、聚合和组合。

关联关系: 例如 人类和电脑类

某个对象会长期的持有另一个对象的引用,而二者的关联往往也是相互的。

public class People {

         int   id;
         String  name;
         String  nickname;
         String  address;

         Computer   c;

 }public class Computer {
     int  id;
     String  name;
     String  cpu;
 }@Test
public  void  test1(){
     People people = new People();
     people.id = 20;
     people.name = "张三0";
     
     people.c.name = "外星人";
 }

依赖关系: Servlet 和service

所谓依赖就是某个对象的功能依赖于另外的某个对象,而被依赖的对象只是作为一种工具在使用,而并不持有对它的引用。

public class StudentServlet {

     StudentService  service;
     
     
 }

3.2 依赖注入---DI Dependency Injection

当某个角色(可能是一个Java实例,调用者)需要另一个角色(另一个Java实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在Spring里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者 实例的工作通常由Spring容器来完成,然后注入调用者,因此也称为依赖注入。

依赖注入有两种:设值注入、构造注入

所谓依赖注入指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO之间依赖关系的管理。

 

在我们开发中会出现对象依赖的情况 ,例如 StudentServlet 依赖于 StudentService。在以前的时候我们都是直接在Servlet中new业务层Service对象,这是一个正转操作,会有高耦合。我们使用Spring框架,要遵循IoC,让对象的创建都交给Spring的核心容器。此时所有的对象都是Spring管理,也就是说 StudentServlet 是Spring 管理的,StudentService也是Spring管理的。

一般spring用的latex模板_xml_13

 

通过依赖注入 我们在类中需要使用另一个类的对象时,完全不需要自己创建了,而是让spring去帮我们创建并注入

IoC是一种思想,在在Spring中 DI 是这个思想的具体实现方式。

3.3 依赖注入的两种方式

A set方法注入

public class StudentServlet {

     StudentService  haha;

     public void setHaha(StudentService haha) {
         this.haha = haha;
     }


     @Override
     public String toString() {
         return "StudentServlet{" +
                 "haha=" + haha +
                 '}';
     }
 }public class StudentService {


 }xml配置DI依赖注入的关系
<bean  id="servlet"  class="com.aaa.test1.StudentServlet">
     <property name="haha"  ref="hehe"  ></property>
</bean>
<bean id="hehe" class="com.aaa.test1.StudentService"></bean>调用代码:
@Test
public  void  test1(){

     ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

     Object servlet = context.getBean("servlet");

     System.out.println(servlet);

 } 
B 构造函数注入
public class StudentServlet {

     StudentService  haha;

     public StudentServlet(StudentService haha) {
         this.haha = haha;
     }

     @Override
     public String toString() {
         return "StudentServlet{" +
                 "haha=" + haha +
                 '}';
     }
 }<bean  id="servlet"  class="com.aaa.test1.StudentServlet">
     <constructor-arg  name="haha"  ref="hehe" ></constructor-arg>
</bean>

<bean id="hehe" class="com.aaa.test1.StudentService"></bean>3.4 依赖注入的底层思考
反射是框架的灵魂
@Test
public  void test2() throws  Exception{

     /*
    <bean  id="servlet"  class="com.aaa.test1.StudentServlet">
         <property  name="haha"  ref="hehe" ></property>
    </bean>

     <bean id="hehe" class="com.aaa.test1.StudentService"></bean>
     */

     Class<?> aClass = Class.forName("com.aaa.test1.StudentServlet");
     Object o = aClass.newInstance();

     BeanInfo beanInfo = Introspector.getBeanInfo(aClass);

     PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();

     for (PropertyDescriptor p:descriptors) {
         if(p.getName().equals("haha")){

             Method writeMethod = p.getWriteMethod();
             writeMethod.invoke( o , Class.forName("com.aaa.test1.StudentService").newInstance()  );
         }
     }

     System.out.println(o);
 }3.5 注入值的形式
A 注入对象
<bean  id="servlet"  class="com.aaa.test1.StudentServlet">
     <property name="haha"  ref="hehe"  ></property>
</bean>

<bean id="hehe" class="com.aaa.test1.StudentService"></bean>注意 注入对象的时候 使用的是 ref
B 注入简单值
注意 注入对象的时候 使用的是 value
<bean  id="dao"  class="com.aaa.test1.BaseDAO">
     <property name="className" value="com.mysql.jdbc.Driver"></property>
     <property name="url"       value="jdbc:mysql:///test"></property>
     <property name="username"  value="root"></property>
     <property name="password"  value="root"></property>
</bean>public class BaseDAO {


     private  String   className;
     private  String   url;
     private  String   username;
     private  String   password;


     public void setClassName(String className) {
         this.className = className;
     }

     public void setUrl(String url) {
         this.url = url;
     }

     public void setUsername(String username) {
         this.username = username;
     }

     public void setPassword(String password) {
         this.password = password;
     }


     @Override
     public String toString() {
         return "BaseDAO{" +
                 "className='" + className + '\'' +
                 ", url='" + url + '\'' +
                 ", username='" + username + '\'' +
                 ", password='" + password + '\'' +
                 '}';
     }
 }代码调用
@Test
public  void  test3(){

     ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

     Object servlet = context.getBean("dao");

     System.out.println(servlet);

 }C 注入集合框架
public class People {

     List<String>          tels;
     Map<String,Object>    friends;

     public void setTels(List<String> tels) {
         this.tels = tels;
     }

     public void setFriends(Map<String, Object> friends) {
         this.friends = friends;
     }

     @Override
     public String toString() {
         return "People{" +
                 "tels=" + tels+"friends:"+ friends +
                 '}';
     }
 }<bean id="p1" class="com.aaa.test1.People">

     <property name="tels">
         <list>
              <value>15966663333</value>
              <value>15166333333</value>
              <value>18966663333</value>
              <value>15933336666</value>
         </list>
     </property>
     
     <property name="friends">
         <map>
            <entry key="f1" value="张三"></entry>
            <entry key="f2" value="李四"></entry>
            <entry key="f3" value="王五"></entry>
            <entry key="f4" value="赵六"></entry>
         </map>
     </property>

</bean>

 

3.6 自动注入的形式

<bean id="haha" class="com.aaa.test1.StudentServlet">
     <property name="haha"   ref="hehe"></property>
</bean>

<bean id="hehe" class="com.aaa.test1.StudentService"></bean>这是我们以前注入的形式 ,是手动维护类和类之间的依赖关系,此时没有问题。但是不优秀,因为在一个项目中会有很多的类需要关系的维护,我们都是通过 propertity进行手动操作很麻烦,而是配置文件会很庞大。
所以spring提供了一套自动注入的形式
 
xml形式:
public class StudentServlet {

     StudentService  hehe;

     public void setHehe(StudentService hehe) {
         this.hehe = hehe;
     }

     @Override
     public String toString() {
         return "StudentServlet{" +
                 "haha=" + hehe +
                 '}';
     }
 }<bean id="haha" class="com.aaa.test1.StudentServlet" autowire="byName"></bean>

<bean id="hehe" class="com.aaa.test1.StudentService"></bean>autowire自动注入,此时设定的是byName按照名字去注入,此时在servlet中有一个成员变量名字是hehe,所以就会去所有的bean中找id为hehe的bean,找到了则注入。没找到则抛异常。
<bean id="haha" class="com.aaa.test1.StudentServlet" autowire="byType"></bean>

<bean id="heihei" class="com.aaa.test1.StudentService"></bean>autowire自动注入,此时设定的是byType按照类型去注入,此时servlet中成员变量的类型为 com.aaa.test1.StudentService, 所以就会去所有的bean中找class为com.aaa.test1.StudentService,找到了则注入,没有就抛异常,找到多个也抛异常。
 
注解形式:
A 配置当前spring需要管理哪些bean(类)
B 配置哪些依赖需要spring帮忙注入
######
第一步: 开启注解扫描
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd" >


        <!--注解配置-->
        <context:annotation-config></context:annotation-config>
        <!--注解包扫描-->
        <context:component-scan base-package="com.aaa.test1"  ></context:component-scan>

</beans>第二步:通过注解 @Component 配置 哪些类需要spring管理
@Component("haha")
/*<bean  id="haha"  class="com.aaa.test1.StudentServlet" />*/
public class StudentServlet {

     StudentService  hehe;

     @Override
     public String toString() {
         return "StudentServlet{" +
                 "haha=" + hehe +
                 '}';
     }
 }@Component("heihei")
public class StudentService {

 }

第三步:通过注解@AutoWired注解告诉spring需要进行依赖注入

@Autowired
StudentService  hehe;

 

3.7 @Autowired 和 @Resource的注入原则/装配顺序

@Autowired:

当自动注入bean的时候 首先根据类型去匹配

如果一个都匹配不到,则查看是否配置(required = true) 如果没有配置或者配置的是true,就代表必须注入,此时则抛出异常。

如果配置的是false,代表不是必须注入,此时如果找不到没救不注入。(注意:如果设定为false,又没有注入成功 ,在使用当前成员变量的时候 会出现空指针异常。)

如果正常匹配到一个 ,则按照类型正常注入。

如果匹配到多个bean的类型都是同一类型。则根据当前变量的名字匹配对应的bean的名字。如果匹配到 则进行注入。

如果匹配不到 则 抛出异常

如果配置了@Qualifier("heihei") 则根据@Qualifier("heihei")的名字匹配对应的bean

 

@Resource
import javax.annotation.Resource;

是我们JDK自带的一个注解

a. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

b. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

c. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常

d. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配(名字没有一样的),则回退为一个原始类(相当于按照类型取查找一个实现类,如果按照类型取查找的实现类有多个 则抛出异常)

3.8 注解配置的语义化注解

@Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Component
 public @interface Controller {
     String value() default "";
 }

我们需要在控制层添加 @Controller

在业务层添加 @Service

在数据持久层添加@Repository

在其他的地方都是用@Component