spring源码刨析总结

spring源码刨析笔记

1.概述

spring就是 spring Framework
Ioc Inversion of Control(控制反转/反转控制)
DI Dependancy Injection(依赖注入)
Aop Aspect oriented Programming 面向切面编程(OOP的延续)

2.Ioc与DI

Ioc与Aop的区别

Ioc在对象角度将对象实例化以及管理的权力交给了容器
DI在容器角度将对象依赖注入到其他对象

3.Aop

3.1横切逻辑代码

多个纵向流程中出现相同子流程代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZtERCe4C-1598602425907)(D:\拉钩笔记\lagouNode\1Stage\spring\pic\横切代码分离.png)]

出现的问题:
横切代码重复问题,与业务逻辑代码混在一起,臃肿,维护不方便
Aop不改变业务逻辑情况下,增强横切逻辑代码,根本上解耦合代码

4.自定义的Spring框架

//将private AccountDao accountDao = new JdbcAccountDaoImpl();转换为 
    private AccountDao accountDao;
    // 构造函数传值/set方法传值
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

4.1思路

public class BeanFactory {
    //集合的定义放在类的最上面来,因为后面的使用时在static静态代码块中,否则在使用时集合对象会为null
    static List<String> classNames = new ArrayList<>();    // 缓存扫描到的class全限定类名
    static List<String> fieldsAlreayProcessed = new ArrayList<>(); // 缓存已经进行过依赖注入的信息
    /**
     * 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
     * 任务二:对外提供获取实例对象的接口(根据id获取)
     */
    private static Map<String,Object> map = new HashMap<>();  // 存储对象
}
1.遍历扫描路径

//获取扫描包的全限定类名

<beans>
    <component-scan base-package="com.lagou.edu"></component-scan>
</beans>
  // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
  // 解析xml
  SAXReader saxReader = new SAXReader();
    Document document = saxReader.read(resourceAsStream);
    Element rootElement = document.getRootElement();
    Element scanElement = (Element) rootElement.selectSingleNode("//component-scan");
    String scanPackage = scanElement.attributeValue("base-package");
    /**
     * 扫描指定包下的注解
     */
    private static void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for(File file: files) {
            if(file.isDirectory()) { // 子package
                // 递归
                doScan(scanPackage + "." + file.getName());  // com.lagou.demo.controller
            }else if(file.getName().endsWith(".class")) {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }

        }
    }
