Spring学习

思考几个问题
如果没有Spring,我们怎么开发维护Java Web代码
Spring的核心功能有哪些?
Spring Bean是怎么被创建的呢?

(1) spring IoC是什么

IoC全称Inversion of Control,直译为控制反转。
IoC是一种思想,可以用来设计出低耦合、易扩展的代码。

就好比以前租房子时自己按照要求一个一个找房东,现在可以把要求告诉房屋中介通过他们找房子。
get-instance-direct

像房屋卧室大小、朝向、位置、价格、家具这些要求全部交给房屋中介去搞定。

get-instance-by-ioc-container

在Java Web开发时,我们经常也会有类似的问题,在创建对象时会有很多字段/状态/要求,可能会发生变更,导致需要改大量简单但是重复的代码,而且可能对线上的服务产生影响,需要研发、测试回归,造成大量的人力资源浪费。

(2) 为什么要用 Spring IoC

在开发Java Web项目时,在没有用Spring以前是怎么开发的?

(2.1) 没有用spring以前是怎么开发的

以用户的增删改查为例。
开发一个用户新增接口时。以当时的mvc架构分层。需要做4步;
1、在web.xml配置servlet映射;
2、修改对应的servlet;
3、修改订单service;
4、修改对应的dao;

 以service为例

public class AddUserServiceImpl {

	public boolean addUser(String name,String password,int age){
	     UserDao dao = new UserDao();
	     int num =dao.addUser(name, password, age);
	}
}

 

(2.2) 上面的代码有什么问题?

上面代码存在一些问题问题,对象与对象之间的关系都是紧密耦合的;
userDao对象的创建被写死在AddUserServiceImpl的构造方法中了,扩展性很差。

假设UserDao 的构造器需要更多的参数了,会发现上述代码到处充斥着需要改进的代码。

如果是一个类还好,但是项目类这样的类这样的写法有很多。一个一个改要改很长时间,改完以后还要测试,上线时还得考虑兼容问题,等等。

 

(3) Spring思想及简单实现

思考几个问题
1、假如让你设计,你怎么设计开发一个类似Spring的功能?
2、怎么知道处理哪些Bean ?
3、怎么创建Bean?
4、怎么扩展? 代理模式?继承?组合?
5、使用时怎么查找Bean?

(3.1) 存取bean的工厂

1、把Spring理解成一个Map存取的工厂即可,Map<String, Bean>

public class BeanFactory {

    private Map<String, Bean> beanMap = new HashMap<>();
    
    public Bean getBean(String key){
      return beanMap.get(key) ;
    }

}

(3.2) 查找bean

2、Bean所属工厂提供了Map的操作来完成查找,找到Bean后装配给其它对象,这就是依赖查找、自动注入的过程。  

(3.3) 发现bean

3、这些 Bean 又是怎么被创建的呢?
需要 Spring 来管理,怎么识别哪些类需要处理,得有一些标记,比如 Component注解  

批量发现bean

4、有了这些注解后,谁又来做“发现”它们的工作呢?直接配置指定自然不成问题,但是很明显“自动发现”更让人省心。此时,我们往往需要一个扫描器
有了扫描器,我们就知道哪些类是需要成为 Bean。

public class AnnotationScan {
    
    //通过扫描包名来找到Bean
    void scan(String packages) {
         //
    }

}

 

(3.4) 实例化bean

5、有了扫描器,我们就知道哪些类是需要成为 Bean。那怎么实例化为 Bean (也就是一个对象实例而已)呢?很明显,只能通过反射来做了。

不过这里面的方式可能有多种:

java.lang.Class.newInsance()
java.lang.reflect.Constructor.newInstance()
ReflectionFactory.newConstructorForSerialization()

 

(3.5) 功能增强 AOP

7、想记录一个方法调用的性能,有时候我们又想在方法调用时输出统一的调用日志。诸如此类,我们肯定不想频繁再来个散弹式的修改。所以我们有了 AOP,帮忙拦截方法调用,进行功能扩展。拦截谁呢?在 Spring 中自然就是 Bean 了。
假设我们判断出一个 Bean 需要“增强”了,我们直接让它从工厂返回的时候,就使用一个代理对象作为返回不就可以了么?

public class BeanFactory {

    private Map<String, Bean> beanMap = new HashMap<>();
    
    public Bean getBean(String key){
       //查找是否创建过
       Bean bean = beanMap.get(key);
       if(bean != null){
         return bean;
       }
       //创建一个Bean
       Bean bean = createBean();
       //判断要不要AOP
       boolean needAop = judgeIfNeedAop(bean);
       try{
           if(needAop)
              //创建代理对象
              bean = createProxyObject(bean);
              return bean;
           else:
              return bean
       }finally{
           beanMap.put(key, bean);
       }
    }
}

怎么知道一个对象要不要 AOP?既然一个对象要 AOP,它肯定被标记了一些“规则”,例如拦截某个类的某某方法

@Aspect
@Service
public class AopConfig {
    @Around("execution(* com.spring.puzzle.ComponentA.execute()) ")
    public void recordPayPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
      //
    }
}

 

(4) Spring IoC 流程

(4.1) 处理哪些Bean?

在用Spring前或者用Spring时有没有思考过有哪些方法可以加载Bean

(4.1.1) 通过xml文件指定某个Bean

<?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 name="personBean" class="com.wkq.java.spring.demo.model.Person"
          init-method="initPerson"
          destroy-method="destroyPerson">
        <property name="id" value="1"/>
        <property name="name" value="johnny"/>
        <property name="age" value="10"/>
    </bean>

</beans>
public class SpringBeanTest {
    
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_bean_test.xml");
        Person person = applicationContext.getBean(Person.class);
        System.out.println("print get bean = " + person);
        applicationContext.destroy();
    }

}

(4.1.2) 通过XML文件配置需要的包

像SpringMVC 通常是在xml文件里配置要扫描的包,Spring会扫描包下的Bean

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

    <!-- 扫码指定包 -->
    <context:component-scan base-package="com.wkq.java.spring.demo"/>

</beans>
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring_bean_test.xml");

(4.1.3) 通过配置@ComponentScan注解扫描

像在SpringBoot里通常通过 @ComponentScan 注解配置需要扫描的包

@Configuration
@ComponentScan("com.wkq.java.spring.demo")
public class AppConfig {
    // 省略部分无关代码
}

(4.2) 怎么把Bean加载到工厂里

ClassPathXmlApplicationContext 的容器初始化我们大致分为下面几步:
1、BeanDefinition 的 Resource 定位
2、从 Resource中解析、载入BeanDefinition
3、BeanDefinition 在IoC 容器中的注册

参考资料

[1] 导读|5分钟轻松了解Spring基础知识
[2] 5. The IoC container