Spring基础
1.Sprint创建bean的几种方法
- 通过反射调用构造方法创建bean对象
- 通过静态工厂方法创建bean对象
- 通过实例工厂方法创建bean对象
- 通过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注解有何差异
- @Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些bean是单例的
- 不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个bean注册到spring容器中
8.Spring生命周期
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方法
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普通对象)
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有常见的几种类型
- value为普通的类
- value为@Configuration标注的类
- value为@CompontentScan标注的类
- value为ImportBeanDefinitionRegistrar接口类型
- value为ImportSelector接口类型
- 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占位符
- 将@Value注解的value参数值作为Environment.resolvePlaceholders方法参数进行解析
- Environment内部会访问MutablePropertySources来解析
- 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