原始链接:https://liayun.blog.csdn.net/article/details/115053350
有用的方法
/**
* 获取获取注解生成的bean
* */
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
/**
* 获取指定bean名称
* */
String[] namesForType = applicationContext.getBeanNamesForType(Person.class);
for (String name : namesForType) {
System.out.println(name);
}
/**
* 获取容器中所有bean的名称
*/
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
/**
* 获取容器中指定bean类的map集合
*/
Map<String, Person> persons = applicationContext.getBeansOfType(Person.class); // 找到这个Person类型的所有bean
System.out.println(persons);
IOC类型
1.组件的注册
@Configuration和@Bean给容器中注册组件
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {
// @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
//bean在IOC容器中的名称就是使用@Bean注解标注的方法名称,即该实例中bean的名称为person
@Bean
public Person person() {
return new Person("liayun", 20);
}
}
2.注解包扫描
@ComponentScan
@Filter
-
type属性:
FilterType.ANNOTATION:按照注解进行包含或者排除
FilterType.ASSIGNABLE_TYPE:按照给定的类型进行包含或者排除;类、接口、实现类、子接口
FilterType.ASPECTJ:按照ASPECTJ表达式进行包含或者排除(不常用)
FilterType.REGEX:按照正则表达式进行包含或者排除(不常用)
FilterType.CUSTOM:按照自定义规则进行包含或者排除(使用该枚举需要有一个实现TypeFilter的类,返回false标识不匹配,true标识匹配)
// 这个配置类也是一个组件
@ComponentScan(value="com.meimeixia") // value指定要扫描的包
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {
// @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("liayun", 20);
}
}
// 带过滤器的包扫描
//excludeFilters:排除扫描的特定包
@ComponentScan(value="com.meimeixia",excludeFilters={
/*
* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:除了@Controller和@Service标注的组件之外,IOC容器中剩下的组件我都要,即相当于是我要排除@Controller和@Service这俩注解标注的组件。
*/
@Filter(type=FilterType.ANNOTATION, classes={Controller.class, Service.class})
}) // value指定要扫描的包
@Configuration // 告诉Spring这是一个配置类
public class MainConfig {
// @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("liayun", 20);
}
}
//includeFilters,禁用掉默认的过滤器
@ComponentScan(value="com.meimeixia", includeFilters={
/*
* type:指定你要排除的规则,是按照注解进行排除,还是按照给定的类型进行排除,还是按照正则表达式进行排除,等等
* classes:我们需要Spring在扫描时,只包含@Controller注解标注的类
*/
@Filter(type=FilterType.ANNOTATION, classes={Controller.class})
}, useDefaultFilters=false) // value指定要扫描的包
3.设置组件的作用域
@Scope属性
-
singleton:表示组件在spring容器中是单实例,是spring的默认值
【spring容器启动时,将单实例组件实例化后会立即加载到spring容器中】
-
prototype:表示组件在spring容器中是多实例
【ioc容器启动并不会去调用方法创建对象放在容器中。每次获取的时候才会调用方法创建对象】
-
request:每次请求都会创建一个新的实例对象,但是同一个请求只能创建一个实例对象,作用域只在spring容器的web容器中
-
session:在同一个session范围内,创建一个新的实例对象,作用域在web容器中
-
application:全局web应用级别的作用域,使用于web环境中,且在web应用有多个spring容器的情况下也只能使用一个同名bean
@Configuration
public class MainConfig2 {
@Scope("prototype") // 通过@Scope注解来指定该bean的作用范围,也可以说成是调整作用域
@Bean("person")
public Person person() {
return new Person("美美侠", 25);
}
}
4.懒加载机制
@Lazy
- @Lazy注解会使得实例化的对象在Spring容器启动的时候并不会加载,而是在第一次使用此bean的时候才会加载,但当你多次获取bean的时候并不会重复加载,只是在第一次获取的时候才会加载,这不是延迟加载的特性,而是单实例bean的特性【配合单实例使用】
5.按照一定的条件注册bean
@Conditional
- 不仅可以添加到类上(Element.TYPE),也可以添加到方法上(Element.METHOD)
- 内含一个继承Condition或者子类型的class对象数组
- Condition是一个接口,需要写一个类实现该接口,并制定匹配原则,返回为true时表示满足实例化条件,false表示不满足实例化条件,带有@Conditional的方法/类会根据实现类制定的匹配规则被ioc容器选择性注册或者不注册
- matches方法中的ConditionContext.getRegistry()返回一个BeanDefinitionRegistry,能获取spring容器中的所有bean
- registerBeanDefinition():向spring容器注册bean对象
- removeBeanDefinition():从spring容器中移除一个bean对象
- getBeanDefinition():查看某个bean的定义信息
- containsBeanDefinition():查看是否包含某个bean
package com.meimeixia.condition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 判断操作系统是否是Linux系统
* @author liayun
*
*/
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:当前标注了@Conditional注解的注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断操作系统是否是Linux系统
// 1. 获取到bean的创建工厂(能获取到IOC容器使用到的BeanFactory,它就是创建对象以及进行装配的工厂)
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
// 2. 获取到类加载器
ClassLoader classLoader = context.getClassLoader();
// 3. 获取当前环境信息,它里面就封装了我们这个当前运行时的一些信息,包括环境变量,以及包括虚拟机的一些变量
Environment environment = context.getEnvironment();
// 4. 获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
// 在这儿还可以做更多的判断,比如说我判断一下Spring容器中是不是包含有某一个bean,就像下面这样,如果Spring容器中果真包含有名称为person的bean,那么就做些什么事情...
boolean definition = registry.containsBeanDefinition("person");
String property = environment.getProperty("os.name");
if (property.contains("linux")) {
return true;
}
return false;
}
}
package com.meimeixia.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import com.meimeixia.bean.Person;
import com.meimeixia.condition.LinuxCondition;
import com.meimeixia.condition.WindowsCondition;
@Configuration
public class MainConfig2 {
@Lazy
@Bean("person")
public Person person() {
System.out.println("给容器中添加咱们这个Person对象...");
return new Person("美美侠", 25);
}
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01() {
return new Person("Bill Gates", 62);
}
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
return new Person("linus", 48);
}
}
6.给容器快速导入一个组件
@import
- 只允许放到类上面(Element.TYPE)
- 属性:class类型数组
- 可以配合@Configuration、ImportSelector、ImportBeanDefinitionRegistrar使用
- 使用方式
- 直接填写class数组以导入类
// 对配置类中的组件进行统一设置 @Conditional({WindowsCondition.class}) // 满足当前条件,这个类中配置的所有bean注册才能生效 @Configuration @Import(Color.class) // @Import快速地导入组件,id默认是组件的全类名 public class MainConfig2 { @Lazy @Bean("person") public Person person() { System.out.println("给容器中添加咱们这个Person对象..."); return new Person("美美侠", 25); } @Bean("bill") public Person person01() { return new Person("Bill Gates", 62); } @Conditional({LinuxCondition.class}) @Bean("linus") public Person person02() { return new Person("linus", 48); } /** * * 结果:把color类导入进spring容器中 * * */ } /******************************************************/ public class Color { }
- importSelector接口,即批量导入
- 方法:String[] selectImports(AnnotationMetadata data);
- 返回值表示需要导入的类(全限定类名)
- AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息
-
/** * 自定义逻辑,返回需要导入的组件 * @author liayun * */ public class MyImportSelector implements ImportSelector { // 返回值:就是要导入到容器中的组件的全类名 // AnnotationMetadata:当前标注@Import注解的类的所有注解信息,也就是说不仅能获取到@Import注解里面的信息,还能获取到其他注解的信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // 在这一行打个断点,debug调试一下 // 方法不要返回null值,否则会报空指针异常 // return new String[]{}; // 可以返回一个空数组 return new String[]{"com.meimeixia.bean.Bule", "com.meimeixia.bean.Yellow"}; } } /*********************************************/ // 对配置类中的组件进行统一设置 @Conditional({WindowsCondition.class}) // 满足当前条件,这个类中配置的所有bean注册才能生效 @Configuration @Import({MyImportSelector.class}) // @Import快速地导入组件,id默认是组件的全类名 public class MainConfig2 { @Lazy @Bean("person") public Person person() { System.out.println("给容器中添加咱们这个Person对象..."); return new Person("美美侠", 25); } @Bean("bill") public Person person01() { return new Person("Bill Gates", 62); } @Conditional({LinuxCondition.class}) @Bean("linus") public Person person02() { return new Person("linus", 48); } }
- 方法:String[] selectImports(AnnotationMetadata data);
- ImportBeanDefinitionRegistrar接口方式,即手工注册bean到容器中
- 方法:registerBeanDefinitions(AnnotationMetadata data,BeanDefinitionRegistry registry);//spring官方动态注册bean所使用的接口
- 所有实现了该接口的类都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其他的bean初始化的,也能被aop、validator等机制处理。
- 需要配合@Configuration和@Import这俩注解,其中@Import注解导入实现了ImportBeanDefinitionRegistrar接口的类,@Configuration注解spring配置文件
-
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * AnnotationMetadata:当前类的注解信息 * BeanDefinitionRegistry:BeanDefinition注册类 * * 我们可以通过调用BeanDefinitionRegistry接口中的registerBeanDefinition方法,手动注册所有需要添加到容器中的bean */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // RootBeanDefinition是BeanDefinition接口的一个实现类 RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); // bean的定义信息 // 注册一个bean,并且指定bean的名称 registry.registerBeanDefinition("rainBow", beanDefinition); } } /************************************************/ package com.meimeixia.bean; public class RainBow { } /*************************************************/ package com.meimeixia.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import com.meimeixia.bean.Color; import com.meimeixia.bean.Person; import com.meimeixia.bean.Red; import com.meimeixia.condition.LinuxCondition; import com.meimeixia.condition.MyImportBeanDefinitionRegistrar; import com.meimeixia.condition.MyImportSelector; import com.meimeixia.condition.WindowsCondition; // 对配置类中的组件进行统一设置 @Configuration @Import({MyImportBeanDefinitionRegistrar.class}) // @Import快速地导入组件,id默认是组件的全类名 public class MainConfig2 { }
- 直接填写class数组以导入类
7.使用FactoryBean向Spring容器中注册Bean
- 为什么需要FactoryBean的注册方式:
- 注册的Bean涉及到复杂的逻辑(一系列的if-else操作、同步操作、代理操作等),使用配置文件导致灵活性严重不足
- 生产一种类型的bean,在创建的过程中再具体实现其功能
- 想获取ColorFactoryBean实例,只需要在获取工厂Bean本身时,在id前面加上&符号即可,例如&colorFactoryBean(Object bean4 = applicationContext.getBean("&colorFactoryBean"))
-
package com.meimeixia.bean; import org.springframework.beans.factory.FactoryBean; /** * 创建一个Spring定义的FactoryBean * T(泛型):指定我们要创建什么类型的对象 * @author liayun * */ public class ColorFactoryBean implements FactoryBean<Color> { // 返回一个Color对象,这个对象会添加到容器中 @Override public Color getObject() throws Exception { // TODO Auto-generated method stub System.out.println("ColorFactoryBean...getObject..."); return new Color(); } @Override public Class<?> getObjectType() { // TODO Auto-generated method stub return Color.class; // 返回这个对象的类型 } // 是单例吗? // 如果返回true,那么代表这个bean是单实例,在容器中只会保存一份; // 如果返回false,那么代表这个bean是多实例,每次获取都会创建一个新的bean @Override public boolean isSingleton() { // TODO Auto-generated method stub return false; } } /***********************************************/ // 对配置类中的组件进行统一设置 @Configuration public class MainConfig2 { @Bean public ColorFactoryBean colorFactoryBean() { return new ColorFactoryBean();//bean的类型为Color,名字为colorFactoryBean } }
8.Bean生命周期以及指定初始化和销毁的方法
@Bean
- 通过@Bean注解指定初始化和销毁方法,且初始化和销毁方法必须是无参方法、bean的销毁方法是在容器关闭的时候被调用的
-
@Configuration public class MainConfigOfLifeCycle { @Bean(initMethod="init", destroyMethod="destroy") public Car car() { return new Car(); } } /*****************************************/ package com.meimeixia.bean; public class Car { public Car() { System.out.println("car constructor..."); } public void init() { System.out.println("car ... init..."); } public void destroy() { System.out.println("car ... destroy..."); } }
-
- 指定初始化和销毁方法的使用场景:对于数据源的管理(配置数据源且在初始化时,对属性进行赋值,销毁的时候对数据源的连接信息进行关闭和清理)
- 初始化和销毁方法调用的时机:
- bean对象的初始化方法调用的时机:对象创建完成,如果对象中存在一些属性,并且这些属性也都赋好值之后,那么就会调用bean的初始化方法。对于单实例bean来说,在Spring容器创建完成后,Spring容器会自动调用bean的初始化方法;对于多实例bean来说,在每次获取bean对象的时候,调用bean的初始化方法
- bean对象的销毁方法调用的时机:对于单实例bean来说,在容器关闭的时候,会调用bean的销毁方法;对于多实例bean来说,Spring容器不会管理这个bean,也就不会自动调用这个bean的销毁方法了。不过,小伙伴们可以手动调用多实例bean的销毁方法。
9.使用InitializingBean和DisposableBean来管理bean的生命周期
- InitializingBean接口的调用时机
- Spring为bean提供了两种初始化的方式,实现InitializingBean接口(也就是要实现该接口中的afterPropertiesSet方法),或者在配置文件或@Bean注解中通过init-method来指定,两种方式可以同时使用
- 实现InitializingBean接口是直接调用afterPropertiesSet()方法,与通过反射调用init-method指定的方法相比,效率相对来说要高点。但是init-method方式消除了对Spring的依赖
- 如果调用afterPropertiesSet方法时出错,那么就不会调用init-method指定的方法了
- afterPropertiesSet方法在属性初始化后执行,同时使用先调用afterPropertiesSet方法,后执行init-method指定的方法
- DisposableBean接口
- 实现DisposableBean接口的bean在销毁前,Spring将会调用DisposableBean接口的destroy()方法
- 多实例bean的生命周期不归Spring容器来管理,这里的DisposableBean接口中的方法是由Spring容器来调用的,所以如果一个多实例bean实现了DisposableBean接口是没有啥意义的,因为相应的方法根本不会被调用
-
package com.meimeixia.bean; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; @Component public class Cat implements InitializingBean, DisposableBean { public Cat() { System.out.println("cat constructor..."); } /** * 会在容器关闭的时候进行调用 */ @Override public void destroy() throws Exception { // TODO Auto-generated method stub System.out.println("cat destroy..."); } /** * 会在bean创建完成,并且属性都赋好值以后进行调用 */ @Override public void afterPropertiesSet() throws Exception { // TODO Auto-generated method stub System.out.println("cat afterPropertiesSet..."); } } /**********************************************************/ @Test public void test02() { // 1. 创建IOC容器 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); System.out.println("容器创建完成"); System.out.println("--------"); // 调用时创建对象 Object bean = applicationContext.getBean("cat"); System.out.println("--------"); // 调用时创建对象 Object bean1 = applicationContext.getBean("cat"); System.out.println("--------"); // 关闭容器 applicationContext.close(); }
10.@PostConstruct注解和@PreDestroy注解
@PostConstruct
- 是Java中的注解
- 修饰一个非静态的void()方法。被@PostConstruct注解修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。被@PostConstruct注解修饰的方法通常在构造函数之后,init()方法之前执行
- 在Spring框架中的执行顺序:
Constructor(构造方法)→@Autowired(依赖注入)→@PostConstruct(注释的方法)→init()方法
@PreDestroy
- @PreDestroy注解修饰的方法会在服务器卸载Servlet的时候运行,并且只会被服务器调用一次,类似于Servlet的destroy()方法。被@PreDestroy注解修饰的方法会在destroy()方法之后
- 在spring框架中的执行顺序:
调用destroy()方法→@PreDestroy→destroy()方法→bean销毁
11.BeanPostProcessor后置处理器
- BeanPostProcessor是一个接口
- postProcessBeforeInitialization()方法:在bean实例化和属性设置之后,自定义初始化方法之前被调用(先于afterPropertiesSet方法执行)
- postProcessAfterInitialization()方法:会在自定义初始化方法之后被调用
- 当容器中存在多个BeanPostProcessor的实现类时,会按照它们在容器中注册的顺序执行
- 作用:
- 可用于bean对象初始化前后进行逻辑增强。
- Spring提供了BeanPostProcessor接口的很多实现类
- AutowiredAnnotationBeanPostProcessor用于@Autowired注解的实现,
- AnnotationAwareAspectJAutoProxyCreator用于Spring AOP的动态代理:就是在AnnotationAwareAspectJAutoProxyCreator后置处理器的postProcessAfterInitialization方法中,即bean对象初始化完成之后,后置处理器会判断该bean是否注册了切面,若是,则生成代理对象注入到容器中
- 除此之外,我们还可以自定义BeanPostProcessor接口的实现类
-
package com.meimeixia.bean; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; /** * 后置处理器,在初始化前后进行处理工作 * @author liayun * */ @Component // 将后置处理器加入到容器中,这样的话,Spring就能让它工作了 public class MyBeanPostProcessor implements BeanPostProcessor, Ordered { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { // TODO Auto-generated method stub System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean); return bean; } @Override public int getOrder() { // TODO Auto-generated method stub return 3; } }
- 带BeanPostProcessor的bean初始化执行流程
- 伪代码实现
populateBean(beanName, mbd, instanceWrapper); // 给bean进行属性赋值 initializeBean(beanName, exposedObject, mbd) { //1.遍历所有BeanPostProcessor对象,然后依次执行所有BeanPostProcessor对象的postProcessBeforeInitiallization方法,直到for循环结束或者有一个返回null applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); // 2.执行自定义初始化,包括initmethod属性指定的方法、@PostConstruct注解标注的方法;实现InittializingBean接口的方法 invokeInitMethods(beanName, wrappedBean, mbd); //3.依次执行所有BeanPostProcessor对象的postProcessAfterInitiallization方法 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); }
- 伪代码实现
- BeanPostProcessor几个常用实现类在Spring中的应用
- ApplicationContextAwareProcessor:作用是可以向组件中注入IOC容器(ApplicationContext)
/** * ApplicationContextAwareProcessor这个类的作用是可以帮我们在组件里面注入IOC容器, * 怎么注入呢?我们想要IOC容器的话,比如我们这个Dog组件,只需要实现ApplicationContextAware接口就行 * * @author liayun * */ @Component public class Dog implements ApplicationContextAware { private ApplicationContext applicationContext; public Dog() { System.out.println("dog constructor..."); } // 在对象创建完成并且属性赋值完成之后调用 @PostConstruct public void init() { System.out.println("dog...@PostConstruct..."); } // 在容器销毁(移除)对象之前调用 @PreDestroy public void destory() { System.out.println("dog...@PreDestroy..."); } /** * 传入的applicationContext(ioc容器)就是通过ApplicationContextAwareProcessor这个类帮我们得到的 * * */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 在这儿打个断点调试一下 // TODO Auto-generated method stub this.applicationContext = applicationContext; } } /**************************************************/ //在ApplicationContextAwareProcessor#postProcessBeforeInitialization方法中通过这种方式传入ioc容器 if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); }
- BeanValidationPostProcessor:作用是用来为bean进行校验操作的
在postProcessBeforeInitialization()方法和postProcessAfterInitialization()方法中的主要逻辑都是调用doValidate()方法对bean进行校验, 只不过在这两个方法中都会对afterInitialization这个boolean类型的成员变量进行判断, 若afterInitialization的值为false,则在postProcessBeforeInitialization()方法中调用doValidate()方法对bean进行校验; 若afterInitialization的值为true,则在postProcessAfterInitialization()方法中调用doValidate()方法对bean进行校验
- InitDestroyAnnotationBeanPostProcessor:作用是用来处理@PostConstruct注解和@PreDestroy注解
postProcessBeforeInitialization{ //首先会找到bean中有关生命周期的注解,比如@PostConstruct注解等 LifecycleMetaData=findLifecycleMetadata(bean.getClass()); //调用metadata的invokeInitMethods()方法,通过反射来调用标注了@PostConstruct注解的方法 metadata.invokeInitMethods(bean,beanName); }
- AutowiredAnnotationBeanPostProcessor类:作用是用于处理标注了@Autowired注解的变量或方法
- ApplicationContextAwareProcessor:作用是可以向组件中注入IOC容器(ApplicationContext)
12.使用@Value注解为bean的属性赋值
@value
- 可以标注在字段(ElementType.FIELD)、方法(ElementType.METHOD)、参数(ElementType.PARAMETER)以及注解上(ElementType.ANNOTATION_TYPE),而且在程序运行期间生效
- 属性:value值
- 使用
- 注入普通字符串
@Value("java") private String name; // 注入普通字符串
- 注入操作系统属性
@Value("#{systemProperties['os.name']}") private String systemPropertiesName; // 注入操作系统属性
- 注入SpEL表达式结果
@Value("#{ T(java.lang.Math).random() * 100.0 }") private double randomNumber; //注入SpEL表达式结果
- 注入其他bean中属性的值
@Value("#{person.name}") private String username; // 注入其他bean中属性的值,即注入person对象的name属性中的值
- 注入文件资源
@Value("classpath:/config.properties") private Resource resourceFile; // 注入文件资源
- 注入URL资源
@Value("http://www.baidu.com") private Resource url; // 注入URL资源
- 注入普通字符串
- @Value中#{···}和${···}的区别
- #{···}:用于执行SpEl表达式,并将内容赋值给属性(@Value(“#{‘Hello World’.concat(‘!’)}”))//调用字符串Hello World的concat方法
- ⋅ ⋅ ⋅ = = : 主 要 用 于 加 载 外 部 属 性 文 件 中 的 值 ( = = @ V a l u e ( " {···}==:主要用于加载外部属性文件中的值(==@Value(" ⋅⋅⋅==:主要用于加载外部属性文件中的值(==@Value("{author.name:meimeixia}"))//属性文件中没有author.name这个属性,那么便向bean的属性中注入默认值meimeixia
#{}与${}可以混用
:但是必须==#{}在外面, = = 在 里 面 【 s p r i n g 执 行 ‘ {}==在里面【spring执行` ==在里面【spring执行‘{}时机要早于
#{},当执行外层的
${}发现内层的
#{}`为空时,会报错】
13.加载配置文件
@PropertySource
- 标注在类上(ElementType.TYPE)
- 属性
- value:string类型数组,表示可以指定多个properties文件
@PropertySource(value={"classpath:/person.properties", "classpath:/car.properties"})
- value:string类型数组,表示可以指定多个properties文件
@PropertySources
- 标注在类上(ElementType.TYPE)
- 属性
- value:string类型数组
- 与@PropertySource配合使用
@PropertySources(value={ @PropertySource(value={"classpath:/person.properties"}), @PropertySource(value={"classpath:/car.properties"}), })
14.注解自动装配组件
@Autowired、@Qualifier、@Primary
@Autowired注解
- 标注在构造函数、方法、参数、属性、注解上
- 属性:required(默认为true)
- 标注后默认是按照类型去容器中找对应的组件,相当于
applicationContext.getBean(类名.class);
@Qualifier注解
- 标注在构造函数、方法、参数、属性、注解上
- @Autowired是根据类型进行自动装配的,需要按名称进行装配,那么就需要配合@Qualifier注解来使用了
@Primary注解
- 使用到@Autowired这个注解,它默认是根据类型Type来自动注入的。但有些特殊情况,对同一个接口而言,可能会有几种不同的实现类,而在默认只会采取其中一种实现的情况下,就可以使用@Primary注解来标注优先使用哪一个实现类
@Resource和@Inject
@Resource
- 该注解默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,那么默认取字段名将其作为组件的名称在IOC容器中进行查找
@Inject
- 该注解默认是根据参数名去寻找bean注入,能识别@Primary注解优先注入,@Inject注解还可以增加@Named注解指定要注入的bean
15.使用自定义组件时引用/注入Spring底层的组件
基本使用:Spring提供了很多xxAware接口,只要实现不同的Aware接口就能引入Spring底层的相关组件
/**
* 以Red类为例来讲解ApplicationContextAware接口、BeanNameAware接口以及EmbeddedValueResolverAware接口
* @author liayun
*
*/
public class Red implements ApplicationContextAware, BeanNameAware, EmbeddedValueResolverAware {
private ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口重写的方法,能够引入ioc容器
*
**/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("传入的IOC:" + applicationContext);
this.applicationContext = applicationContext;
}
/**
* 参数name:IOC容器创建当前对象时,为这个对象起的名字
*
* 实现BeanNameAware接口重写的方法,能够得到bean的名称
*
*/
@Override
public void setBeanName(String name) {
System.out.println("当前bean的名字:" + name);
}
/**
* 参数resolver:IOC容器启动时会自动地将这个String值的解析器传递过来给我们
*
* 实现EmbeddedValueResolverAware接口重写的方法,能够解析由${}和#{}包裹的值
*/
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
String resolveStringValue = resolver.resolveStringValue("你好,${os.name},我的年龄是#{20*18}");
System.out.println("解析的字符串:" + resolveStringValue);
}
}
XxxAware原理
- XxxAware接口的底层原理是由XxxAwareProcessor实现类实现的,也就是说每一个XxxAware接口都有它自己对应的XxxAwareProcessor实现类
- 实现原理源码可参考ApplicationContextAware接口
AOP类型
16.完整AOP注解使用
- 步骤:
- 将切面类和业务逻辑组件(目标方法所在类)都加入到容器中,并且要告诉Spring哪个类是切面类(标注了@Aspect注解的那个类)
- 在切面类上的每个通知方法上标注通知注解,要写好切入点表达式
- 开启基于注解的AOP模式(@EnableAspectJAutoProxy)
- 代码示范
-
/** * ①切面类与业务逻辑组件都加入到容器中 * ③开启基于注解的AOP模式 * * @author liayun * */ @EnableAspectJAutoProxy @Configuration public class MainConfigOfAOP { // 将业务逻辑类(目标方法所在类)加入到容器中 @Bean public MathCalculator calculator() { return new MathCalculator(); } // 将切面类加入到容器中 @Bean public LogAspects logAspects() { return new LogAspects(); } } /************************************************/ /** * ②定义为切面类,并标注通知注解、写好切入点表达式 * * @Aspect:告诉Spring当前类是一个切面类,而不是一些其他普通的类 * @author liayun * */ @Aspect public class LogAspects { // 如果切入点表达式都一样的情况下,那么我们可以抽取出一个公共的切入点表达式 @Pointcut("execution(public int com.meimeixia.aop.MathCalculator.*(..))") public void pointCut() {} // @Before:在目标方法(即div方法)运行之前切入,public int com.meimeixia.aop.MathCalculator.div(int, int)这一串就是切入点表达式,指定在哪个方法切入 // @Before("public int com.meimeixia.aop.MathCalculator.*(..)") @Before("pointCut()") public void logStart() { System.out.println("除法运行......@Before,参数列表是:{}"); } // 在目标方法(即div方法)结束时被调用 // @After("pointCut()") @After("com.meimeixia.aop.LogAspects.pointCut()") public void logEnd() { System.out.println("除法结束......@After"); } // 在目标方法(即div方法)正常返回了,有返回值,被调用 @AfterReturning("pointCut()") public void logReturn() { System.out.println("除法正常返回......@AfterReturning,运行结果是:{}"); } // 在目标方法(即div方法)出现异常,被调用 @AfterThrowing("pointCut()") public void logException() { System.out.println("除法出现异常......异常信息:{}"); } }
17.使注解版的AOP功能起作用,@EnableAspectJAutoProxy注解
@EnableAspectJAutoProxy
-
使用了@Import注解给容器中引入了AspectJAutoProxyRegister类
- 该类实现了ImportBeanDefinitionRegistrar接口,从而将自定义的组件添加到IOC容器中,也即AnnotationAwareAspectJAutoProxyCreator【注解装配模式的AspectJ切面自动代理创建器】
类继承关系: AnnotationAwareAspectJAutoProxyCreator ->AspectJAwareAdvisorAutoProxyCreator(父类) ->AbstractAdvisorAutoProxyCreator(父类) ->AbstractAutoProxyCreator(父类) implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware(两个接口) 结论:AnnotationAwareAspectJAutoProxyCreator不仅是一个后置处理器,还是一个BeanFactoryAware接口的实现类
-
registerBeanPostProcessors(beanFactory);//注册bean的所有后置处理器,用于拦截bean创建
->String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);//拿到IOC容器中所有需要创建的后置处理器
->按实现接口类型分离成不同postProcess数组
->doCreateBean()//bean实例的创建(AnnotationAwareAspectJAutoProxyCreator)【包括:创建bean的实例->populateBean()->initializeBean()】
->判断我们这个bean对象是不是Aware接口,并处理Aware接口的方法回调
->拿到所有的后置处理器,再调用后置处理器的postProcessBeforeInitialization()方法
->初始化方法执行
->拿到所有的后置处理器,然后再调用后置处理器的postProcessAfterInitialization()方法
->internalPostProcessors;//拿到所有的BeanPostProcessor,然后调用beanFactory的addBeanPostProcessor()方法将BeanPostProcessor注册到BeanFactory中
->initBeanFactory()//初始化beanfactory,生成aspectJAdvisorFactory和aspectJAdvisorsBuilderfinishBeanFactoryInitialization(beanFactory);//完成BeanFactory的初始化工作
postProcessBeforeInstantiation()【区别于postProcessbeforeInitialization,继承接口为InstantiationAwareBeanPostProcessor】
->preInstantiateSingletons()//遍历获取容器中所有的bean,并依次创建对象(先从缓存中获取当前bean,如果能获取到,说明当前bean之前是被创建过的,那么就直接使用,否则的话再创建)【单例模式】
->resolveBeforeInstantiation()//
->applyBeanPostProcessorsBeforeInstantiation()//拿到所有的后置处理器,如果后置处理器是InstantiationAwareBeanPostProcessor这种类型的,那么就执行该后置处理器的postProcessBeforeInstantiation()方法
->doCreateBean()//创建新bean
- 该类实现了ImportBeanDefinitionRegistrar接口,从而将自定义的组件添加到IOC容器中,也即AnnotationAwareAspectJAutoProxyCreator【注解装配模式的AspectJ切面自动代理创建器】
-
AOP原理总结
- 通过@EnableAspectJAutoProxy注解向IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件(后置处理器)
-
registerBeanPostProcessors(beanFactory); // 注册后置处理器,在这一步会创建AnnotationAwareAspectJAutoProxyCreator对象
-
finishBeanFactoryInitialization(beanFactory); // 完成BeanFactory的初始化工作。所谓的完成BeanFactory的初始化工作,其实就是来创建剩下的单实例bean的(要增强的类以及切面类)。
- 创建业务逻辑组件(要增强的类)和切面组件(切面类)
- 判断逻辑组件是否需要增强,如需要,则会把切面里面的通知方法包装成增强器,然后再为业务逻辑组件创建一个代理对象,代理方式根据是否有实现接口决定采用jdk还是cglib;一旦这个代理对象创建出来了,那么它里面就会有所有的增强器
- 代理对象来执行目标方法
- 使用CglibAopProxy类的intercept()方法来拦截目标方法的执行
- 将每一个通知方法又被包装为了方法拦截器,即MethodInterceptor
- 利用拦截器的链式机制,依次进入每一个拦截器中进行执行
- 最终执行效果 正常情况:@Before= => 目标方法= =>@AfterReturning= =>@After
- 使用CglibAopProxy类的intercept()方法来拦截目标方法的执行
-
- 通过@EnableAspectJAutoProxy注解向IOC容器中注册一个AnnotationAwareAspectJAutoProxyCreator组件(后置处理器)
18.声明式事务
@Transactional
使用步骤:
- 配置数据源以及JdbcTemplate
-
/** * * @author liayun * */ @Configuration @EnableTransactionManagement // 它是来开启基于注解的事务管理功能的 public class TxConfig { // 注册c3p0数据源 @Bean public DataSource dataSource() throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("liayun"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); return dataSource; } //使用jdbctemplate @Bean public JdbcTemplate jdbcTemplate(DataSource dataSource) throws Exception { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return jdbcTemplate; } }
-
- 使用事务注解执行事务
-
@Transactional public void insertUser() { userDao.insert(); // otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法 System.out.println("插入完成..."); int i = 10 / 0; }
-
- 配置事务管理器来控制事务(向TxConfig配置类中添加一个如下方法)
-
// 注册事务管理器在容器中 @Bean public PlatformTransactionManager platformTransactionManager() throws Exception { return new DataSourceTransactionManager(dataSource()); }
-
声明式事务的原理
-
利用@EnableTransactionManagement注解通过@Import注解给容器导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这两个组件
-
导入的第一个组件(即AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator【后置处理器】;利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会存有所有的增强器。最后,代理对象执行目标方法,在此过程中会利用拦截器的链式机制,依次进入每一个拦截器中进行执行。
-
ProxyTransactionManagementConfiguration是一个配置类,向容器中注入各种组件
- 注册事务增强器(BeanFactoryTransactionAttributeSourceAdvisor)
封装事务属性源、事务拦截器
- 事务属性源(TransactionAttributeSource)
解析方法/类上事务注解,包括它里面的每一个属性,例如rollbackFor、noRollbackFor、···
不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器
- 事务拦截器(TransactionInterceptor)
实质上还是一个MethodInterceptor(方法拦截器)
①获取事务相关的一些属性信息
②从容器中按照类型来获取一个PlatformTransactionManager(从容器中注入)
③创建一个事务,并执行目标方法
④正常提交返回(commitTransactionAfterReturning):先获取到事务管理器,然后再利用事务管理器提交事务
⑤异常返回(completeTransactionAfterThrowing):是先获取到事务管理器,然后再利用事务管理器回滚这次操作
- 注册事务增强器(BeanFactoryTransactionAttributeSourceAdvisor)
-
总结:使用AutoProxyRegistrar向Spring容器里面注册一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后,使用ProxyTransactionManagementConfiguration(配置类)再向Spring容器里面注册一个事务增强器,此时,需要用到事务拦截器。最后,代理对象执行目标方法,在这一过程中,便会执行到当前Spring容器里面的拦截器链,而且每次在执行目标方法时,如果出现了异常,那么便会利用事务管理器进行回滚事务,如果执行过程中一切正常,那么则会利用事务管理器提交事务