spring基础知识-01-C-2020-10-09

日志编号说明
C-2020-10-09第一次创建

框架

框架就是一些类和接口的集合,通过这些类和接口协调来完成一系列的程序实现。JAVA框架可以分为三层:表示层,业务层和物理层。框架又叫做开发中的半成品,它不能提供整个WEB应用程序的所有东西,但是有了框架,我们就可以集中精力进行业务逻辑的开发而不用去关心它的技术实现以及一些辅助的业务逻辑。大家熟知的Structs和Spring就是表示层和业务层框架的强力代表。

java主流框架演变之路

  1. JSP+Servlet+JavaBean
  2. MVC三层框架
  3. 使用EJB进行应用的开发,但是EJB是重量级框架,在使用时产生过多的接口和依赖,侵入性强,使用起来比较麻烦
  4. Struts1/Struts2+Hibernate+Spring
  5. SpringMVC+Spring+Mybatis
  6. Springboot

spring

简单来讲,spring是一个开源框架,为了简化企业开发而生,使得开发工作变得更加优雅和简洁。
spring是一个IOCAOP的容器框架。
IOC:控制反转
AOP:面向切面编程
容器:包含并管理应用对象的生命周期。就像用杯子装水一样,杯子就是spring,水就是对象。

使用spring的优点

  1. Spring通过DI、AOP和肖出样板代码来简化企业级Java开发
  2. Spring框架之外还存在一个构建在心框架之上的庞大生态圈,它将Spring扩展到不同的领域,如Web服务、REST、移动开发以及NoSQL
  3. 低侵入式设计,代码的污染极低
  4. 独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺
  5. Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦
  6. Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用
  7. Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
  8. Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部

此处,引入一张Spring Framework的结构图,这个图不是最新(5.XX)版本的,是4的一个图,最新的图没从官网找到
spring结构图上面的图是Spring组成结构图,这个图有一个统一说明,即每一个绿色底色构成的部分,如AOP,Aspects等,是可以单独拎出来使用的。而类似Core Container,在使用这样的集合体的时候,需要按照实际需求,引入这个集合体内相关的包。此处更推荐通过maven的方式去进行项目构建。

针对图上的内容,进行模块的简易解释

  • Test:spring的单元测试模块
  • Core Container:核心容器模块
  • AOP + Aspects:面向切面编程模块
  • Instrumentation:提供了class instrumentation支持和类加载器的实现来在特定的应用服务器上使用(几乎不用)
  • Messaging:包括一系列的用来映射消息到方法的注解(几乎不用)
  • Data Access/Integration:数据的获取/整合模块,包括了JDBC,ORM,OXM,JMS和事务模块
  • Web:提供面向web整合特性

使用spring为什么能够简化开发

  1. 基于POJO的轻量级和最小侵入性编程
  2. 通过依赖注入和面向接口实现松耦合
  3. 基于切面和惯例进行声明式编程
  4. 通过切面和模板减少样板式代码

IOC(Inversion of Control)控制反转

为什么引入IOC

先看一个例子:
代码结构
上面的代码结构里,dao层定义了接口和实现类。同时service层定义了接口和实现类。实现类代码如下:

package com.phl.noioc.service.impl;

import com.phl.noioc.dao.UserDao;
import com.phl.noioc.dao.impl.UserDaoImpl;
import com.phl.noioc.service.UserService;

public class UserServiceImpl implements UserService {
    UserDao userDao = new UserDaoImpl();
    public void getUserNameById(Integer userId) {
        System.out.println(userDao.getUserNameById(userId));
    }
}

这样的代码能执行,也没有错误。假设现在需要对UserDao这个接口进行多种特殊情况的实例化,会产生UserDaoImpl2,UserDaoImpl3,UserDaoImpl4等多种实现类,为了应对这样的情况,难道需要产生多个UserServiceImpl2,UserServiceImpl3,UserServiceImpl4这样的情况去应对吗?显然不合适,代码耦合度太高了。因此,将上述的UserServiceImpl改成如下情况:

package com.phl.noioc.service.impl;

import com.phl.noioc.dao.UserDao;
import com.phl.noioc.service.UserService;

public class UserServiceImpl implements UserService {
    UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void getUserNameById(Integer userId) {
        System.out.println(userDao.getUserNameById(userId));
    }
}

对于UserDao这个对象,不再在UserServiceImpl中去手动new它,而是set方法进行注入,这样的写法更灵活。

注意,此时这个例子只是针对解耦合而言。并没有涉及到如何通过依赖注入实现具体的过程。

