Spring基础

160 阅读8分钟

Spring基础

1.Sprint创建bean的几种方法

  1. 通过反射调用构造方法创建bean对象
  2. 通过静态工厂方法创建bean对象
  3. 通过实例工厂方法创建bean对象
  4. 通过FactoryBean创建bean对象

2.Spring Bean Scope作用域

  • Singleton 单例,默认,注意Controller是单例的,@lazy使用时才会创建
  • prototype 原型,多实例,不要使用创建时间长的bean
  • request,session,applicaiton java web中
  • 自定义:实现Scope接口

3.Depond on 干预bean的创建销毁顺序

默认情况下bean的创建和销毁顺序是按照定义来的

bean的创建和销毁的顺序,得去调整xml中bean的定义顺序,或者去加强依赖,这样是非常不好的,spring中可以通过depend-on来解决这些问题,在不调整bean的定义顺序和强加依赖的情况下,可以通过通过depend-on属性来设置当前bean的依赖于哪些bean,那么可以保证depend-on指定的bean在当前bean之前先创建好,销毁的时候在当前bean之后进行销毁。

4.@Primary解决多个bean实现的问题

当容器中有多个满足条件的bean的时候,容器会报错

org.springframework.beans.factory.NoUniqueBeanDefinitionException

xml添加 primary="true"可以置为主要候选者

还可以使用配置候选的方法

 autowire-candidate="false"

5.lazy

普通的bean是在容器启动过程中初始化的(实时初始化),如果有一些bean比较耗时,可以使用延迟初始化,bean会在使用的时候初始化。减少容器的启动时间

如果实时初始化的bean依赖延迟初始化的bean,那么都会实时初始化

解压解决循环依赖的问题

@Lazy通过生成“假”代理对象的方式,阻止参与循环依赖的bean解决依赖。直接阻止了项目启动时有可能发生的循环依赖错误,而随后真正使用@Autowired注入的bean时,获取Spring容器中完整的代理bean

6.单例bean中使用多例bean:lookup-method、replaced-method

用来解决对象中的bean每次都是不同对象的问题

7.代理

jdk : 自带的代理,只能为接口类创建代理类 原理:反射

cglib: 通过asm修改class,可以通过继承为普通类创建代理 原理:asm

jdk代理

常用方法

// 创建代理类
 getProxyClass(ClassLoader loader,Class<?>... interfaces)
​
// 创建实例对象
 newProxyInstance
// 调用方法invoke
​
 Class<IService> proxyClass = (Class<IService>) Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
    // 2. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };
    // 3. 创建代理实例
    IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
    // 4. 调用代理的方法
    proxyService.m1();

cglib

本质是一个字节码生成库,使用asm来操作字节码生成新的类,代理的原理是通过动态生成目标类的子类取覆盖需要代理的类

使用方法

1.Enhancer

2.CallbackHelper

