一文学习Spring(核心篇)-基于Spring官方文档

目录

1. 前言

image-20211113172026739

Spring的重要性毋庸置疑,前前后后也花了很多心思去学习,包括极客时间上的小马哥的课程,丁雪峰的玩转Spring全家桶,Spring编程常见错误50例。

此外,还学习了刘欣的《从零开始造Spring》,以及Spring官方文档。今天对所学习的内容进行比较系统的总结。

我们大体上可以把Spring技术分为核心特性和Web技术。

这篇文章我们主要讨论核心特性。

2. 核心特性

dependency injection, events, resources, i18n, validation, data binding, type conversion, SpEL, AOP.

Spring最为核心的特性是IoC和AoP。围绕这两者会有大量的面试题目。

这两个概念不在赘述。

2.1 IoC

2.1.1 容器是什么

IoC容器用来管理我们的Bean。根据我们配置的信息来实例化和组装Bean。

container magic

我们可以基于xml来进行配置,注解和Java类来进行配置。

先来看基于XML的配置:

<?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="petStore"
          class="org.litespring.service.v2.PetStoreService">

        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <property name="owner" value="hjs"/>
        <property name="version" value="2"/>
    </bean>

    <bean id="accountDao" class="org.litespring.dao.v2.AccountDao">
    </bean>

    <bean id="itemDao" class="org.litespring.dao.v2.ItemDao">
    </bean>

</beans>

再来看基于注解的配置,这个和xml本质上是类似的,其中@Component注解描述了该类需要被实例化,@Autowired注解描述了两个类之间的依赖关系。

<?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">

    <context:component-scan base-package="org.litespring.service.v4,org.litespring.dao.v4">

    </context:component-scan>
</beans>


import org.litespring.dao.v4.AccountDao;
import org.litespring.dao.v4.ItemDao;
import org.litespring.stereotype.Autowired;
import org.litespring.stereotype.Component;

@Component(value = "petStore")
public class PetStoreService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private ItemDao itemDao;

    public AccountDao getAccountDao() {
        return accountDao;
    }

    public ItemDao getItemDao() {
        return itemDao;
    }
}

和@Component相似的注解有@Repository,@Service和@Controller。和@Autowired相似的注解有@Primary,@Qualifier,@Resource。

最后再来看基于Java类配置,定义Bean信息,类标注@Configuration注解注解,方法标注@Bean注解。

// 1.将一个POJO标注定义为Bean的配置类
@Configuration
public class AppConf {
    // 2.以下两个方法定义了两个Bean,并提供了Bean的实例化逻辑
    @Bean
    public UserDao userDao() {
        return new UserDao();
    }
    @Bean
    public LogDao logDao() {
        return new LogDao()
    }
    @Bean
    public LogonService logonService() {
        LogonServcie logonService = new LogonService();
        // 将上面2处定义的Bean注入到logonService的Bean
        logonService.setLogDao(logDao());
        logonService.setUserDao(userDao());
        return logonService;
    }
}

等价于:

<bean id="userDao" class="com.hhxs.bbt.dao.UserDao" />
<bean id="logDao" class="com.hhxs.bbt.dao.LogDao" />
<bean id="logonService" class="com.hhxs.bbt.conf.LogonService"  
    p:logDao-ref="userDao" p:userDao-ref="logDao" />

启动Spring容器有2种方式:

// 第一种方式,通过AnnotationConfigApplicationContext
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
        LogonService logonService = ctx.getBean(logonService.class);
        logonService.printHello();
    }
}
// 第二种方式,通过register函数
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class JavaConfigTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        // 注册多个@Configuration配置类
        ctx.register(DaoConfig.Class);
        ctx.register(ServiceConfig.class);
        // 刷新容器以应用这些注册的配置类
        ctx.refresh();
        LogonService logonService = ctx.getBean(logonService.class);
        logonService.printHello();
    }
}

总结,他们之间的区别如下:

基于XML配置 基于注解配置 基于Java类配置
Bean定义 <bean class="com.hhxs.bbt.UserDao"> Bean实现类出通过标注 @Component、@Repository、@Service、@Controller 在标注了@Configuration的Java类中,通过在类方法上标注@Bean定义一个Bean。方法必须提供Bean的实例化逻辑。
Bean名称 通过的id或name属性定义 通过注解的value属性定义,如@Component(“userDao”)。默认名称为小写字母打头的类名(不带包名):userDao 通过@Bean的name属性定义,如@Bean(“userDao”),默认名称为方法名。
Bean注入 通过子元素或通过p命名空间的动态属性 通过在成员变量或方法入参出标注@Autowired,按类型匹配自动注入 可以通过在方法处通过@Autowired使方法入参绑定Bean,然后在方法中通过代码进行注入,还可以通过调用配置类的@Bean方法进行注入
Bean生命过程方法 通过的init-method和destory-method属性指定Bean实现类的方法名。最多只能指定一个初始化方法和一个销毁方法 通过在目标方法上标注@PostConstruct和@PreDestroy注解指定初始化或销毁方法,可以定义任意多个方法 通过@Bean的initmethod或destoryMethod指定一个初始化或销毁方法。
Bean作用范围 通过的scope属性指定 通过在类定义出标注@Scope指定 通过在Bean方法定义处标注@Scope指定
Bean延迟初始化 通过的lazy-init属性指定,默认为default,继承与的default-lazy-init设置,该值默认为false 通过在类定义处标注@Lazy指定,如@Lazy(true) 通过在Bean方法定义处标注@Lazy指定

上面我们配置了Bean的关系,我们可以通过ApplicationContext来实例化容器,进而通过context.getBean方法来获取到Spring装配的Bean。

2.1.2 Bean到底是什么

说了这么多,Bean到底是啥呢?

BeanDefinition是Spring抽象出的一个很重要的类,包含下面的元信息:

  • 类的全限定名,是实现类,而不是抽象类或者接口,因为二者不能被实例化;
  • 作用域,生命周期等;
  • 依赖,就是其他的相关Bean;
  • 。。。

具体的参考下面的。

Property Explained in…
Class Instantiating Beans
Name Naming Beans
Scope Bean Scopes
Constructor arguments Dependency Injection
Properties Dependency Injection
Autowiring mode Autowiring Collaborators
Lazy initialization mode Lazy-initialized Beans
Initialization method Initialization Callbacks
Destruction method Destruction Callbacks

另外一个重要的知识点是Bean的名字,需要注意bean的id和name属性。java.beans.Introspector.decapitalize是bean的命名的实现。此外,我们还可以给bean取别名。

Bean的实例化,有以下几种方法:

  1. 静态工厂方法
<bean id="clientService"
    class="examples.ClientService"
    factory-method="createInstance"/>

public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
  1. 实例工厂方法
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

2.1.3 依赖:Bean之间的协同关系

单个Bean不可能组成复杂的应用。

先来看依赖注入

它是为给定代码提供资源的过程。

有2种实现,分别是基于构造函数和Setter方法。

先来看前者,在简单的情况下,是没问题的。

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

在更多数情况下,Spring无法准确的判断出我们的参数的顺序的,上面的写法是有歧义的。

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

我们的解决方案是,一是根据类型来进行判断:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

二是根据顺序进行指定:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

还可以

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

最后一种方法是:

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private final int years;

    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;
    
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

基于setter的实现比较简单。

public class PetStoreService {
    private AccountDao accountDao;
    private ItemDao itemDao;
    private String owner;
    private int version;

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }
}

<?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="petStore"
          class="org.litespring.service.v2.PetStoreService">

        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <property name="owner" value="hjs"/>
        <property name="version" value="2"/>
    </bean>

    <bean id="accountDao" class="org.litespring.dao.v2.AccountDao">
    </bean>

    <bean id="itemDao" class="org.litespring.dao.v2.ItemDao">
    </bean>

</beans>