2.对象实例化
 /**
     * 通过反射实例化对象,此时暂不维护依赖注入关系
     */
    private static void doInstance() {
        if (classNames.size() == 0) return;

        try {

            for (int i = 0; i < classNames.size(); i++) {
                String className = classNames.get(i);

                // 反射
                Class<?> aClass = Class.forName(className);
                // 只处理标注了注解@MyService、@MyRepository和@MyComponent的类
                if (aClass.isAnnotationPresent(MyService.class)
                        || aClass.isAnnotationPresent(MyRepository.class)
                        || aClass.isAnnotationPresent(MyComponent.class)) {

                    //获取注解value值
                    String beanName = null;

                    if (aClass.isAnnotationPresent(MyService.class)) {
                        beanName = aClass.getAnnotation(MyService.class).value();
                    } else if (aClass.isAnnotationPresent(MyRepository.class)) {
                        beanName = aClass.getAnnotation(MyRepository.class).value();
                    } else if (aClass.isAnnotationPresent(MyComponent.class)) {
                        beanName = aClass.getAnnotation(MyComponent.class).value();
                    }


                    // 如果指定了id,就以指定的为准
                    Object o = aClass.newInstance();
                    if ("".equals(beanName.trim())) {
                        beanName = lowerFirst(aClass.getSimpleName());
                    }
                    map.put(beanName,o);


                    // service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到容器中,便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    if(interfaces != null && interfaces.length > 0) {
                        for (int j = 0; j < interfaces.length; j++) {
                            Class<?> anInterface = interfaces[j];
                            // 以接口的全限定类名作为id放入
                            map.put(anInterface.getName(), aClass.newInstance());
                        }
                    }

                } else {
                    continue;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.维护注入关系
  /*
     * 实现依赖注入
     */
    private static void doAutoWired(){
        if(map.isEmpty()) {return;}

        // 遍历ioc中所有对象,查看对象中的字段,是否有@LagouAutowired注解,如果有需要维护依赖注入关系
        for(Map.Entry<String,Object> entry: map.entrySet()) {
            try {
                doObjectDependancy(entry.getValue());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * A 可能依赖于 B ,B 可能依赖于 C ,C 可能又依赖于D,本方法主要维护一下嵌套依赖
     */
    private static void doObjectDependancy(Object object) throws IllegalAccessException {
        Field[] declaredFields = object.getClass().getDeclaredFields();

        if(declaredFields == null || declaredFields.length ==0) {
            return;
        }
        // 遍历判断处理
        for (int i = 0; i < declaredFields.length; i++) {

            Field declaredField = declaredFields[i];

            if (!declaredField.isAnnotationPresent(MyAutowired.class)) {
                continue;
            }


            // 判断当前字段是否处理过,如果已经处理过则continue,避免嵌套处理死循环
            if(fieldsAlreayProcessed.contains(object.getClass().getName()  + "." + declaredField.getName())){
                continue;
            }


            Object dependObject = null;
            dependObject = map.get(declaredField.getType().getName());  //  先按照声明的是接口去获取,如果获取不到再按照首字母小写

            if(dependObject == null) {
                dependObject = map.get(lowerFirst(declaredField.getType().getSimpleName()));
            }

            // 记录下给哪个对象的哪个属性设置过,避免死循环
            fieldsAlreayProcessed.add(object.getClass().getName() + "." + declaredField.getName());

            // 迭代
            doObjectDependancy(dependObject);

            declaredField.setAccessible(true);
            declaredField.set(object,dependObject);

        }
    }


4.维护事务
  /*
     * 实现事务管理,为添加了@MyTransactional注解的对象创建代理对象,并覆盖原IOC容器中的对象
     */
    private static void doTransactional() {
        ProxyFactory proxyFactory = (ProxyFactory) map.get("proxyFactory");
        for(Map.Entry<String,Object> entry: map.entrySet()) {
            String beanName = entry.getKey();
            Object o = entry.getValue();
            Class<?> aClass = entry.getValue().getClass();
            if(aClass.isAnnotationPresent(MyTransactional.class)) {
                // 需要进行事务控制

                // 有实现接口
                Class<?>[] interfaces = aClass.getInterfaces();
                if(interfaces != null && interfaces.length > 0) {
                    // 使用jdk动态代理
                    map.put(beanName,proxyFactory.getJdkProxy(o));
                }else{
                    // 使用cglib动态代理
                    map.put(beanName,proxyFactory.getCglibProxy(o));
                }
            }
        }

    }

4.2代码

在这里插入图片描述

创建connection的时候放到同一个线程中

public class ConnectionUtils {
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); // 存储当前线程的连接

    /**
     * 从当前线程获取连接
     */
    public Connection getCurrentThreadConn() throws SQLException {
        /**
         * 判断当前线程中是否已经绑定连接,如果没有绑定,需要从连接池获取一个连接绑定到当前线程
          */
        Connection connection = threadLocal.get();
        if(connection == null) {
            // 从连接池拿连接并绑定到线程
            connection = DruidUtils.getInstance().getConnection();
            // 绑定到当前线程
            threadLocal.set(connection);
        }
        return connection;
    }
}

5. FactoryBean 和 BeanFactory区别

BeanFactory是个bean 工厂,是一个工厂类(接口), 它负责生产和管理bean的一个工厂
是ioc 容器最底层的接口,是个ioc容器,是spring用来管理和装配普通bean的ioc容器(这些bean成为普通bean)。

FactoryBean是个bean,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式,是一个可以生产对象和装饰对象的工厂bean,由spring管理后,生产的对象是由getObject()方法决定的。

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
 @Nullable
 // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
 T getObject() throws Exception;
 @Nullable
 // 返回FactoryBean创建的Bean类型
 Class<?> getObjectType();
 // 返回作⽤域是否单例
 default boolean isSingleton() {
 return true;
 } }

public class CompanyFactoryBean implements FactoryBean<Company> {
         @Override
         public Company getObject() throws Exception {
         // 模拟创建复杂对象Company
         Company company = new Company();
         String[] strings = companyInfo.split(",");
         company.setName(strings[0]);
         company.setAddress(strings[1]);
         company.setScale(Integer.parseInt(strings[2]));
         return company;
         }

}

6.spring的循环引用

1.构造器注入循环依赖

2.@Autowired的依赖注入

6.1流程

在这里插入图片描述

①:构造器的循环依赖。【这个Spring解决不了】

Spring是先将Bean对象实例化【依赖无参构造函数】—>再设置对象属性的


//一级缓存
//singletonFactories : 单例对象工厂的cache 
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
//二级缓存
// earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】
/** Cache of early singleton objects: bean name --> bean instance */ 
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
//三级缓存
 //singletonObjects:单例对象的cache
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Names of beans that are currently in creation. */
// 这个缓存也十分重要:它表示bean创建过程中都会在里面呆着~
// 它在Bean开始创建时放值,创建完成时会将其移出~
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** Names of beans that have already been created at least once. */
// 当这个Bean被创建完成后,会标记为这个 注意:这里是set集合 不会重复
// 至少被创建了一次的  都会放进这里~~~~
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));


6.2为什么不用二级缓存要用三级缓存

可以二级缓存就可以初始化一些内容,

在将三级缓存放入二级缓存的时候,会判断是否有SmartInstantiationAwareBeanPostProcessor这样的后置处理器,换句话说这里是给用户提供接口扩展的,所以采用了三级缓存, 特殊写法中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5YIeqFNy-1598602425922)(D:\拉钩笔记\lagouNode\1Stage\spring\课堂Pic\spring生命周期.png)]

