# Spring框架知识体系

https://pdai.tech/md/spring/spring.html

主要介绍Spring框架整体架构,Spring的核心IOC,AOP的案例和具体实现机制;以及SpringMVC框架的案例和实现机制。

Spring的核心要点,比如IOC和AOP等;在此基础上还引入了不同的配置方式, 如XML,Java配置和注解方式的差异。

# Spring基础 - Spring和Spring框架组成

# Spring的特性和优势

Spring Framework有哪些特性,用了这个框架对开发而言有什么好处呢?

从Spring 框架的特性来看:

  • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
  • 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
  • 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  • 面向切面编程:Aspect Oriented Programming——AOP
  • 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

理解为何会有如下的知识点了

  1. Spring框架管理这些Bean的创建工作,即由用户管理Bean转变为框架管理Bean,这个就叫控制反转 - Inversion of Control (IoC)
  2. Spring 框架托管创建的Bean放在哪里呢? 这便是IoC Container;
  3. Spring 框架为了更好让用户配置Bean,必然会引入不同方式来配置Bean? 这便是xml配置,Java配置,注解配置等支持
  4. Spring 框架既然接管了Bean的生成,必然需要管理整个Bean的生命周期等;
  5. 应用程序代码从Ioc Container中获取依赖的Bean,注入到应用程序中,这个过程叫 依赖注入(Dependency Injection,DI) ; 所以说控制反转是通过依赖注入实现的,其实它们是同一个概念的不同角度描述。通俗来说就是IoC是设计思想,DI是实现方式
  6. 在依赖注入时,有哪些方式呢?这就是构造器方式,@Autowired, @Resource, @Qualifier... 同时Bean之间存在依赖(可能存在先后顺序问题,以及循环依赖问题等)

# 面向切面 - AOP

来看第二个需求:给Service所有方法调用添加日志(调用方法时),本质上是解耦问题;

  • 如果没有Spring框架,我们需要在每个service的方法中都添加记录日志的方法,比如:
/**
* find user list.
*
* @return user list
*/
public List<User> findUserList() {
    System.out.println("execute method findUserList");
    return this.userDao.findUserList();
}
  • 有了Spring框架,通过@Aspect注解 定义了切面,这个切面中定义了拦截所有service中的方法,并记录日志; 可以明显看到,框架将日志记录和业务需求的代码解耦了,不再是侵入式的了
/**
* aspect for every methods under service package.
*/
@Around("execution(* tech.pdai.springframework.service.*.*(..))")
public Object businessService(ProceedingJoinPoint pjp) throws Throwable {
    // get attribute through annotation
    Method method = ((MethodSignature) pjp.getSignature()).getMethod();
    System.out.println("execute method: " + method.getName());

    // continue to process
    return pjp.proceed();
}

更进一步,你便能理解为何会有如下的知识点了

  1. Spring 框架通过定义切面, 通过拦截切点实现了不同业务模块的解耦,这个就叫面向切面编程 - Aspect Oriented Programming (AOP)
  2. 为什么@Aspect注解使用的是aspectj的jar包呢?这就引出了Aspect4J和Spring AOP的历史渊源,只有理解了Aspect4J和Spring的渊源才能理解有些注解上的兼容设计
  3. 如何支持更多拦截方式来实现解耦, 以满足更多场景需求呢? 这就是@Around, @Pointcut... 等的设计
  4. 那么Spring框架又是如何实现AOP的呢? 这就引入代理技术,分静态代理和动态代理,动态代理又包含JDK代理和CGLIB代理等

# Spring框架设计如何逐步简化开发的

通过上述的框架介绍和例子,已经初步知道了Spring设计的两个大的要点:IOC和AOP;从框架的设计角度而言,更为重要的是简化开发,比如提供更为便捷的配置Bean的方式,直至0配置(即约定大于配置)。这里我将通过Spring历史版本的发展,和SpringBoot的推出等,来帮你理解Spring框架是如何逐步简化开发的。

# # Java 配置方式改造

在前文的例子中, 通过xml配置方式实现的,这种方式实际上比较麻烦; 我通过Java配置进行改造:

  • User,UserDaoImpl, UserServiceImpl,LogAspect不用改
  • 将原通过.xml配置转换为Java配置
package tech.pdai.springframework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import tech.pdai.springframework.aspect.LogAspect;
import tech.pdai.springframework.dao.UserDaoImpl;
import tech.pdai.springframework.service.UserServiceImpl;

/**
 * @author pdai
 */
@EnableAspectJAutoProxy
@Configuration
public class BeansConfig {

    /**
     * @return user dao
     */
    @Bean("userDao")
    public UserDaoImpl userDao() {
        return new UserDaoImpl();
    }

    /**
     * @return user service
     */
    @Bean("userService")
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }

    /**
     * @return log aspect
     */
    @Bean("logAspect")
    public LogAspect logAspect() {
        return new LogAspect();
    }
}
  • 在App中加载BeansConfig的配置
package tech.pdai.springframework;

import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tech.pdai.springframework.config.BeansConfig;
import tech.pdai.springframework.entity.User;
import tech.pdai.springframework.service.UserServiceImpl;

/**
 * @author pdai
 */
public class App {

    /**
     * main interfaces.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // create and configure beans
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeansConfig.class);

        // retrieve configured instance
        UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);

        // use configured instance
        List<User> userList = service.findUserList();

        // print info from beans
        userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
    }
}
  • 整体结构和运行app

# # 注解配置方式改造

更进一步,Java 5开始提供注解支持,Spring 2.5 开始完全支持基于注解的配置并且也支持JSR250 注解。在Spring后续的版本发展倾向于通过注解和Java配置结合使用.

  • BeanConfig 不再需要Java配置
package tech.pdai.springframework.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author pdai
 */
@Configuration
@EnableAspectJAutoProxy
public class BeansConfig {

}
  • UserDaoImpl 增加了 @Repository注解
/**
 * @author pdai
 */
@Repository
public class UserDaoImpl {

    /**
     * mocked to find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return Collections.singletonList(new User("pdai", 18));
    }
}
  • UserServiceImpl 增加了@Service 注解,并通过@Autowired注入userDao.
/**
 * @author pdai
 */
@Service
public class UserServiceImpl {

    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }

}
  • 在App中扫描tech.pdai.springframework包
package tech.pdai.springframework;

import java.util.List;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import tech.pdai.springframework.entity.User;
import tech.pdai.springframework.service.UserServiceImpl;

/**
 * @author pdai
 */
public class App {

    /**
     * main interfaces.
     *
     * @param args args
     */
    public static void main(String[] args) {
        // create and configure beans
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                "tech.pdai.springframework");

        // retrieve configured instance
        UserServiceImpl service = context.getBean(UserServiceImpl.class);

        // use configured instance
        List<User> userList = service.findUserList();

        // print info from beans
        userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
    }
}
  • 整体结构和运行app

# # SpringBoot托管配置

Springboot实际上通过约定大于配置的方式,使用xx-starter统一的对Bean进行默认初始化,用户只需要很少的配置就可以进行开发了。

这个因为很多开发者都是从SpringBoot开始着手开发的,所以这个比较好理解。我们需要的是将知识点都串联起来,构筑认知体系。