总结下,依赖的处理过程。

  • 通过配置元数据来创建管理Bean的ApplicationContext容器。其中配置元数据可以基于xml,Java代码和注解。
  • 对于每个Bean来说,其依赖通过配置文件,构造函数的参数等提供;
  • 每个配置或者构造函数的参数都是值或者引用;
  • 值都要转换成实际的类型,如int,long,String,boolean等;

循环依赖是一个很重要的话题。

Xml可以使用p命名空间和c命名空间进行简化。如p相当于property:

<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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean name="classic" class="com.example.ExampleBean">
        <property name="email" value="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</beans>

c相当于constructor-arg:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:c="http://www.springframework.org/schema/c"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="beanTwo" class="x.y.ThingTwo"/>
    <bean id="beanThree" class="x.y.ThingThree"/>

    <!-- traditional declaration with optional argument names -->
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg name="thingTwo" ref="beanTwo"/>
        <constructor-arg name="thingThree" ref="beanThree"/>
        <constructor-arg name="email" value="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</beans>

注意区分depends-on和ref:

  • ref:用来表示2个Bean之间的强依赖关系,如一个Bean是另外一个Bean的属性;
  • depends-on:非直接的依赖关系;
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

2.1.4 Bean的作用域

Scope Description
singleton (Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
prototype Scopes a single bean definition to any number of object instances.
request Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
session Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
application Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
websocket Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.

2.1.4 定制Bean的特性

Spring为我们提供了3个接口来进行定制:

第一个是生命周期回调:Lifecycle Callbacks。

对于初始化回调,我们有以下三种方法(且有优先级):

  • 方法上标注@PostConstruct注解
  • 我们可以实现InitializingBean,覆写afterPropertiesSet方法
  • xml中指定init-method,指定init方法

对于销毁方法:

  • 方法上标注@PreDestroy注解
  • 实现DisposableBean接口,覆写destroy()方法
  • xml中指定destroy-method,指定destroy方法

第二个是ApplicationContextAwareBeanNameAware

以及其他的Aware接口:

Name Injected Dependency Explained in…
ApplicationContextAware Declaring ApplicationContext. ApplicationContextAware and BeanNameAware
ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext. Additional Capabilities of the ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes. Instantiating Beans
BeanFactoryAware Declaring BeanFactory. The BeanFactory
BeanNameAware Name of the declaring bean. ApplicationContextAware and BeanNameAware
LoadTimeWeaverAware Defined weaver for processing class definition at load time. Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization). Additional Capabilities of the ApplicationContext
NotificationPublisherAware Spring JMX notification publisher. Notifications
ResourceLoaderAware Configured loader for low-level access to resources. Resources
ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC
ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext. Spring MVC

2.1.5 继承BeanDefinition

类似于模板模式的实现,可以简化配置:

<bean id="inheritedTestBean" abstract="true"
        class="org.springframework.beans.TestBean">
    <property name="name" value="parent"/>
    <property name="age" value="1"/>
</bean>

<bean id="inheritsWithDifferentClass"
        class="org.springframework.beans.DerivedTestBean"
        parent="inheritedTestBean" init-method="initialize">  
    <property name="name" value="override"/>
    <!-- the age property value of 1 will be inherited from parent -->
</bean>

2.1.5 容器扩展点

  1. 使用BeanPostProcessor来自定义Bean
public interface BeanPostProcessor {
  // bean初始化前
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
	// bean初始化后
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
  1. 使用BeanFactoryPostProcessor来自定义配置元数据
public interface BeanFactoryPostProcessor {
  // 应用程序在Spring创建Bean对象前修改BeanDefinition。
  // 比如:Bean属性配置的类型转换,占位符的替换等。
	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
  1. 通过FactoryBean来自定义Bean的实例化逻辑
package org.springframework.beans.factory;
 // 实现该接口,可以自定义Bean的创建逻辑
public interface FactoryBean<T> {
  // 该工厂创建对象的实例
	T getObject() throws Exception;