6.3源代码中的特殊写法

	//添加到三级缓存中
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	//====改写后
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            getEarlyBeanReference(beanName, mbd, bean);
        } 
   		});
	//ObjectFactory方法
    public interface ObjectFactory<T> {
        T getObject() throws BeansException;
    }

	//addSingletonFactory方法		
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			if (!this.singletonObjects.containsKey(beanName)) {
				//添加三级缓存
				this.singletonFactories.put(beanName, singletonFactory);
				//移除二级
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
		}
	}
	//getEarlyBeanReference方法
	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					// 这么一大段就这句话是核心,也就是当bean要进行提前曝光时,
					// 给一个机会,通过重写后置处理器的getEarlyBeanReference方法,来自定义操作bean
					// 值得注意的是,如果提前曝光了,但是没有被提前引用,则该后置处理器并不生效!!!
					// 这也正式三级缓存存在的意义,否则二级缓存就可以解决循环依赖的问题
	
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

			

6.4文字版叙述流程

  1. 使用context.getBean(A.class),旨在获取容器内的单例A(若A不存在,就会走A这个Bean的创建流程),显然初次获取A是不存在的,因此走A的创建之路~

  2. 实例化A(注意此处仅仅是实例化),并将它放进缓存(此时A已经实例化完成,已经可以被引用了)

  3. 初始化A:@Autowired依赖注入B(此时需要去容器内获取B)为了完成依赖注入B,会通过getBean(B)去容器内找B。但此时B在容器内不存在,就走向B的创建之路~

  4. 实例化B,并将其放入缓存。

  5. 初始化B,@Autowired依赖注入A(此时需要去容器内获取A)

  6. 此处重要:初始化B时会调用getBean(A)去容器内找到A,上面我们已经说过了此时候因为A已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在A的引用了的,所以getBean(A)能够正常返回(此时B也能够被引用了)然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);

    经过上面的步骤后,B里面,field已经填充ok,其中,且填充的field是代理后的A,这里命名为proxy A。

    B 继续其他的后续处理。

  7. B初始化成功(此时已经注入A成功了,已成功持有A的引用了),return(注意此处return相当于是返回最上面的getBean(B)这句代码,回到了初始化A的流程中~)。

  8. 因为B实例已经成功返回了,因此最终A也初始化成功

  9. 到此,B持有的已经是初始化完成的A,A持有的也是初始化完成的B