回顾第一个问题,为什么要引入IOC?
在此需要对IOC(控制反转)有一个清晰的认识。

  • 谁控制谁
    在之前的代码示例里,都是程序员要么自己new一个,要么手动传一个。有了IOC容器之后,可以通过配置信息和注解,将手动传入的部分提前配置好,最终由IOC容器来控制对象
  • 控制什么
    被控制的,就是在实现过程中需要的对象和需要依赖的对象
  • 什么是反转
    在有IOC容器之前,我们都是手动创建依赖对象,这是正向的。而有了IOC之后,依赖的对象直接由IOC容器进行创建,之后注入到对象中,这一步从主动创建变成了被动接受,这就是反转
  • 哪些方面被反转了
    依赖的对象被反转了,说白了一个是主动创建并使用,另一个是容器创建并注入
<?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="userDaoImpl1" class="com.phl.noioc.dao.impl.UserDaoImpl" scope="singleton"></bean>
    <bean id="userDaoImpl2" class="com.phl.noioc.dao.impl.UserDaoImpl2" scope="singleton"></bean>
    <bean id="serviceImpl" class="com.phl.noioc.service.impl.UserServiceImpl" scope="singleton">
        <constructor-arg name="userDao" ref="userDaoImpl1"></constructor-arg>
    </bean>
    <bean id="serviceImpl2" class="com.phl.noioc.service.impl.UserServiceImpl" scope="singleton">
        <constructor-arg name="userDao" ref="userDaoImpl2"></constructor-arg>
    </bean>
</beans>

上面给了一个针对这个例子的简单的解决方案,通过IOC容器,定义好了bean对象,以及各个bean之间的引用关系。
下面给出测试代码。

package com.phl.noioc.test;

import com.phl.noioc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    @org.junit.Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext(
                "applicationContext.xml");
        UserService serviceImpl1 = (UserService)context.getBean("serviceImpl");
        UserService serviceImpl2 = (UserService)context.getBean("serviceImpl2");
        serviceImpl1.getUserNameById(1);
        serviceImpl2.getUserNameById(2);
    }
}

针对上述配置中涉及的内容暂且忽略,这个例子是想描述如何通过配置和IOC容器,实现不同bean之间的转载,创建,使用。细看这个测试类的写法,这里面没有任何一个new关键字是用来创建dao或者service的,这也就是IOC的核心体现,通过容器去管理Bean对象。

IOC和DI

这个标题的意思是控制反转和依赖注入。不少人说IOC和DI是同一个东西,笼统来讲没什么大问题,但是这里是有差异的。IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是设计思想,而DI是具体的实现方式

针对上面内容的一个总结

通过上面的spring结构图,帮助我们理解了什么是spring框架,他各个模块之间,模块内部又应该如何组合地去使用。同时,通过一个代码示例展示了IOC在解耦合上的便捷。最后,指出了IOC和DI的区别。

IOC基本使用

虽然现在都在使用注解,但是出于学习的目的,下面基础使用的例子采用了配置文件的形式。

复杂属性赋值

package com.phl.spring.csdn.bean;

import java.util.*;

public class UserBean {

    private Integer id;
    private Integer age;
    private String gender;
    private String name;
    //人物关系
    private Map<String,Object> relations;
    //最喜欢的电影名称
    private List<String> favMovies;
    //最喜欢的食物
    private Set<String> favFoods;
    //最喜欢的颜色
    private String[] favColors;
    //账号密码
    private Properties prop;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

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

    public Map<String, Object> getRelations() {
        return relations;
    }

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

    public List<String> getFavMovies() {
        return favMovies;
    }

    public void setFavMovies(List<String> favMovies) {
        this.favMovies = favMovies;
    }

    public Set<String> getFavFoods() {
        return favFoods;
    }

    public void setFavFoods(Set<String> favFoods) {
        this.favFoods = favFoods;
    }

    public String[] getFavColors() {
        return favColors;
    }

    public void setFavColors(String[] favColors) {
        this.favColors = favColors;
    }

    public Properties getProp() {
        return prop;
    }

    public void setProp(Properties prop) {
        this.prop = prop;
    }

    @Override
    public String toString() {
        return "UserBean{" +
                "id=" + id +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", name='" + name + '\'' +
                ", relations=" + relations +
                ", favMovies=" + favMovies +
                ", favFoods=" + favFoods +
                ", favColors=" + Arrays.toString(favColors) +
                ", prop=" + prop +
                '}';
    }