使用示例

   @Test
    public void test() {
        //使用Enhancer来给某个类创建代理类,步骤
        //1.创建Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
        enhancer.setSuperclass(Service1.class);
        /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
        此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接口,实现了Callback接口,
        当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
        enhancer.setCallback(new MethodInterceptor() {
            /**
             * 代理对象方法拦截器
             * @param o 代理对象
             * @param method 被代理的类的方法,即Service1中的方法
             * @param objects 调用方法传递的参数
             * @param methodProxy 方法代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("调用方法:" + method);
                //可以调用MethodProxy的invokeSuper调用被代理类的方法
                Object result = methodProxy.invokeSuper(o, objects);
                return result;
            }
        });
        //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
        Service1 proxy = (Service1) enhancer.create();
        //5.调用代理对象的方法
        proxy.m1();
        proxy.m2();
    }

@Configuration注解有什么用

直接加@bean注解就可以交给spring管理,类上是否有@Configuration注解有何差异

  1. @Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些bean是单例的
  2. 不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个bean注册到spring容器中

8.Spring生命周期

image.png 1.通过BeanDefinationReader读取Bean的配置信息

2.ApplicationContext是BeanFactory的一个实现类

3.BeanFactoryProcessor执行占位符替换PlaceHolder

4.Bean进行实例化

5.Bean进行初始化,属性赋值

6.PostProcessorBefore

7.执行init方法

8.执行afterPropertiesSet方法属性赋值

9.PostProcessorAfter(AOP)

10.开始销毁Disposiable接口andDestory方法

image-20220513225328756

9.如何拿到Spring的context

实现相关aware接口,通过get获取相关对象

BeanFactoryAware接口
​
ApplicationAware
​
EnvionmentAware

10.Spring循环依赖

几种场景:单例(三级缓存)、多例(无法解决),属性依赖,构造器依赖(无法解决)

代理对象和普通对象的关系 : 代理对象.target = 普通对象

1.创建普通对象 直接存放到单例池

2.通过creatingSet判断出现循环依赖,执行提前AOP,将代理对象放到二级缓存中,保证代理对象只有一个

3.将不完整的代理对象放到三级缓存中,打破循环

注意:对象池中存放的是代理对象而不是普通对象

出现了循环依赖:提前AOP,实例化之后进行AOP

CreatingSet用来判断是否存在循环依赖

一级缓存:单例池 存储最终生成的bean对象

二级缓存:早期Bean earlySingletonObject 存放代理对象:保证多个Bean依赖的同一个Bean时,代理对象只有一个

三级缓存:单例工厂singletonFactory 返回工厂,用于真正创建Bean,打破循环依赖,把没有填充属性的bean放到map里

singletonFactory.put("service",AService普通对象)

image-20220513233724750

11.解决循环依赖

1.@lazy生成假的对象

2.默认开启循环依赖 Spring 2.6.x版本默认关闭循环依赖 applicationContext.setAllowCircularReferences(true);

12.@Autowired和@Resource有什么区别

13.@componentScan注解

扫面指定路径下的包,递归下面的子包,通过过滤器控制可以扫描的文件

默认扫描当前类所在的包,规则是凡是@Component@Repository@Service@Controller都注册到容器中

这四个注解本质上没有任何区别

14.@Import注解

解决了什么问题:批量导入其他来源的bean,例如jar包或其他模块

import注解的value有常见的几种类型

  1. value为普通的类
  2. value为@Configuration标注的类
  3. value为@CompontentScan标注的类
  4. value为ImportBeanDefinitionRegistrar接口类型
  5. value为ImportSelector接口类型
  6. value为DeferredImportSelector接口类型

ImportSelect接口

用来做指定导入,和@Configuration一起使用,所有的Enablexxx注解都是靠他实现的,例如EnableAsync,EnableTransactionManager

使用方法

1.自定义一个注解,例如Enablexxx

2.Enablexxx上添加注解,@Import(MyImportSelector.calss)

3.MyImportSelector类实现ImportSelect接口,实现类选择的逻辑

15.@Conditional注解

实现在不同添加下加载不同的bean对象

@Conditional({WindowCondition.class})

@ConditionContext更具不同条件加载不同配置(包括@Component,@Configuration,@ComponentScan,@Import,@Bean)

public class WindowCondition implements Condition {
    public boolean matches(ConditionContext,AnnotatedTypeMetadata meta)
}

16.父子容器

SpringMVC中经常出现父子容器的问题

为了使依赖隔离,层次分明,给Spring容器设置父子关系

父容器包含Dao和Service,子容器包含Controller,子容器可以访问到父容器中的Bean对象

17.@Value注解

解决什么问题

通常使用@Value注解实现对配置类属性的赋值,例如数据库的连接信息,常常和@PropertySource注解配合引入配置文件

// 引用配置文件
@Component
@PropertySource({"classpath:db.properties"})
public class DbConfig {
}
// 使用配置文件中的值
@Value("${jdbc.username,"admin"}")

数据源有哪些

数据来源:从Environment中获取,解析placeholder占位符

  1. 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
  2. Environment内部会访问MutablePropertySources来解析
  3. MutablePropertySources内部有多个PropertySource,此时会遍历PropertySource列表,调用PropertySource.getProperty方法来解析key对应的值

如何自己配置数据来源,例如从数据库解析配置,通过context.getEnvironment().getPropertySources() 进行赋值

    /*下面这段是关键 start*/
    //模拟从db中获取配置信息
    Map<String, Object> mailInfoFromDb = DbUtil.getMailInfoFromDb();
    //将其丢在MapPropertySource中(MapPropertySource类是spring提供的一个类,是PropertySource的子类)
    MapPropertySource mailPropertySource = new MapPropertySource("mail", mailInfoFromDb);
    //将mailPropertySource丢在Environment中的PropertySource列表的第一个中,让优先级最高
    context.getEnvironment().getPropertySources().addFirst(mailPropertySource);
    /*上面这段是关键 end*/

如何自动刷新

@RefreshScope注解可以实现value自动刷新

18.spring Cache

使用流程,开启Cache,配置Cache

@EnableCaching:启用缓存功能

@Cacheable:赋予缓存功能

@Cacheable可以标记在一个方法上,也可以标记在一个类上,通过value指定使用cache的名称,具体的缓存有CacheManager对象指定

通过Spel表达式控制使用cache的场景,具体有Key参数传入,例如哪个方法,哪个入参

@Cacheable(cacheNames = {"cache1"}, key = "#root.target.class.name+'-'+#page+'-'+#pageSize")

缓存清理

通过@CacheEvict注解,指定哪个缓存的哪个key进行清理

@CacheEvict(cacheNames = "cache1", key = "'findById'+#id") //@1