6.5 spring的事务

1.四大特征

原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)

2.事务的隔离级别

Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼
Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆
该机制下会对要update的⾏进⾏加锁
Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三
Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低
注意:级别依次升⾼,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ

3.事务的传播行为

PROPAGATION_REQUIRED 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,
加⼊到这个事务中。这是最常⻅的选择
PROPAGATION_SUPPORTS ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY 使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则
执⾏与PROPAGATION_REQUIRED类似的操作。

6.6资料

1.https://blog.csdn.net/chaitoudaren/article/details/104833575
2.https://zhuanlan.zhihu.com/p/84267654
3.https://blog.csdn.net/qq_36381855/article/details/79752689

java代理

1.java动态代理

实现了InvocationHandler接口

 public Object getJdkProxy(Object obj) {
 Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = null;
                        try{
                            // 开启事务(关闭事务的自动提交)
                            transactionManager.beginTransaction();

                            result = method.invoke(obj,args);

                            // 提交事务

                            transactionManager.commit();
                        }catch (Exception e) {
                            e.printStackTrace();
                            // 回滚事务
                            transactionManager.rollback();

                            // 抛出异常便于上层servlet捕获
                            throw e;

                        }
                        return result;
                    }
                });
}

2.cglib动态代理

实现MethodInterceptor接口

 public Object getCglibProxy(Object obj) {
//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        return  Enhancer.create(obj.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object result = null;
                try{
                    // 开启事务(关闭事务的自动提交)
                    transactionManager.beginTransaction();

                    result = method.invoke(obj,objects);

                    // 提交事务

                    transactionManager.commit();
                }catch (Exception e) {
                    e.printStackTrace();
                    // 回滚事务
                    transactionManager.rollback();

                    // 抛出异常便于上层servlet捕获
                    throw e;

                }
                return result;
            }
        });
    }

3.区别

1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,
并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,
对于final类或方法,是无法继承的

链接:https://www.jianshu.com/p/46d092bb737d

补充

1.ThreadLocal是什么

// 存储当前线程的连接
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); 

ThreadLocal 内部维护了一个 Map ,这个 Map 是叫做 ThreadLocalMap 里卖弄有个Entey数组的table

ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

ThreadLocal 适用于如下两种场景

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

1.1源代码刨析

总结
1.每个线程持有一个ThreadLocalMap对象==》set()方法没有的时候会去CreateMap()

2.每个线程Thread持有一个ThreadLocalMap类型的实例内含Entry(可以理解为每个线程Thread都持有一个Entry型的数组table)

	//set方法
  public void set(T value) {
  		//获取当前线程
        Thread t = Thread.currentThread();
        //取值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
	//创建一个ThreadLocalMap方法
 	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
	//ThreadLocalMap方法
	 static class ThreadLocalMap {
       //Entry数组
        private Entry[] table;
         	//Entry对象 继承了弱引用的接口
            static class Entry extends WeakReference<ThreadLocal<?>> {
                    /** The value associated(允许) with this ThreadLocal. */
                    Object value;
                    Entry(ThreadLocal<?> k, Object v) {
                        super(k);
                        value = v;
                    }
                }
  			//ThreadLocalMap对象的实例化
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                private static final int INITIAL_CAPACITY = 16;
                //初始化因子为16
                table = new Entry[INITIAL_CAPACITY];
                //位运算,结果与取模相同,计算出需要存放的位
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
                 //给ThreadLocalMap存值       
                private void set(ThreadLocal<?> key, Object value) {
                    //获取ThreadLocalMap的Table
                    Entry[] tab = table;
                    int len = tab.length;
                    //将threadLocalHashCode进行一个位运算(取模)得到索引i
                    int i = key.threadLocalHashCode & (len-1);

                    for (Entry e = tab[i];
                         e != null;
                         e = tab[i = nextIndex(i, len)]) {
                        ThreadLocal<?> k = e.get();

                        if (k == key) {
                            e.value = value;
                            return;
                        }

                        if (k == null) {
                            replaceStaleEntry(key, value, i);
                            return;
                        }
                    }

                    tab[i] = new Entry(key, value);
                    int sz = ++size;
                    if (!cleanSomeSlots(i, sz) && sz >= threshold)
                        rehash();
                }
            }
}