    public UserBean(Integer id, Integer age, String gender, String name, Map<String, Object> relations, List<String> favMovies, Set<String> favFoods, String[] favColors, Properties prop) {
        this.id = id;
        this.age = age;
        this.gender = gender;
        this.name = name;
        this.relations = relations;
        this.favMovies = favMovies;
        this.favFoods = favFoods;
        this.favColors = favColors;
        this.prop = prop;
    }

    public UserBean() {
    }
}

上面给出了一个Java类,这个类很简单,里面是各种数据类型的属性,以及对应的getset,构造方法,toString方法。
首先声明一下,很多初学者不明白的是,IOC在进行装配时,对象的属性是由setter/getter方法决定的,而不是定义的成员属性。

<?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="father" class="com.phl.spring.csdn.bean.UserBean">
        <!--普通属性赋值-->
        <property name="id" value="751"></property>
        <property name="age" value="42"></property>
        <property name="gender" value="male"></property>
        <property name="name" value="张二"></property>
    </bean>
    <bean id="user" class="com.phl.spring.csdn.bean.UserBean">
        <!--普通属性赋值-->
        <property name="id" value="951"></property>
        <property name="age" value="12"></property>
        <property name="gender" value="male"></property>
        <property name="name" value="张三"></property>
        <!--数组赋值-->
        <property name="favColors">
            <array>
                <value>red</value>
                <value>blue</value>
            </array>
        </property>
        <!--Set赋值-->
        <property name="favFoods">
            <set>
                <value>noodle</value>
            </set>
        </property>
        <!--List赋值-->
        <property name="favMovies">
            <list>
                <value>这个杀手不太冷</value>
            </list>
        </property>
        <!--Properties赋值-->
        <property name="prop">
            <props>
                <prop key="百度密码">123!123</prop>
            </props>
        </property>
        <!--Map赋值-->
        <property name="relations">
            <map>
            	<!--引用了另一个Bean对象-->
                <entry key="父亲" value-ref="father"></entry>
            </map>
        </property>
    </bean>
</beans>

首先可以看到,这个Bean与第一个例子中的Bean不同,这个不是通过构造方法给Bean对象赋值,而是通过属性进行赋值。
上面例子里列举了对不同类型对象进行赋值或者引用。不论是对数组,list,set,map,properties中,都可以有value(直接赋值),ref或者value-ref(引用一个对象)。

类的继承

有两个类,一个是Animal,另一个是Dog,Dog继承自Animal

package com.phl.spring.csdn.bean;

public class Animal {
}
package com.phl.spring.csdn.bean;

public class Dog extends Animal {
}

两个空类,这里注意一个细节,Animal类并不是抽象类。但是,在IOC中可以这样配置。

<bean id="animal" class="com.phl.spring.csdn.bean.Animal" abstract="true"></bean>
<bean id="dog" class="com.phl.spring.csdn.bean.Dog" parent="animal"></bean>

类的继承,通过parent进行描述。这个例子中dog是子类,所以他通过parent指明了父类。
在父类上需要留意一个属性就是abstract,这个属性是告诉IOC容器,是否对这个类进行实例化操作。与Animal类本身是否是抽象类无关。

注,这篇博客中并没有面向纯小白的意思,因此对于XML中配置的具体详情并没有详细介绍,只是通过DEMO来熟悉一下这种感觉。

通过其他配置文件装配对象

在Spring IOC容器中,经常会出现通过另一个配置文件中的值来装配对象信息,常见的就是数据库链接。一般我们都是通过一个专门的db.properties文件进行数据库信息的配置。之后再把这个配置文件引入到spring的配置文件中。这个过程,就是通过其他配置文件装配对象。
在做这里例子之前,需要先引入spring jdbc和mysql的依赖到POM文件中。
引入完成之后,再在项目的resource目录中创建db.properties配置信息,配置文件内容如下:
db.properties
创建好外部资源文件之后,需要引入到spring配置信息中。
这个过程里,在IDEA开发工具下,针对XML的命名空间信息会随着标签的编写自动添加,在eclipse的话,很久没用,具体会不会自动添加我想不起来了。如果不会自动添加,那第一步就是手动添加命名空间。

<?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 
       https://www.springframework.org/schema/context/spring-context.xsd">
    <!--这个context就是新引入的命名空间-->
    <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
    <bean id="userDaoImpl1" class="com.phl.noioc.dao.impl.UserDaoImpl" scope="singleton"></bean>
    <bean id="userDaoImpl2" class="com.phl.noioc.dao.impl.UserDaoImpl2" scope="singleton"></bean>
    <bean id="serviceImpl" class="com.phl.noioc.service.impl.UserServiceImpl" scope="singleton">
        <constructor-arg name="userDao" ref="userDaoImpl1"></constructor-arg>
    </bean>
    <bean id="serviceImpl2" class="com.phl.noioc.service.impl.UserServiceImpl" scope="singleton">
        <constructor-arg name="userDao" ref="userDaoImpl2"></constructor-arg>
    </bean>
    <bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="url" value="${url}"></property>
        <property name="driverClassName" value="${driver}"></property>
    </bean>
