Spring学习
思考几个问题
如果没有Spring,我们怎么开发维护Java Web代码
Spring的核心功能有哪些?
Spring Bean是怎么被创建的呢?
(1) spring IoC是什么
IoC全称Inversion of Control,直译为控制反转。
IoC是一种思想,可以用来设计出低耦合、易扩展的代码。
就好比以前租房子时自己按照要求一个一个找房东,现在可以把要求告诉房屋中介通过他们找房子。
像房屋卧室大小、朝向、位置、价格、家具这些要求全部交给房屋中介去搞定。
在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 容器中的注册