不定期更新到个人网站:https://www.upheart.top/
从spring5开始新增了null-safety注解@NonNull
,@Nullable
,@NonNullFields
,@NonNullApi
,来防止出现运行时的空指针异常
@NonNull
使用在字段,方法参数或方法的返回值。表示不能为空
@NonNullFields
使用在包级别,并且是该包下类的字段不能为空
当一个类中的字段使用了太多的NonNull时可以考虑使用@NonNullFields注解,使用该注解必须先定义一个名为package-info.java
的文件,例如:
@NonNullApi
@NonNullFields
package org.springframework.mail;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
@Nullable
使用在字段,方法参数或方法的返回值。表示可以为空
当一个类的包被@NonNullFields或@NonNullApi注解,而我们想要从包级别指定的非null约束中免除某些字段,方法,返回值时可以使用@Nullable
@NonNullApi
和@NonNullFields
一样使用在包级别,但是区别是它作用是该包下的类的方法参数和返回值不能为空
当一个类中的方法参数和返回值使用了太多的NonNull时可以考虑使用@NonNullFields注解,使用该注解必须先定义一个名为package-info.java
的文件,形式同上
在容器内部,每个bean的定义可以被表示为BeanDefinition,通过BeanDefinition可以获得bean的很多信息包括:包名,bean的作用域,生命周期,bean的引用和依赖等
通过ApplicationContext的getBeanFactory()方法,能够获得DefaultListableBeanFactory的实现。实现类有两个方法可以将用户自定义的bean注册到Spring容器中。两个方法是:
其中,registerSingleton通过bean名字,和bean实例在注册。registerBeanDefinition则通过bean名字和beanDefinition来注册
如果想自己构建beanDefinition比较麻烦,他需要实现的方法比较多
使用方式如下:
// create and configure beans
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//从ApplicationContext 中获取 DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory=(DefaultListableBeanFactory)context.getBeanFactory();
BeanDefinition beanDefinition=beanFactory.getBeanDefinition("daoA");
System.out.println(beanDefinition.getBeanClassName());
DaoC daoC=new DaoC();
//将daoC注册到spring容器中
beanFactory.registerSingleton("daoC", daoC);
//也可以注册beanDefinition
// beanFactory.registerBeanDefinition();
//从Spring容器中获取到刚刚手动注册的bean
System.out.println(context.getBean("daoC"));
Bean的命名
Bean可以通过id和name属性来命名,id是全局唯一的,name可以有多个,可以通过逗号,分号或者空格来区分多个name
当然id和name不是必须的,如果你不指定bean的标志符,SPring容器会为你自动分配一个。这种没有名字的Bean一般用在内部bean或者自动装载的情况
bean命名一般采用开头小写的驼峰模式。如:accountManager, accountService, userDao, loginController等
使用
也可以为bean定义别名,一般用在大型系统中,将引入的外部bean和自身的系统命名保持一致
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
如果你使用Java配置,那么可以使用@Bean来为Bean命名
Bean的实例化
实例化bean一般有3种方式,通过构造函数实例化,通过静态工程方法实例化,通过实例的工厂方法实例化
一般来说我们使用构造函数在Spring容器中创建bean。这个和用new创建bean并将其注入到Spring容器中在本质上是一样的
工厂方法用的比较少,如果我们需要每次生成一个新的对象时候,就可以考虑使用工厂方法了
构造函数实例化
在Spring中,bean的构造函数跟普通构造函数没有什么区别,最常见的就是无参构造函数:
<bean id="exampleBean" class="examples.ExampleBean"/>
当然也可以创建带参数的构造函数:
静态工厂方法
静态工厂方法通过class属性指定包含静态工厂方法的类,使用名为factory-method的属性指定工厂方法本身的名称
这个指定的方法用来返回需要的实例
<bean id="clientService"
class="com.flydean.services.ClientService"
factory-method="createInstance"/>
ClientService的代码如下:
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
/**
* 工厂方法,必须是静态方法
* @return
*/
public static ClientService createInstance() {
return clientService;
}
}
大家注意,这里的createInstance方法必须是静态的
实例工厂方法
和静态工厂方法一样,实例工厂方法只不过是使用实例中的方法来创建相应的bean对象
这样在factory-bean定义工厂bean实例,在factory-method中定义需要创建bean的方法:
<bean id="serviceLocator" class="com.flydean.services.DefaultServiceLocator">
bean>
<bean id="serviceA"
factory-bean="serviceLocator"
factory-method="createServiceAInstance"/>
<bean id="serviceB"
factory-bean="serviceLocator"
factory-method="createServiceBInstance"/>
DefaultServiceLocator代码如下:
public class DefaultServiceLocator {
private static ServiceA serviceA = new ServiceA();
private static ServiceB serviceB = new ServiceB();
public ServiceA createServiceAInstance() {
return serviceA;
}
public ServiceB createServiceBInstance() {
return serviceB;
}
}
在Spring中,有六个作用域。分别是singleton,prototype,request,session,application,websocket
除了这六个比较通用的作用域外,Spring3.0开始,添加了thread作用域,spring4.2开始,添加了transaction作用域
Singleton作用域
设计模式大家应该学过,设计模式里面有一个很经典的模式就是Singleton模式。Spring里面的Singleton作用域表明这个定义的bean在整个Spring容器中只有一个实例。任何对这个bean的请求都会返回这个唯一的实例
Singleton作用域是Spring中bean的默认作用域,所以,Singleton的bean可以如下定义:
<bean id="beanA" class="com.flydean.beans.BeanA" scope="singleton"/>
<bean id="beanB" class="com.flydean.beans.BeanB" />
上面两个bean都是singleton作用域
Prototype作用域
Prototype也是设计模式中一个很经典的模式。Prototype也被很多人也叫他多例模式,就是说可以创建出很多个类的实例
在Spring容器中,如果一个bean被定义为Prototype,那么,每次通过getBean()方法来获取这个bean都会返回一个新的bean实例
下面展示了如何定义一个prototype的bean:
<bean id="BeanC" class="com.flydean.beans.BeanB" scope="prototype"/>
因为prototype是多例的模式,所以Spring不负责该bean的整个生命周期,一旦bean被创建,交给client使用,Spring就不会再负责维护该bean实例
如果在Prototype bean上面配置了生命周期回调方法,那么该方法是不会起作用的。客户端需要自己释放该bean中的资源
要让Spring容器释放原型作用域bean所拥有的资源,可以使用自定义bean post-processor,用来处理bean的资源清理
某种意义上Spring的Prototype相当于java中的new方法
web 作用域
Request, Session, Application, 和WebSocket作用域仅在使用web的Spring ApplicationContext实现中,如果将这些作用域同Spring正常的IOC容器一起使用,则会报错:IllegalstateException
配置web作用域的方式和普通的应用程序稍有不同。Web程序需要运行在相应的Web容器中,通常我们需要将程序入口配置在web.xml中
如果你使用了Spring MVC的DispatcherServlet,那么不需要做额外的配置,因为DispatcherServlet已经包含了相关的状态
下面是注册RequestContextListener的例子:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
listener-class>
listener>
...
web-app>
如果Listener不能注册,那么可以注册RequestContextFilter,如下所示:
<web-app>
...
<filter>
<filter-name>requestContextFilterfilter-name>
<filter-class>org.springframework.web.filter.RequestContextFilterfilter-class>
filter>
<filter-mapping>
<filter-name>requestContextFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
...
web-app>
通过配置DispatcherServlet, RequestContextListener, 和 RequestContextFilter ,我们就可以在相应的请求服务中调用相应范围的bean
属性赋值:Bean的属性上使用@Value注解:必须是由容器调用构造器创建的bean这些@Value中的属性值才会被注入,自己显示写了new方法的@Value属性就不起作用了,因为自己写了new方法则实例不是由容器创建
public class Person {
@Value("张三丰")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${property.address}")
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
可以通过@Value注入基本数据类型、字符串、SpEL表达式(#{})、也可获取运行环境变量(${}),即properties文件中的变量值,在之前采用配置的方式的时候,需要使用
@Configuration
@PropertySource("classpath:config.properties")
public class MainConfig {
@Bean
public Person person() {
return new Person();
}
}
配置文件中的值也可以通过applicationContext来获取:因为容器加载时会将这些配置的键值对存放在环境变量中
public class MainTest {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
@Test
public void test1() throws Exception {
ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("property.address");
System.out.println(property);
context.close();
}
}
@Scope设置组件作用域
@Configuration
public class MainConfigA {
@Bean("person")
@Scope("singleton")
public Person person(){
return new Person("张三丰",25);
}
}
@Scope的值:
singleton:单例,默认值,每次都返回同一个对象,该对象是在容器启动的时候创建的,在使用的时候直接从容器中获取
prototype:多实例,每次都新创建一个对象,对象不会在容器启动时创建,而是在获取对象时创建,每次获取都创建一个新对象
request:一个请求范围内只创建一个对象
session:一次会话范围内只创建一个对象
@Scope注解也可以在组件的类名上标注:
@Component
@Scope("prototype")
public class Person {
public Person() {
System.out.println("Person Constructor");
}
}
@Lazy注解实现组件Bean的懒加载
单实例的Bean对象默认在容器启动的时候创建,可以设置懒加载使Bean的创建时机延后至第一次获取的时候创建和初始化,懒加载仅作用于单实例(singleton)的Bean
@Configuration
@ComponentScan(value="com.bdm",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM,classes = {BdmTypeFilter.class})})
public class MainConfig {
@Bean
@Lazy
public Person person() {
return new Person("lisi", 12);
}
}
@Conditional:按照条件动态注册Bean
使用@Conditional注解需自定义Condition:实现Condition接口
public class MyCondition implements Condition {
// context:判断条件使用的上下文
// metaData:使用该注解的类的注解信息
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 运行时的信息以及环境变量、虚拟机的变量
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.toLowerCase().contains("Windows"))
return true;
return false;
}
}
容器Bean中使用@Conditional注解:使person类仅会在windows系统中才会创建
@Configuration
public class MainConfig {
@Bean
@Conditional({MyCondition.class})
public Person person(){
return new Person("张三",12);
}
}
@Conditional注解可以标注在配置类上也可以标注在@Bean注解的方法上,作用的范围不同,标注在类上时影响整个容器Bean
@Conditional的值是一个Condition类型的数组,Condition是一个接口,有一个matches方法,该方法中有两个参数:
ConditionContext:通过此类型的参数可以获取运行环境、Bean工厂等信息
AnnotatedTypeMetadata:通过此类型的变量可获取到使用@Conditional注解的类的注解信息
@Configuration
@ComponentScan(value = "com.bdm", useDefaultFilters = false, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})
public class MainConfig {
@Bean
public Person person() {
return new Person("lisi", 12);
}
}
@ComponentScan注解的value属性指定了扫描的包,表示此包中使用了@Controller、@Service、@Repository、@Component等注解的类会被纳入Spring容器的管理;excludeFilters属性表示该包中哪些类别的类不会被纳入Spring容器管理,它的属性值是一个数组;includeFilters属性表示指定的包中哪些类别的类会被纳入到Spring容器管理,注意要同时设置useDefaultFilters的属性为false
JDK1.8之后一个容器类可以使用多次@ComponentScan注解,JDK1.8之前则可以使用@ComponentScans实现这个需求
@Configuration
@ComponentScans(value = {@ComponentScan(value = "com.bdm", useDefaultFilters = false, includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})}), @ComponentScan(value = "com.bdm", excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})})})
public class MainConfig {
@Bean
public Person person() {
return new Person("lisi", 12);
}
}
如果通过@ComponentScan设置的包有包含关系时,同一个被纳入Spring容器的Class会创建多少个bean实例呢?答案是1个,因为通过包扫描的方式创建的bean实例的id是类名的首字母小写,spring在创建bean时会先判断该id的bean有无创建,已创建则不再创建,否则才创建;那如果除了包扫描中有该类,同时@Bean注解的方法中也有该Class的实例,这个时候会创建几个bean实例呢?这个要看@Bean注解的方法的方法名或者通过@Bean的name属性为该实例指定的id是否和类名的首字母小写后相同,相同则仅创建一个,否则创建多个,说明spring容器在创建bean时会先检查该id的bean是否已经存在,不存在则创建,否则不创建
FilterType的值:
FilterType.ANNOTATION:指定注解注解的类
FilterType.ASSIGNABLE_TYPE:指定类型的类
FilterType.ASPECTJ:使用AspectJ表达式过滤
FilterType.REGEX:使用正则表达式过滤
FilterType.CUSTOM:自定义过滤规则
自定义过滤规则
自定义规则类需实现TypeFilter接口:
public class BdmTypeFilter implements TypeFilter{
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//注解
AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
//路径
Resource resource = metadataReader.getResource();
//类名
ClassMetadata classMetadata = metadataReader.getClassMetadata();
String className = classMetadata.getClassName();
if(className.contains("action"))
return true;
MetadataReader metadataReader1 = metadataReaderFactory.getMetadataReader("");
return false;
}
}
MetadataReader:获取正被扫描的类的信息,包括该类的注解信息、路径信息、类名信息等
MetadataReaderFactory:获取MetadataReader
使用自定义过滤规则:type=FilterType.CUSTOM
BeanPostProcessor:是一个接口,bean的后置处理器(实际是在bean的初始化前后执行操作)
public class MyBeanPostProcessor implements BeanPostProcessor{
/**
* bean是容器调用构造器创建的实例
* beanName是实例的id
* 在所有初始化方法之前调用
*/
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
/**
* bean是容器调用构造器创建的实例
* beanName是实例的id
* 在所有初始化方法之后调用
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
将MyBeanPostProcessor纳入到容器中:
@Configuration
@Import(MyBeanPostProcessor.class)
public class LifeCycleConfig {
@Bean(initMethod="init0",destroyMethod="destroy0")
public Car car(){
return new Car();
}
}
这样该容器中的所有bean在初始化前后都会执行该后置处理器中的逻辑,即使未定义初始化方法这些逻辑也会执行的,由此可以猜测:BeanPostProcessor可能是面向切面的
通过查看源码,整个逻辑是这样的:
在调用构造器之后,会为对象的所有属性赋值,赋值完成后会调用初始化方法,但是在调用初始化方法时会进入一个切面,完成在初始化的前后执行BeanPostProcessor的逻辑:容器会遍历所有的BeanPostProcessor并依次执行每个后置处理器的前置方法(postProcessBeforeInitialization),直至全部执行完或者其中一个该类方法返回null,然后完成Bean的初始化,初始化完成后,容器会继续遍历执行所有的BeanPostProcessor的后置方法(postProcessAfterInitialization),直至全部执行完或者其中一个该方法返回null
JDK1.8之后interface也可以有方法体了,子类也可以不实现接口中已有方法体的方法:
public interface BeanPostProcessor {
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
BeanPostProcessor在Spring底层的使用:
1.组件通过实现ApplicationContextAware接口获取IOC容器,其实是ApplicationContextAwareProcessor在起作用
@Component
public class ContextBean implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
}
怎么获取到的IOC容器呢?容器在调用Bean的构造器并给所有属性赋值之后,在执行所有初始化方法之前和之后,会执行一些BeanPostProcessor接口的子类中的逻辑,比如ApplicationContextAwareProcessor,在执行ApplicationContextAwareProcessor的postProcessBeforeInitialization()方法时,会判断Bean是否实现了ApplicationContextAware接口,然后注入相应的ApplicationContext容器给Bean使用
2.对组件的属性进行校验的BeanValidationPostProcessor
3.InitDestroyAnnotationBeanPostProcessor:扫描Bean中标注了@PostConstruct和@PreDestroy注解的方法并执行,这也就是为什么@PostConstruct标注的初始化方法在其他初始化方法之前执行
4.AutowiredAnnotationBeanPostProcessor:为bean中标注了@Autowired注解的属性赋值,这就是为什么可以在init()方法中使用@Autowired注解的属性的原因,因为在调用init方法之前会将这些属性的值注入
5.AsyncAnnotationBeanPostProcessor:使标注了@Async的方法异步执行
Bean的生命周期:bean创建->初始化->销毁的过程,由容器来管理,容器会在bean进行到当前生命周期时调用我们自定义的初始化和销毁方法
1、通过@Bean的属性指定初始化和销毁方法:这两个方法必须是无入参的
public class Car {
public Car(){
System.out.println("构造");
}
public void init(){
System.out.println("初始化");
}
public void destroy(){
System.out.println("销毁");
}
}
@Configuration
public class LifeCycleConfig {
@Bean(initMethod="init",destroyMethod="destroy")
public Car car(){
return new Car();
}
}
对于单实例的Bean来说:容器启动时就会调用该Bean的构造器和初始化方法,销毁方法会在容器关闭的时候调用
对于其他类型的Bean来说:容器会在获取Bean时调用完构造器之后调用其初始化方法,但是容器不会销毁多实例Bean
public class MainTest {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
@Test
public void test1(){
context.close();
}
}
注意上面的context.close()方法并不是在ApplicationContext接口中定义的,而是在其子类中定义的
这样我们就可以在Bean的初始化和销毁时执行一些逻辑,比如数据源的开启和关闭
2、让Bean实现InitializingBean和DisposableBean两个接口
public class Car implements InitializingBean,DisposableBean{
public Car(){
System.out.println("构造");
}
public void afterPropertiesSet() throws Exception {
System.out.println("初始化");
}
public void destroy() throws Exception {
System.out.println("销毁");
}
}
这样容器在调用完构造器之后就会接着调用该Bean的afterPropertiesSet方法完成初始化,如果为单例Bean,在容器关闭时会调用destroy方法执行销毁逻辑
3、使用@PostConstruct和@PreDestroy注解
public class Car {
public Car() {
}
@PostConstruct
public void init() {
}
@PreDestroy
public void destroy() {
}
}
@PostConstruct:在执行完构造器并且对象的所有属性被赋值之后调用
@PreDestroy:在对象销毁之前调用,相当于销毁前的通知方法
在Bean创建和初始化时的执行顺序:构造器->@PostConstruct注解的方法->InitializingBean接口的afterPropertiesSet->@Bean的initMethod属性指定的方法
在Bean销毁时的调用顺序:@PreDestroy注解的方法->DisposableBean接口的destroy方法->@Bean的destroyMethod属性指定的方法
FactoryBean是一个接口,子类需实现其三个方法:
getObject():获取对象,必须重写
getObjectType():获取对象类型,必须重写
isSingleton():是否单例,可以重写
public class MyPersonFactoryBean implements FactoryBean<Person> {
public Person getObject() throws Exception {
return new Person("小吴",23);
}
public Class<?> getObjectType() {
return Person.class;
}
/**
* true:单例
* false:每次获取都重新创建新实例
*/
public boolean isSingleton() {
return true;
}
}
容器中注册工厂Bean:
@Configuration
public class MainConfig {
@Bean
public MyPersonFactoryBean myPersonFactoryBean(){
return new MyPersonFactoryBean();
}
}
测试会发现在容器中获取id为myPersonFactoryBean的Bean的实例的类型是Person:说明将工厂Bean注册后获取的对象其实是工厂Bean的getObject()方法返回的实例,那如果想获取MyPersonFactoryBean类型的实例呢?在id的前面加个&即可
在Spring中有个Aware接口
Spring中很多接口继承于该接口,该接口的子接口可以为Spring容器中的组件提供Spring自带的一些组件,比如ApplicationContext、BeanFactory等,只需要实现XxxAware接口即可,比如容器中的组件通过实现ApplicationContextAware接口来获取ApplicationContext对象
public interface ApplicationContextAware extends Aware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
@Component
public class ContextBean implements ApplicationContextAware{
private ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
组件Bean仅仅是实现了接口,接口中有个setXxx(Xxx xxx)的方法,组件Bean实现的接口并不会为相应的Xxx赋值,那么这些值是怎么注入到组件Bean中的呢?是Spring通过回调的方式注入的,也就是说Spring在发现组件Bean实现了XxxAware接口时,会调用其setXxx(Xxx xxx)方法将xxx(ApplicationContext、BeanFactory等)注入到相应的组件bean中
常用的Aware子接口:BeanNameAware(查找当前bean的名字)、ApplicationContextAware(获取当前IOC容器)、BeanFactoryAware(获取beanFactory)、EmbeddedValueResolverAware(Spring内置的值解析器,可以解析出占位符、SpEL表达式等)
@Component
public class ContextBean implements EmbeddedValueResolverAware {
private StringValueResolver resolver;
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String resolveStringValue = resolver.resolveStringValue("你好:${os.name},我今年#{2*15}岁了");
System.out.println(resolveStringValue);//你好:Windows 10,我今年30岁了
this.resolver = resolver;
}
}
通过${}获取系统环境变量,#{}则可以动态计算一些值
之所以能通过实现ApplicationContextAware等接口的方式获取到ApplicationContext等Spring的底层组件,是因为一些Bean的后置处理器在起作用,比如ApplicationContextAwareProcessor,在这些bean的后置处理器中会回调容器组件bean中的setXxx(Xxx xxx)方法将相应的Spring组件注入,每个XxxAware都有对应的XxxAwareProcessor后置处理器,这些后置处理器会在创建完bean之后再为bean的属性赋值或者注入相应的组件
@Import快速导入一个组件
@Import:值是一个Class[],容器会注册被导入的类,可用来导入第三方的类对象,或者一个包下仅有极少数的类需要被纳入IOC容器管理时,导入对象的id默认为组件的全类名
@Configuration
@Import({Student.class,Teacher.class})
public class MainConfig {
@Bean
@Conditional({MyCondition.class})
public Person person(){
return new Person("张三",12);
}
@Bean
public Person person1(){
return new Person("李四",13);
}
}
ImportSelector:是一个接口,子类需实现其selectImports方法,该方法的入参是AnnotationMetadata(可获取当前标注@Import注解的类的所有注解信息),返回需要导入的组件的全类名数组,不能返回null,否则会报空指针异常
public class MyImportSelector implements ImportSelector{
public String[] selectImports(AnnotationMetadata metadata) {
return new String[]{"com.bdm.modle.Teacher"};
}
}
使用ImportSelector:可实现有条件的导入
@Configuration
@Import({Student.class,MyImportSelector.class})
public class MainConfig {
@Bean
@Conditional({MyCondition.class})
public Person person(){
return new Person("张三",12);
}
}
此时容器中导入的类除了Student之外,还包括MyImportSelector的selectImports方法中返回的数组
ImportBeanDefinitionRegistrar:也是一个接口,子类需实现其registerBeanDefinitions方法,该方法的入参是AnnotationMetadata(获取类的注解信息)和BeanDefinitionRegistry(调用其registerBeanDefinition方法注册Bean),通过实现此接口可以使容器手工注册组件
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar{
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean flag = registry.containsBeanDefinition("person");//如果容器中含有person
if(flag) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(Teacher.class);
//指定bean的id
registry.registerBeanDefinition("teacher", beanDefinition);
}
}
}
@Configuration
@Import({Student.class,MyImportBeanDefinitionRegistrar.class})
public class MainConfig {
@Bean
@Conditional({MyCondition.class})
public Person person(){
return new Person("张三",12);
}
}
@Autowired:
默认先按照类型去容器中找相应的组件(applicationContext.getBean(Xxx.class)),若只找到一个则进行装配,若找到多个则再按照属性的名称作为id找对应的组件(applicationContext.getBean(“beanId”));
另外可使用@Qualifier指定属性Bean的id进行装配,如果没有找到指定的属性bean,则会报NoSuchBeanDefinitionException的异常
如果想在找不到属性bean的时候不让程序出错,则可以设置@Autowired的required属性为false:@Autowired(required=false),此时则在容器中有该属性bean时装配,无则不装配
@Service
public class BookService {
@Autowired
@Qualifier("bookDao")
private BookDao bookDao;
}
@Configuration
@ComponentScan({"com.bdm.service","com.bdm.dao"})
public class MyAutowireConfig {
@Bean
public BookDao bookDao2(){
return new BookDao();
}
}
此时该配置类容器中会有两个BookDao实例,一个是通过自动扫描创建的,一个是通过@Bean注解创建的,通过自动扫描创建的bean的id默认为类名的首字母小写,通过@Bean注解创建的id则为方法名
使用Spring提供的@Primary注解指定首选装配的Bean:在容器中有多个该类型的bean时,优先装配该bean
@Configuration
@ComponentScan({"com.bdm.service","com.bdm.dao"})
public class MyAutowireConfig {
@Bean
@Primary
public BookDao bookDao2(){
return new BookDao();
}
}
此时在容器中有多个BookDao类型的bean时会优先装配这个由@Primary注解的bean,除非使用@Qualifier明确制定了要装配的bean的id
@Autowired不止可以标注在属性上,也可以标注在构造器、方法、参数上
@Component
public class Student {
private Bike bike;
public Bike getBike() {
return bike;
}
@Autowired
public void setBike(Bike bike) {
this.bike = bike;
}
}
一般标注在Setter方法上,其实可以标注在任何方法上,但是方法一定要有入参
此时,Spring容器在初始化Student对象后就会调用其标注@Autowired的方法,将属性注入进去,注入的方式依然是先根据类型查找,查到多个时再根据参数名匹配。那么问题来了,标注有@Autowired注解的方法是何时调用的呢?如果在被注入的对象创建之前调用肯定是不行的,在从容器中获取Student的实例时该实例就已经有了bike属性值,说明该属性的注入并非在第一次使用时,而是在容器创建bean之后通过后置处理的方式注入的该属性,难道是在容器中所有的Bean都创建完之后才会调用这些@Autowired注解的方法吗?是的,其实是AutowiredAnnotationBeanPostProcessor后置处理器的功劳,在创建完Bean之后会后置处理这些标注有@Autowired注解的方法、属性、入参等
标注在构造器上:注意必须是有参构造器
@Component
public class Student {
private Bike bike;
@Autowired
public Student(Bike bike) {
this.bike = bike;
}
public Bike getBike() {
return bike;
}
public void setBike(Bike bike) {
this.bike = bike;
}
}
默认情况下,Spring在实例化bean时会调用类的无参构造器,但是在类提供了有参构造器时,如果有参构造器上有@Autowired注解,Spring则会调用该有参构造器创建实例,如果没有,则还是调无参构造器(无参构造器存在)。那么问题来了,如果类中既有无参构造,又有@Autowired注解的有参构造,或者有多个@Autowired注解的有参构造时Spring会怎么创建实例呢?在有多个@Autowired标注的构造器时会报错,在仅有一个@Autowired标注的构造器时,会调用@Autowired标注的构造器,如果只有一个有参构造器时,@Autowired可以省略
那么还有一个问题假如容器中的bean没有无参构造器,但有一个标注了@Autowired注解的有参构造器(在只有一个有参构造器时该构造器上的@Autowired可以省略,就是说隐藏带有@Autowired),此时在创建bean的时候就需要注入属性,那就不是AutowiredAnnotationBeanPostProcessor能解决的了,这时会怎么样呢?这时Spring会将这个Bean的创建时机置后,待其他的Bean都创建完之后再创建该Bean,如果两个类中都有@Autowired注解的构造器,且彼此的构造器中的入参相互引用的话则会报错,因为这两个对象都无法创建,报UnsatisfiedDependencyException异常,此时采用@Autowired(required=false)也不可以,原因也很简单,因为在创建对象时都需要检查彼此有没有创建好,而返回的都是false,所以不可以
标注在参数上:作用和标注在方法上是一样的,只不过标注在参数上可以更精确的指明哪些参数需要由Spring注入,哪些不必由Spring注入
@Component
public class Student {
private Bike bike;
private Car car;
public Student(@Autowired Bike bike, Car car) {
this.bike = bike;
this.car = car;
System.out.println("两个参数...");
}
public Bike getBike() {
return bike;
}
public void setBike(Bike bike) {
this.bike = bike;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
容器类中@Bean注解的方法的入参的实例也是从容器中获取的:相当于入参的位置加了@Autowired,此时入参位置的bean必须能在容器中找到,否则会报错,如果不想使其报错,可以使用required=false指明其并非必须的
在IOC容器中的bean的方法的入参中如果有Spring的底层类型,则会自动注入进去:
@Component
public class Car {
public Car(ApplicationContext applicationContext) {
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String string : beanDefinitionNames) {
System.out.println(string);
}
}
}
此处构造器中的ApplicationContext会由容器注入进去
@Resource(JSR250规范)和@Inject(JSR330规范)
这两个是Java规范的注解,而@Autowired是Spring自己的注解
@Resource默认是按照组件的名称(属性名作为id)装配,而不像@Autowired是默认先按照类型装配,使用@Resource时@Primary注解会失效,也可以使用@Resource注解的name属性指定组件,类似于@Autowired结合@Qualifier,但是@Resource不支持required=false,也就是说使用@Resource时必须装配成功,否则会报错:NoSuchBeanDefinitionException
@Service
public class BookService {
//@Autowired(required=false)
//@Qualifier("bookDao3")
@Resource(name="bookDao2")
private BookDao bookDao;
}
使用@Inject时需要导入依赖:javax.inject
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
@Inject的功能和@Autowired的功能一样,也支持@Primary注解,只是少了required=false的属性
@Service
public class BookService {
//@Autowired(required=false)
//@Qualifier("bookDao3")
//@Resource(name="bookDao2")
@Inject
private BookDao bookDao;
}
这三个注解的区别在于:@Autowired是Spring的注解,脱离不了Spring的支持,@Resource和@Inject是java的规范,其他的框架也会支持,但是@Autowired的功能稍微强大一些
这些注解之所以能完成自动装配的功能,是因为Spring提供的后置处理器AutowiredAnnotationBeanPostProcessor,该后置处理器通过解析@Autowired、@Resource、@Inject等注解完成自动装配
AOP:在程序运行期间,动态的将某段代码切入到指定方法运行时的指定时机运行,其实就是动态代理
Spring提供了对AOP很好的支持,使用时需要导入spring-aspects包
切面类:类上需要注解@Aspect,切面类中的方法需要动态感知业务方法的执行情况
@Aspect
public class AopAspect {
@Before("public int com.bdm.aop.MathCalculator.div(int, int)")
public void logStart() {
System.out.println("计算开始");
}
@After("public int com.bdm.aop.MathCalculator.*(int, int)")
public void logEnd() {
System.out.println("计算结束");
}
@AfterReturning("public int com.bdm.aop.MathCalculator.*(..)")
public void logReturn() {
System.out.println("计算结果");
}
@AfterThrowing("public int com.bdm.aop.MathCalculator.div(..)")
public void logException() {
System.out.println("计算异常");
}
}
切面类中的方法也称为通知方法:
前置通知(@Before):在目标方法运行之前运行
后置通知(@After):在目标方法运行之后运行,即使出现异常也会运行
返回通知(@AfterReturning):在目标方法正常返回之后运行
异常通知(@AfterThrowing):在目标方法运行出现异常之后运行
环绕通知(@Around):动态代理,手动推进目标方法的运行
在使用这些注解的时候必须配合切入点表达式,来指明在哪些方法执行之前、之后、返回时、异常时执行,在写切入点表达式时可以使用通配符*表示所有方法或者类,入参可以使用..表示所有参数类型
如果切入点表达式一样的话,则可以使用一个方法抽取切入点表达式,注意该方法的名称可以任意,而且方法体内无需写任何内容,只需要使用@PointCut注解并将切入点表达式标明即可:
@Aspect public class AopAspect {
@Pointcut("execution(public int com.bdm.aop.MathCalculator.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void logStart() {
System.out.println("计算开始");
}
@After("com.bdm.aop.AopAspect.pointCut()")
public void logEnd() {
System.out.println("计算结束");
}
@AfterReturning("pointCut()")
public void logReturn() {
System.out.println("计算结果");
}
@AfterThrowing("com.bdm.aop.AopAspect.pointCut()")
public void logException() {
System.out.println("计算异常");
}
}
本类中使用时可直接写方法名,其他类也想使用此切入点表达式时则需要使用该方法的全类名
容器会将标注有@Aspect注解的类认为是切面类
使用Spring的切面需要开启Spring的切面自动代理,只需要在配置类中加注解@EnableAspectJAutoProxy,Spring中有很多@EnableXxx注解,用来开启一些功能
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public MathCalculator cal(){
return new MathCalculator();
}
@Bean
public AopAspect aspect(){
return new AopAspect();
}
}
配置bean怎么区分哪个bean是切面类呢,它会看哪个类上有@Aspect注解,另外切面方法的执行仅对Spring容器中的bean起作用,对于我们自己new出来的对象是不起作用的,原因也很简单,我们自己创建的bean并没有被spring管理,也就没有为其设置切面方法等
通过JoinPoint对象获取调用目标方法时的信息,比如方法名、参数等,使用returning指定用通知方法的哪个入参接收返回值,使用throwing指定用哪个入参接收异常,另外如果使用JoinPoint,则必须将其放在切面方法入参的第一个位置,否则会报错:
@Aspect
public class AopAspect {
@Pointcut("execution(public int com.bdm.aop.MathCalculator.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void logStart(JoinPoint point) {
String name = point.getSignature().getName();
Object[] args = point.getArgs();
System.out.println(name + "方法计算开始,入参:" + Arrays.asList(args));
}
@After("com.bdm.aop.AopAspect.pointCut()")
public void logEnd(JoinPoint point) {
String name = point.getSignature().getName();
System.out.println(name + "方法计算结束");
}
@AfterReturning(value="pointCut()",returning="obj")
public void logReturn(JoinPoint point,Object obj) {
String name = point.getSignature().getName();
System.out.println(name +"方法的计算结果:" + obj.toString());
}
@AfterThrowing(value="com.bdm.aop.AopAspect.pointCut()",throwing="exception")
public void logException(Exception exception) {
System.out.println("计算异常:"+ exception.getMessage());
}
}
Quartz是个开源的作业调度框架
Quartz核心的概念:scheduler任务调度、Job任务、JobDetail任务细节、Trigger触发器
使用Quartz调度系统的思路就是,首先写一个具体的任务(job),配置任务的触发时间(Trigger),Scheduler很根据JobDetail+Trigger安排去执行此任务
Quartz 定时器的时间设置
时间的配置如下:0 30 16 * * ?
时间大小由小到大排列,从秒开始,顺序为 秒,分,时,天,月,年 *为任意 ?为无限制。由此上面所配置的内容就是,在每天的16点30分启动
Profile:Spring提供的可以根据当前环境(开发、测试、生产),动态的激活和切换一系列的组件的功能,可以使用@Profile注解实现,比如数据源根据环境的切换。@Profile注解用于指定组件在哪个环境下会注册到IOC容器中,若不加该注解则在所有环境下都会注册到容器中:
这里使用了三种不同的方式为属性赋值(@Value或者SpringValueResolver都可以)
@PropertySource("classpath:/config.properties")
@Configuration
public class MyProfileConfig implements EmbeddedValueResolverAware {
@Value("${db.user}")
private String user;
private String driverClass;
@Bean
@Profile("dev")
public DataSource devDataSource(@Value("${db.password}") String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/dev");
return dataSource;
}
@Bean
@Profile("test")
public DataSource testDataSource(@Value("${db.password}") String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
return dataSource;
}
@Bean
@Profile("pro")
public DataSource proDataSource(@Value("${db.password}") String password) throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(user);
dataSource.setPassword(password);
dataSource.setDriverClass(driverClass);
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/pro");
return dataSource;
}
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.driverClass = resolver.resolveStringValue("${db.driverClass}");
}
}
加了环境标识(@Profile)的bean,只有在这个环境被激活时该bean才会被注册到IOC容器中,容器的默认环境是default,即在不设置容器的环境的情况下所有的@Profile(“default”)和不加@Profile注解的bean都会注册到容器中,那么怎么改变容器的环境呢?
1、使用命令行参数在Run Configuration的VM arguments中设置:-Dspring.profiles.active的值
-Dspring.profiles.active=test
此时则容器中只有testDataSource
2、使用代码的方式为容器设置启用环境:
public void test1() throws Exception {
// 1、创建容器对象
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 2、设置容器的环境:此操作要在注册配置bean之前,可以设置多个环境
context.getEnvironment().setActiveProfiles("dev", "test");
// 3、注册配置bean
context.register(MyProfileConfig.class);
// 4、刷新容器
context.refresh();
context.close();
}
注意此时在创建IOC容器的时候必须使用无参构造器创建,因为有参构造器在创建对象时就会注册配置bean并刷新容器,此时即使再设置其容器环境也已经迟了,因为bean已经创建了
也可以将@Profile注解标注在配置类上,此时该配置类只有在与IOC容器环境一致时才会被注册到IOC容器中
@Profile({"test","dev"})
@Configuration
@PropertySource("classpath:/config.properties")
public class MyProfileConfig implements EmbeddedValueResolverAware {
}