1.2总结

1.不同线程之间访问时访问的是不同的table数组的同一位置即都为table[i],只不过这个不同线程之间的table是独立的。
2.对于同一线程的不同ThreadLocal来讲,这些ThreadLocal实例共享一个table数组,然后每个ThreadLocal实例在table中的索引i是不同的。

1.3ThreadLocal和Synchronized

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
Synchronized是通过线程等待,牺牲时间来解决访问冲突
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

链接:https://www.jianshu.com/p/3c5d7f09dfbd

1.4关于Spring,ioc创建的对象和new创建的对象有啥区别

Spring Bean是反射创建,先创建bean之后,有一系列的步骤(ioc 12个步骤)可以被开发中敢于修改扩展,最终把创建好的对象放在map里

读源码

今天我就给大家分享一下路神的Spring源码学习方法:(源码的学习方法是通用的)

1、通读Spring官方文档
学习Spring源码之前,首先要把Spring官方网文档系统的阅读一遍。哪怕你读不懂,也会接触到很多名词,读源码的时候大有帮助。
有人拿自己英语不好当借口,子路笑言自己的英文水平经常被人喷,这个困难要自己克服。

2、如何正确阅读Spring源码
读完源码就忘,是因为你没有理解透彻。子路建议:“不要从字面意义上去读源码,通过源码启动方式阅读。”
比如读nacos的源码,要理解作者做这个设计变量的思路、设计代码的原则、作者的想法是怎样的?
比如nacos跟Spring、Spring boot、Spring cloud这四个角色分别完成什么样的功能?Spring cloud中Spring-cloud-common这个包有什么用?Spring boot主要完成的功能?Spring又完成什么功能?
那么三者结合在一起就可以看出作者写代码的意图,一定要站在作者的角度,结合全局来看源码。
3、尽情去调试Spring吧
源码级的知识一定要自己验证!特别是Spring的扩展点!
在学习过程中,不要怕,尽量多去调试;看一下就去断点调试一下;多去写自己的注释;尽量去把Spring代码改了,把代码给删了!
多思考Spring某些地方预留的接口能干嘛?这个地方是不是可以做扩展?MyBatis是如何扩展Spring的?市面上还有哪些主流框架扩展了Spring?边看源码边思考,这样你的记忆会加深很多。
学习Spring源码目的就是为了让我们能够去对Spring做二次开发或者扩展。

实话实说,大多数人学Spring,就是为了去面试。很多人在简历上写“读过Spring源码”,这么写你连电话都接不到!
我们读过Spring源码之后,简历上该怎么写?给大家做个参考:
系统的阅读过Spring源码;
能够对Spring做二次开发;
并且熟知Spring当中的各种扩展点;
熟知主流框架对Spring源码的扩展;

单词

Injection 注射注入
current 现在,流通的
associated关联的,联系
necessary必须的,必要的
rehash重复
refresh更新,恢复
obtain获得流行

triggered 引起 触发

学习资源

spring循环依赖 https://mp.weixin.qq.com/s/RFxoVMeW5Mx9kzzFGhpyKA
阅读源码的方式:https://mp.weixin.qq.com/s/XQ5jl2pUa1W7Ueu0pWFS2Q

什么是ThreadLocal:https://mp.weixin.qq.com/s/bcH2pL06J5udBWedE1tbCA

springBean的生命周期:https://mp.weixin.qq.com/s/rz9cZRLZZnA_kahetEYjaw

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值