	Class<?> getObjectType();
  // getObject()方法返回对象的类型
	boolean isSingleton();
}

2.2 资源管理

2.3 校验、数据绑定、类型转换

2.3.1 校验

校验不应该被放在业务逻辑中。Spring提供了Validator接口。

public interface Validator {
  // 校验目标类能否校验
	boolean supports(Class<?> clazz);
	// 校验目标对象,并将校验失败的内容输出至 Errors 对象
	void validate(Object target, Errors errors);
}

举个例子:

public class UserLoginValidator implements Validator {
  
      private static final int MINIMUM_PASSWORD_LENGTH = 6;
  
      public boolean supports(Class clazz) {
         return UserLogin.class.isAssignableFrom(clazz);
      }
  
      public void validate(Object target, Errors errors) {
         ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "field.required");
         ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "field.required");
         UserLogin login = (UserLogin) target;
         if (login.getPassword() != null
               && login.getPassword().trim().length() < MINIMUM_PASSWORD_LENGTH) {
            errors.rejectValue("password", "field.min.length",
                  new Object[]{Integer.valueOf(MINIMUM_PASSWORD_LENGTH)},
                  "The password must be at least [" + MINIMUM_PASSWORD_LENGTH + "] characters in length.");
         }
      }
   }

需要注意

  • ValidationUtils
  • Errors:数据绑定和校验错误收集接口
    • 核心方法
      • reject 方法(重载):收集错误文案
      • rejectValue 方法(重载):收集对象字段中的错误文案

2.3.2 数据绑定

  • 数据绑定:DataBinder
  • Web参数绑定:WebDataBinder

2.3.3 类型转换

/**
 * A converter converts a source object of type S to a target of type T.
 * Implementations of this interface are thread-safe and can be shared.
 *
 * <p>Implementations may additionally implement {@link ConditionalConverter}.
 *
 * @author Keith Donald
 * @since 3.0
 * @param <S> The source type
 * @param <T> The target type
 */
public interface Converter<S, T> {

	/**
	 * Convert the source of type S to target type T.
	 * @param source the source object to convert, which must be an instance of S (never {@code null})
	 * @return the converted object, which must be an instance of T (potentially {@code null})
	 * @throws IllegalArgumentException if the source could not be converted to the desired target type
	 */
	T convert(S source);

}

2.4 AOP

AOP是OOP的一种补充。

这个可以参考这里

2.5 附录

3. 测试

mock objects, TestContext framework, Spring MVC Test, WebTestClient

TODO。

4. 数据存储

transactions, DAO support, JDBC, ORM, Marshalling XML.

本部分介绍的是数据存储层和业务层的交互。

4.1 事务管理

Spring提供的事务管理的优势如下:

  • 事务管理可以跨不同的API,如JTA,JDBC和JPA等;
  • 支持声明式事务;
  • 使用起来比JTA等复杂的API简单;

4.1.1 Spring事务支持模型的优势

TODO

4.2 支持DAO

目标是在不同的技术之间(JDBC, Hibernate,或者 JPA)快速切换。同时具备完善的异常体系。

4.2.1 统一的异常层次体系

DataAccessException

4.2.2 使用注解配置DAO对象

@Repository
public class HibernateMovieFinder implements MovieFinder {

    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    // ...
}

4.3 JDBC

4.3.1 JDBC数据库的访问方法

  • JdbcTemplate

    • int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
          
      int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
              "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
          
      Actor actor = jdbcTemplate.queryForObject(
              "select first_name, last_name from t_actor where id = ?",
              (resultSet, rowNum) -> {
                  Actor newActor = new Actor();
                  newActor.setFirstName(resultSet.getString("first_name"));
                  newActor.setLastName(resultSet.getString("last_name"));
                  return newActor;
              },
              1212L);
          
      this.jdbcTemplate.update(
              "insert into t_actor (first_name, last_name) values (?, ?)",
              "Leonor", "Watling");
          
      this.jdbcTemplate.update(
              "update t_actor set last_name = ? where id = ?",
              "Banjo", 5276L);
          
          
      
  • SimpleJdbcInsert

参考

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