Spring
Bean概述
Spring IoC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的;常用的配置元数据有class,name,scope,id,abstract等;
Bean通过id定义唯一标识符(规范:驼峰式),通过name定义一个或多个别名,使用逗号,分号或空格分隔;可以通过id定义的名称引用该bean或在其他bean中使用ref属性引用该bean;
bean 定义本质上是创建一个或多个对象的方法,可以使用class
属性中指定要实例化的对象的类型或类;使用parent属性实现类的继承,
初始化回调:
- 基于XML:使用init-method属性;
- 基于注解:使用@PostConstruct注解;
- 基于代码:使用
销毁时回调:
- 基于XML:使用init-method属性;
- 基于注解:使用@PreDestroy注解;
- 基于代码:使用
三种实例化Bean的方式
基于构造函数实例化
默认使用的实例化方式,即直接通过<bean/>实现实例化,一般还需要使用setter方式进行依赖注入;
基于静态工厂方法实例化
使用class 属性指定包含static工厂方法的类,使用命名属性factory-method指定工厂方法本身的名称,该方法必须是静态的;
基于实例工厂方法实例化
将class属性留空,并在factory-bean属性中指定当前(或父级或祖先)容器中的 bean 的名称,该容器包含要调用以创建对象的实例方法。使用factory-method属性设置工厂方法本身的名称;一个工厂类可以有多个工厂方法;
Bean的加载过程
- 从xml文件或类路径下使用了@Component等注解的类,或是使用了@Configuration注解的配置类中,获取其BeanDefinitions,然后注册到BeanFactory中;
- spring会通过BeanDefinitionReader从不同的方式中获取其BeanDefinitions;
- BeanDefinition就是bean的配置元数据,会读取不同方式中bean的定义信息;
- BeanDefinitionReader获取到BeanDefinition后,spring会通过BeanDefinitionRegistry(单例bean会使用SingletonBeanRegistry注册)将BeanDefinition注册到BeanFactory中去,存储在一个concurrentHashMap中,key为beanName,value为bean的元数据;
BeanFactory
这是一个接口,是spring中一个核心类,用于创建和管理Ico容器中的Bean;其常用的实现类是DefaultListableBeanFactory :这个实现类是默认的实现类,提供了BeanDefinition的存储map,以及Bean实例的存储map;
Bean的实例map定义在FactoryBeanRegistrySupport中;
BeanFactoryPostProcessor
BeanFactory后置处理器,会在BeanFactort创建之后,注册了BeanDefinition但没有进行Bean的实例化之前,执行,即调用postProcessBeanFactory方法,作用为:
- 加载更多的元数据,主要是对配置类进行注册;
- 对bean的元数据进行处理,对beanDefinition进行修改或赋值;
BeanPostProcessor
Bean对象后置处理器:负责对已创建好的bean对象进行加工处理,有两个核心方法:
- **postProcessBeforeInitialization ** : 在创建好bean实例,但是在任何初始化回调执行之前,先执行该方法;
- **postProcessAfterInitialization ** :在创建好bean实例,并且所有的初始化回调都执行完了,再执行该方法。
BeanFactoy的创建过程
创建过程主要与refresh()方法相关,是线程安全的,spring源码翻译为:
//准备为此上下文以进行刷新
prepareRefresh();
// 告诉子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//准备在目前情况下使用的bean工厂(BeanFactory的预准备工作)
prepareBeanFactory(beanFactory);
try {
//允许在上下文子类中对bean工厂进行后处理
postProcessBeanFactory(beanFactory);
//调用在上下文中注册的bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截Bean创建的Bean处理器
registerBeanPostProcessors(beanFactory);
// 为此上下文初始化消息源
initMessageSource();
//为此上下文初始化事件多播器
initApplicationEventMulticaster();
// 在特定上下文子类中初始化其他特殊bean(空方法,留给子类重写使用的方法,在SpringBoot中,会将该方法重写,并且在该方法中创建了嵌入式Servlet容器实例)
onRefresh();
// 检查侦听器bean并注册它们
registerListeners();
// 实例化所有剩余的(非延迟)单例
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布相应的事件
finishRefresh();
}
- 调用prepareRefresh()方法进行预处理
//准备在此上下文中使用的bean工厂
prepareBeanFactory(beanFactory);
//初始化上下文环境中的任何占位符属性源。
initPropertySources();
//验证所有标记为required的属性都是可解析的:
getEnvironment().validateRequiredProperties();
//存储预刷新applicationlistener…
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
-
通过obtainFreshBeanFactory()方法得到BeanFactory对象;
-
对得到的BeanFactory对象进行一些预处理:
postProcessBeanFactory方法是一个空的方法, 子类可以重写这个方法来在BeanFactory创建时执行一些操作;
registerBeanPostProcessors:会获取所有的BeanPostProcessor,然后注册到beanFactory中;
//允许在上下文子类中对bean工厂进行后处理
postProcessBeanFactory(beanFactory);
//调用在上下文中注册的bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截Bean创建的Bean处理器
registerBeanPostProcessors(beanFactory);
-
一些操作
-
finishBeanFactoryInitialization(beanFactory):完成单例Bean的实例化;
BeanFactory 和ApplicationContext的区别
- BeanFactory 提供了基础的访问容器的能力;
- ApplicationContext 属于BeanFactory 的子类,ApplicationContext 具有比BeanFactory更多访问容器的方法,比如对国际化的支持,支持资源的访问,支持事件的传播等;
- 对于执行性能来说,ApplicationContext一次性加载并初始化所有的 bean,所以它的启动过程可能比较慢,但是后续的执行比较快(饿汉式)
- 而BeanFactory 是需要哪个类才去加载那个类,因此BeanFactory占用的系统资源更少,启动更快,但后续的执行可能会慢一些(懒汉式)
FactoryBean与BeanFactory的不同
BeanFactory是一个接口,具体上文;主要分析FactoryBean以及其主要功能:
FactoryBean也是一个接口,是一个工厂Bean,最大的作用是可以让我们自定义Bean的创建过程,spring源码如下:
public interface FactoryBean<T> {
//返回的对象实例
T getObject() throws Exception;
//Bean的类型
Class<?> getObjectType();
//true是单例,false是非单例 在Spring5.0中此方法利用了JDK1.8的新特性变成了default方法,返回true
boolean isSingleton();
}
那么,Factory如何做到让我们自己可以自定义Bean的创建过程呢?
首先,我们要知道,FactoryBean对象也是在spring中也是一个bean,只不过是一个特殊的bean,所以也是由Ioc容器管理的,需要注册到BeanFactory中去;但是特殊在于它可以创建其他的Bean实例,但是这个创建的Bean实例不是由Ioc直接管理的(不需要注册到Ioc容器中),所以通过getBean方法得到的不是beanName不是这个bean对象,而是factoryBean的实例,我们可以通过FactoryBean的getObject方法得到beanName;
通过debug可以发现:
在调用geetBean时,不会向由Ioc直接管理的Bean创建实例一样,直接通过beanName调用getBeanInstance方法创建一个实例;
而是通过Factory来创建实例,创建的过程是由程序员自定义的,通过实现FactoryBean的getObject方法,这个getObject方返回的就是我们需要创建的bean;
最后,通过getBean方法得到bean实例;
IOC概述
IOC控制反转,也称为依赖注入DI,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即它们工作的其他对象)。然后容器在创建bean时注入这些依赖项;简单来说就是不再使用new创建出新的对象,而是通过反射创建新的对象,创建对象交给了BeanFactory;
在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。在项目中可以将dao类,service类对象交给IOC容器管理;
使用org.springframework.context.ApplicationContext
接口实现 Spring IoC 容器,负责实例化、配置和组装 bean;容器通过读取配置元数据来获取关于要实例化、配置和组装哪些对象的指令。配置元数据以 XML、Java 注解或 Java 代码表示;
- 在XML中使用**<bean/>定义配置元数据**,
- 使用Java注解配置元数据:@Autowired,@Primary,@Resource等注解实现;
- 基于Java代码的配置元数据:@Configuration,@Bean,@Import等注解实现;
使用ClassPathXmlApplicationContext类实例化一个IOC容器,使用getBean方法可以通过bean的名称访问IOC容器管理的Bean;
依赖注入
基于构造函数的注入
使用constructor-arg属性指定有参构造函数中的参数,常用的属性有value,ref,type,index等;如果参数不存在歧义,则可以直接注入依赖,否则需要通过以下属性匹配参数位置: 这个方式被注册的类不需要set方法,但需要有参构造函数,而且依赖注入不能为null;
type :指定构造函数参数的类型;
index : 指定构造函数参数的索引,索引从0开始;
基于setter的注入
基于 Setter 的 DI 是通过容器在调用无参数构造函数或无参数static工厂方法来实例化 bean 后调用 bean 上的 setter 方法来完成的,即被注册的类中的属性要有对应的set方法;
sping官网的举例(基于setter):
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
sping官网的举例(基于构造函数):
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
基本类型和类的注入
基本类型和String:直接注入
其他类型如何Date:需要创建Bean对象后注入
<bean id="now" class="java.util.Date" scope="prototype"></bean>
<property name="birthday" ref="now"></property>
命名空间
带有p的命名空间
p-namespace 允许您定义bean元素的属性(代替<property/>元素)来描述协作 bean 或两者的属性值,以下两种bean的注册相同;
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
带有c的命名空间
c-namespace 允许内联属性来配置构造函数参数,代替嵌套constructor-arg元素,bean引用使用尾随-ref实现;
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
自动装配
- 通过xml配置元数据实现:使用属性autowire定义是否自动装配,自动装配的规则:
no:(默认)不自动装配
byName: 按属性名称自动装配。Spring 寻找与需要自动装配的属性同名的 bean;
byType: 如果容器中恰好存在一个属性类型的 bean,则让属性自动装配。如果存在多个,则会抛出异常;
constructor: 类似于byType,但适用于构造函数参数
- 基于注解的自动装配:通过**@autowire**注解实现,其参数的匹配规则为通过类型匹配byType,再通过name匹配byName;可以在属性,方法,构造函数上使用;required=false时,允许该属性为null,否则不可为null,默认为true;
Bean的作用域
singleton:单例模式
IOC容器中Bean都只会在创建容器的时候,创建唯一的一个实例;这个是IOC容器的
配置元数据: scope=“singleton”;
创建时间: IOC容器创建时同时创建;
生命周期: 容器存在bean实例就存在,直到容器被销毁;
作用范围: 每一个应用只有一个该bean的实例,作用范围是一整个应用;
prototype:原型模式
调用Bean时,都返回一个新的实例。即每次调用getBean()时,相当于执行newXxxBean(),调用getBean()才创建一个bean的对象懒加载;
配置元数据: scope=“prototype”;
创建时间: 每次访问bean时;都会创建一个bean;
生命周期: 多例对象的创建与销毁时机不受容器控制,对象有使用就会存在,直到对象不再使用,于new的对象相似;
作用范围: 每次访问bean时;都会创建一个bean;
request
每次HTTP请求都会创建一个新的Bean,同一Session不同的HTTP请求会创建不同bean;
session
同一个HTTP Session共享一个Bean;
application
同一个ServletContext共享一个bean;
websocket
同一个websocket共享一个bean;
Aop概述
AOP的实现原理
动态代理,InvocationHandler接口,Proxy类
//要实现InvocationHandler接口以实现动态代理
public class getProxyClass implements InvocationHandler {
private Object target; //需要代理的对象
public void setTarget(Object target) {
this.target = target;
}
//动态的生成代理类
//代理的是对象的接口
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//InvocationHandler接口的invoke方法,返回
//proxy:代理的真实对象
//method:真实对象需要执行的方法
//args:方法的参数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这里可以执行公共方法
Object result = method.invoke(target, args);
return result;
}
}
AOP的重要名词
目标对象(target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象,需要添加功能的对象;
连接点(JoinPoint)
在主业务对象中,程序执行过程中一个确定的点,比如方法的调用或者是异常抛出,一般来说,在aop中,连接点需要指明主业务对象中的哪个方法,同时需要指明切面切入这个点的位置,比如在这个方法之前,在这个方法之后,或者围绕这个方法前后;
通知(Advice)
在切面对象中,定义了目标对象所需要的方法,通知在aop中定义了切面方法在主业务对象方法执行前或后或环绕执行,以及执行什么操作;
切入点(Pointcut )
用来指定需要将通知使用到哪些地方,即指明主业务对象是哪个,连接点在哪里;
切面(Aspect)
是通知和切入点的组合,一般是一个类,类中存在需要的通知和切入点的方法;
顾问(Advisor)
Advisor 其实它就是 Pointcut 与 Advice 的组合,Advice 是要增强的逻辑,而增强的逻辑要在什么地方执行是通过Pointcut来指定的,所以 Advice 必需与 Pointcut 组合在一起,这就诞生了 Advisor 这个类,spring Aop中提供了一个Advisor接口将Pointcut 与 Advice 的组合起来。
java实现AOP
public void AOPTest() {
//定义目标对象
Target target = new Target();
//创建pointcut,用来拦截UserService中的work方法,即找到连接点
Pointcut pointcut = new Pointcut() {
@Override
public ClassFilter getClassFilter() {
//判断是否是Target类型,即找到目标对象
return a -> Target.class.isAssignableFrom(a);
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
//判断方法名称是否是work,找到目标对象的连接点
return "work".equals(method.getName());
}
@Override
public boolean isRuntime() {
return false;
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false;
}
};
}
};
//创建通知,此处需要在方法之前执行操作,所以需要用到MethodBeforeAdvice类型的通知
MethodBeforeAdvice advice = (method, args, target1) -> System.out.println("AOP:" + args[0]);
//创建Advisor,将pointcut和advice组装起来
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
//通过spring提供的代理创建工厂来创建代理
ProxyFactory proxyFactory = new ProxyFactory();
//为工厂指定目标对象
proxyFactory.setTarget(target);
//调用addAdvisor方法,为目标添加增强的功能,即添加Advisor,可以为目标添加很多个Advisor
proxyFactory.addAdvisor(advisor);
//通过工厂提供的方法来生成代理对象
UserService userServiceProxy = (Target) proxyFactory.getProxy();
//调用代理的work方法
userServiceProxy.work("Spring");
}
springXML实现AOP
<!--自定义类-->
<aop:config>
<!--引入切面-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="pointcutAnno" expression="execution(* xyz.mintop.anno.SqlDaoImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="pointcutAnno"/>
<aop:after method="afterReturning" pointcut-ref="pointcutAnno"/>
</aop:aspect>
</aop:config>
Spring注解实现AOP
//定义该类为一个切面
@Aspect
public class Aspect01 {
//定义通知,切入点和连接点(执行后通知)
@After("execution(* xyz.mintop.anno.SqlDaoImpl.*(..))")
public void afterReturning(){
System.out.println("Before-anno");
}
//定义通知,切入点和连接点(执行前通知)
@Before("execution(* xyz.mintop.anno.SqlDaoImpl.*(..))")
public void before() {
System.out.println("After-anno");
}
//定义通知,切入点和连接点(环绕通知)
@Around("execution(* xyz.mintop.anno.SqlDaoImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("around-before");
//连接点签名信息
Signature signature = jp.getSignature(); //获得签名
System.out.println("signature"+signature);
Object proceed = jp.proceed(); //执行目标方法
System.out.println("around-after");
}
//还有 @AfterThrowing() @AfterReturning() 等
}
//测试类
public class AopTest {
public void test() {
//对应目标对象
Target target = new Target();
//创建AspectJProxyFactory对象
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
//设置被代理的目标对象
proxyFactory.setTarget(target);
//设置标注了@Aspect注解的类,添加一个切面
proxyFactory.addAspect(Aspect01.class);
//生成代理对象
Target proxy = proxyFactory.getProxy();
//使用代理对象
proxy.m1();
proxy.m2();
}
}
连接点
@Pointcut: 用于绑定连接点,可以不用,通知中也可以找到连接点;
通知
@After,@Before,@Around,@AfterThrowing:异常通知, 在方法抛出异常之后 ,@AfterReturning:返回通知, 在方法返回结果之后执行
通知中获取被调方法信息
使用以下两个接口
org.aspectj.lang.JoinPoint //非环绕通知
//或
org.aspectj.lang.ProceedingJoinPoint //环绕通知,继承于JoinPoint
提供访问当前被通知方法的目标对象、代理对象、方法参数等数据;
环绕通知需要 Object target= joinPoint.proceed();
执行目标对象方法,以继续下一个通知;
Spring注解
注解注入在 XML 注入之前执行。因此,XML 配置覆盖了通过这两种方法连接的属性的注释;
使用注解,首先需要在xml配置文件中配置<context:annotation-config/>
以开启注解配置,然后通过<context:component-scan base-package="需要扫描的包路径"/>
配置启用注解配置的类;
@Required
该注释适用于 bean 属性设置方法,即set方法,此注解指示必须在配置时通过 bean 定义中的显式注入属性值或通过自动装配来填充受影响的 bean 属性,即必须对使用了@Required注解的属性,必须赋值;如果受影响的 bean 属性尚未填充,则容器将引发异常
@Value
@Value通常用于注入外部属性,可以使用EL表达式,通常用来导入properties文件中的键值对,也可以引入DB,redis等存储设备中的数据;
EL表达式
语法:
- @Value(${key}) 不指定默认值;
- @Value(${key:default}) 指定默认值,当外部数据中没有该项的引入时,会使用指定的默认值;
数据来源
首先,肯定需要导入一个外部的数据存储文件或数据库,properties文件使用**@PropertySource注解**指定文件的类路径classpath:xxxx.properties
,但这个只是导入静态文件是肯定不够的;
对于db的外部数据存储,spring中有一个重要的接口org.springframework.core.env.Environment
,可以用于配置环境信息,实现类中有两个重要的方法:
-
resolvePlaceholders:这个方法就是解析EL表达式的;
-
getPropertySources:这个方法会返回一种MutablePropertySources对象:
@Override
public MutablePropertySources getPropertySources() {
return this.propertySources;
}
那么MutablePropertySources对象是什么呢?MutablePropertySources对象实现了PropertySources接口,维护了一个propertySourceList,从上面可以知道propertySource是@Value的数据来源,所以这样很明显了,我们可以通过修改MutablePropertySources对象来改变@Value的数据来源了,即只需要将外部数据包装为PropertySource对象,丢到Environment中的MutablePropertySources内部就可以了。
public class MutablePropertySources implements PropertySources {
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
}
具体的包装方法为context.getEnvironment().getPropertySources().addFirst("外部数据对象");
,这个外部对象一般数据结构为map键值对;
动态刷新
springboot中有现成的实现:@RefreshScope注解实现;实现原理与@Scoper注解相关;
@Autowired
这个注解可以用于属性,构造函数,set方法以及多个参数的方法 ,作用是实现依赖的自动装配,会匹配对象类型;默认情况下,当给定注入点没有匹配的候选 bean 时,自动装配会失败,默认行为是将带注释的方法和字段视为指示所需的依赖项,于是可以通过required=false
来说明可以不需要注入;
如果出现多个候选bean,@Autowired需要与@Qualifier或@Primary使用,如果没有,会使用对象名作为最后的回调匹配;
@Nullable
这个注释用于@Autowired方法下的参数上,与required=false
作用相似,但是在多个参数的情况下,可以具体到否个变量;
@Primary
由于按类型自动装配可能会导致多个候选者,因此通常需要对选择过程进行更多控制,指示当多个 bean 是自动装配到单值依赖项的候选对象时,应该优先考虑特定的 bean,即优先自动注入使用了@Primary注解的bean;
@Qualifier
当需要对选择过程进行更多控制时,可以使用 Spring 的@Qualifier
注解,可以将限定符值与特定参数相关联,缩小类型匹配的范围,以便为每个参数选择特定的 bean;还可以@Qualifier
在单个构造函数参数或方法参数上指定注释,官网上的例子:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
}
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
}
说明:具有限定符值的 bean 与main
具有相同值限定的构造函数参数连接;简单来说就是@Qualifier能根据名字进行注入,更能进行更细粒度的控制如何选择候选者,以从同类型的候选者中通过bean的限定符即名称,选择匹配的bean;
@Resource
@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型,使用name属性则通过name进行匹配,如果使用type属性则通过type进行匹配;
这个注解与@Autowired注解的作用相似,但存在着一些不同,主要的区别如下:
- @Resource并不是spring包下个注解,而是javax.annotation.Resource包下的注解,但是spring也支持其使用;
- @Resource是通过bean的名称或类型进行匹配的,而@Autowired是通过bean的类型进行匹配的,遇到同一类型时,@Autowired需要与@Qualifier或@Primary使用,如果没有,会使用对象名作为最后的回调匹配;
@Resource装配顺序:
①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Scope
这个注解的作用是指定bean的作用域,等价于xml文件中bean的scope属性,在ConfigurableBeanFactory接口中定义了spring有的作用域,可以直接使用;
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
常用方法:
- 与@Bean一起使用在方法上:可以指定这个方法注册到容器后的bean作用域;
- 与@Compontent一起使用在类上:可以指定这个类注册到容器后的bean作用域;
@DependsOn
这个注解的作用是指定当前bean依赖的bean,等价于xml文件中的bean的depends-on属性;
·<bean id="bean1" class="" depend-on="bean2,bean3; bean4" />
,通过逗号或分号进行分隔;Depends-On的作用效果:由于通过depends-on指定了bean1的依赖bean,所以在bean1创建之前,它所依赖的bean2,bean3,bean4都会先于bean1创建,与bean在xml文件中声明的次序无关;
常用方法:
- 与@Bean一起使用在方法上
- 与@Compontent一起使用在类上
@ImportResource
作用是在配置类中导入bean定义的配置文件 ,@ImportResource中有两个常用的属性:
@AliasFor("locations")
String[] value() default {};
@AliasFor("value")
String[] locations() default {};
都可以指定资源路径:classpath或classpath* 参考
以classpath:开头:检索目标为当前项目的classes目录
以classpath*:开头:检索目标为当前项目的classes目录,以及项目中所有jar包中的目录,如果你确定jar不是检索目标,就不要用这种方式,由于需要扫描所有jar包,所以速度相对于第一种会慢一些
@Configuration
@Configuration这个注解可以加在类上,让这个类的功能等同于一个bean xml配置文件,即一个Configuration类就是一个xml文件;
@Configuration常常与@Bean搭配使用,但是存在一个问题:类上加不加@Configuration注解的区别:
- 使用了@Configuration注解,由于@Bean的作用域默认是单例的,所以在调用bean时,都会是同一个bean对象;
- 不使用@Configuration注解,@Bean无法保证是单例的,所以在调用bean时,不会是同一个bean对象;
总结:不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个bean注册到spring容器中
@Bean
用于方法上,作用是将bean注入到Ioc容器中,在xml中对应<bean/>
,可以使用value或bame属性指定bean的限定符即名称;默认的话就是将方法名称作为bean名称,将方法返回值作为bean对象,注册到spring容器中。
@AliasFor("name")
String[] value() default {}; //指定bean的id
@AliasFor("value")
String[] name() default {}; //指定bean的id
boolean autowireCandidate() default true; //是否作为其他对象注入时候的候选bean
String initMethod() default ""; //初始化方法
String destroyMethod() default AbstractBeanDefinition.INFER_METHOD; //销毁方法
@Lazy
作用是使当前的bean进行懒加载,即等到需要使用的时候才创建这个bean的实例;等价于xml文件中的lazy-init属性,lazy-init = true:延迟初始化,false:实时初始化;
@Component
通常是用于类上,标注该类是一个组件,将会被当作bean注册到容器中,String value() default "";
用于指定bean的id;@Component有几个衍生的注解,比如:@Controller,@Service,@Repository,三者与@Component没有区别 :
@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层;
@ComponentScan
@ComponentScan用于批量注册bean,这个注解会让spring去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean注册到spring容器容器中。
@Repeatable(ComponentScans.class) //可以同时使用多个,即多个需要扫描的包
public @interface ComponentScan {
@AliasFor("basePackages")
//这三个属性的作用都是指定扫描的包
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
//自定义bean名称生成器,默认BeanNameGenerator
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
//需要扫描包中的那些资源,默认是**/*.class
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
//对扫描的类是否启用默认过滤器
boolean useDefaultFilters() default true;
//过滤器:包含什么
Filter[] includeFilters() default {};
//过滤器:排除什么
Filter[] excludeFilters() default {};
}
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
@ComponentScan的实现过程:
- spring会去找指定的包,同时递归扫描其下的所有子包,并包装为数组;如果没有指定扫描哪个包,就会扫描该注解使用类所在包下个所有包;
- spring根据过滤器规则,获得最后需要的包,并注册到容器中,一般默认是使用默认过滤器的,默认过滤器的规则是将所有包含@Component及其衍生注解的类注册到容器中;
@ComponentScan是通过org.springframework.context.annotation.ConfigurationClassPostProcessor
类进行处理的;这个类在spring中十分十分重要,用于bean的注册;
但是@ComponentScan使用value或basePackages属性存在一个问题,若包被重名了,会导致扫描会失效 ,这个问题我们可以通过basePackageClasses属性解决:
basePackageClasses的使用方式:我们要在需要扫描的包中,添加一个"工具人",一个接口或类,用于作为basePackageClasses的值,@ComponentScan会通过这个接口或类的名称找到需要扫描的包,所以这个接口或类需要在被扫描的包下,位置不能错了;其他实现过程与前两者属性相同;
过滤器
spring源码过滤器定义如下:
@interface Filter {
FilterType type() default FilterType.ANNOTATION; //指定过滤的方式,默认通过注解;
//这两个属性都是过滤的条件值,可以通过这个判断是否被过滤;用于ANNOTATION,ASSIGNABLE_TYPE,CUSTOM方式下;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
//与上述两个作用相似,这个是用于ASPECTJ或REGEX方式下,指定过滤条件;
String[] pattern() default {};
}
public enum FilterType {
ANNOTATION, //注解方式过滤
ASSIGNABLE_TYPE, //指定类型过滤
ASPECTJ, //ASPECTJ表达式方式过滤
REGEX, //正则方式过滤
CUSTOM //用户自定义方式过滤
}
自定义的过滤器需要实现org.springframework.core.type.filter.TypeFilter
接口,重写boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory);
方法;
注意:使用includeFilters或excludeFilters时,注意是否还需要默认的过滤器,过滤的条件是可以并的;
@Import
@Import可以用于导入第三方的bean,可以导入的配置类有@Configuration, ImportSelector, ImportBeanDefinitionRegistrar等配置类,或要导入的常规组件类即@Component;该注解中只有一个属性Class<?>[] value();
用于指定要导入的bean,使用逗号分隔多个类;
@Import({xxx.class,xxx.class}) //xxx为下述的5种中的一种情况
5种value常用的方法:
- value为普通的类
- value为@Configuration标注的类
- value为@CompontentScan标注的类
- value为ImportBeanDefinitionRegistrar接口类型
- value为ImportSelector接口类型 ,返回需要导入的类名的数组,可以是任何普通类,配置类
- value为DeferredImportSelector接口类型 ,DeferredImportSelector可以实现延时导入,以及导入次序;
@Conditional
可以用在任何类型或者方法上面,通过@Conditional注解可以配置一些条件判断,当所有条件都满足的时候,被@Conditional标注的目标才会被spring容器处理;比如根据一定的条件判断这个bean是否可以被注册到容器中,是否解析@Configuration配置类;
public @interface Conditional {
//条件数组,父类为Condition接口
Class<? extends Condition>[] value();
}
Condition接口
这个是一个函数式接口,只有一个方法matches,用于判断所有条件是否满足,返回boolean;
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
// context:条件上下文,ConditionContext接口类型,可以用来获取容器中的bean配置元数据以及bean对象,环境配置信息等;
// metadata:用来获取被@Conditional标注的对象上的所有注解信息,注解类型元数据
ConditionContext接口
public interface ConditionContext {
//返回bean定义注册器,可以通过注册器获取bean定义的各种配置信息
BeanDefinitionRegistry getRegistry();
// 返回ConfigurableListableBeanFactory类型的bean工厂,相当于一个ioc容器对象
@Nullable
ConfigurableListableBeanFactory getBeanFactory();
//返回当前spring容器的环境配置信息对象
Environment getEnvironment();
}
ConfigurationCondition接口
ConfigurationCondition接口继承了Condition接口,是对Condition接口的进一步拓展,可以实现更多的功能:
public interface ConfigurationCondition extends Condition {
//可以控制条件判断在哪个阶段执行;
ConfigurationPhase getConfigurationPhase();
//可以评估条件的各种配置阶段
enum ConfigurationPhase {
PARSE_CONFIGURATION, //解析配置
REGISTER_BEAN //注册bean
}
}
Condition接口无法更加精细的控制使用了@Conidtional接口的配置类,条件判断在什么阶段执行,但是ConfigurationCondition接口可以:使用getConfigurationPhase方法, PARSE_CONFIGURATION,或REGISTER_BEAN;
@Conditional执行流程:
主要说明两个主要过程,其他过程与普通bean的注册一样;Condition接口只可以是这两个阶段都执行条件判断,无法更加灵活控制在其中一个阶段才执行条件判断,但我们可以通过ConfigurationCondition接口实现灵活控制具体在哪一个阶段执行条件判断
- 配置类的解析:spring会通过@Conditional注解配置的条件判断这个配置类是否要被解析,如果不被解析,其产生的其他配置类也不会被解析;
- bean的注册:当配置类解析后,会得到一组配置类的bean以及其他需要注册的bean,spring会通过@Conditional配置的条件判断是否注册当前的bean;
- 完成了前面两个阶段,注册完成的bean可能也会产生其他新的配置类,所以还会不断的循环遍历两个阶段,直达没有新的配置类需要解析,bean需要注册;
@Conditional的执行次序
默认情况下,会根据value中的声明次序先后执行,但是也可以自定义执行的优先级:可以实现PriorityOrdered接口或者继承Ordered接口,或者使用@Order注解,通过这些来指定这些Condition的优先级,Condition接口的默认ordered为1;其中PriorityOrdered接口继承了Ordered接口,区别是PriorityOrdered接口的实现类先于Ordered实现类,都是重写getOrder方法实现指定@Conditional的执行次序;
public interface PriorityOrdered extends Ordered {
}
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
//获取该对象的顺序值:可以自定义,按升序进行排序;
int getOrder();
}