</beans>

引入命名空间之后,先通过property-placeholder指明外部配置文件信息的地址,之后在实际传入值的地方通过${“属性名”}这样的形式进行值传递。

自动装配

当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置。自动装配的配置就是在Bean标签里的autowire属性。
属性的配置的方式有以下几种:

  • default/no:不自动装配
  • byName:按照名字进行装配,以属性名作为id去容器中查找组件,进行赋值,如果找不到则装配null
  • byType:按照类型进行装配,以属性的类型作为查找依据去容器中找到这个组件,如果有多个类型相同的bean对象,那么会报异常,如果找不到则装配null
  • constructor:按照构造器进行装配,先按照有参构造器参数的类型进行装配,没有就直接装配null;如果按照类型找到了多个,那么就使用参数名作为id继续匹配,找到就装配,找不到就装配null。

采用自动装配之后,就无需编写那些冗长的bean信息,而是把这个过程交给IOC容器去完成即可。

注解

上面的老黄历也回忆完了,现在来说注解。
spring中包含了4个主要的组件添加注解,分别是:

  • @Controller:控制器,推荐给controller层添加此注解
  • @Service:业务逻辑,推荐给业务逻辑层添加此注解
  • @Repository:仓库管理,推荐给数据访问层添加此注解
  • @Component:给不属于以上基层的组件添加此注解

进行一个说明,上面这四个名字和分类,其实是spring为了便于我们提升代码可读性的,这四个注解对于spring而言,是没有区别的,我们可以给任意一个类上加任意一个注解。
给类加上注解之后,还需要在配置文件中开启注解扫描,这样就能通过注解的形式把类对象交由spring IOC容器进行管理。
开启注解扫描的配置如下:

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

只有这一行配置,同样也是context命名空间下的。在这个配置内部,还可以添加exclude和include两个子注解,用于定于说,在这个包下面扫描的时候,不包括或者包括哪些注解。这样的配置更多时候是在整合了SSM框架的时候会用到。比如把@Controller交由Spring MVC扫描,而其他的则由Spring进行扫描。下面这个只是列举了这种例子。

<context:component-scan base-package="com.phl">
       <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
   </context:component-scan>

针对上面出现的include-filter和exclude-filter,他们内部的属性在这里进行详细解释。因为exclude-filter与include-filter是一组意义相反的配置,下面的内容和解释就取其中exclude-filter为例进行描述,include的意义与这个相反就是了。
type,表示指定过滤的规则,具体的值如下:

  • annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
  • assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
  • aspectj:后面讲aop的时候说明要使用的aspectj表达式,几乎不用
  • custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉,几乎不用
  • regex:使用正则表达式过滤,几乎不用

上面说到的那几个主要的注解和componentScan,组合起来就是如何通过注解的形式让Spring IOC容器把bean管理起来。

@AutoWired

@AutoWired,这个注解实在是太常用了,作用就是通过这个注解,实现对象的注入。
注意:当使用AutoWired注解的时候,自动装配的时候是根据类型实现的。

  1. 如果只找到一个,则直接进行赋值
  2. 如果没有找到,则直接抛出异常
  3. **如果找到多个,那么会按照变量名作为id继续匹配。**匹配上直接进行装配,匹配不上则直接报异常

还可以配合使用@Qualifier注解来指定id的名称,让spring不要使用变量名,当使用@Qualifier注解的时候也会有两种情况。

  1. 找到,则直接装配
  2. 找不到,就会报错

拓展一下,@AutoWired除了可以用在属性上,这个注解还可以用在方法上。当用在方法上的时候,这个方法会在Bean创建的时候自动调用,并且方法中的每个入参都会自动进行装配。同样,在方法上使用的时候也可以配合着@Qualifier使用,给每个入参指定专门的ID,而不是按照变量名作为ID去IOC容器中查询相应的bean。

@Resource

通过注解进行自动装配时,除了可以使用@AutoWired之外,还可以使用@Resource。这里主要概述两者区别。

  • @AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准
  • @AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性
  • @AutoWired只适合spring框架,而@Resource扩展性更好
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值