浅尝Spring注解开发,基于Spring 4.3.12
包含自定义扫描组件、自定义导入组件、手动注册组件、自动注入方法和参数、使用Spring容器底层组件等
告诉Spring这是一个配置类,代替以前的xml文件,配置类=配置文件
@Configuration public class MainConfig { //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id @Bean("person") public Person person01(){ return new Person("lisi", 20); } }
给容器中注册组件有以下方法:
包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
@Bean[导入的第三方包里面的组件]
@Import[快速给容器中导入一个组件]
- @Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
- ImportSelector:返回需要导入的组件的全类名数组;
- ImportBeanDefinitionRegistrar:手动注册bean到容器中
使用Spring提供的 FactoryBean(工厂Bean);
默认获取到的是工厂bean调用getObject创建的对象
要获取工厂Bean本身,我们需要给id前面加一个&
如:&colorFactoryBean
给容器中注册一个Bean,类型为返回值的类型,id默认是用方法名作为id,也可以指定id
@Bean("person")//指定Bean id public Person person01(){ return new Person("lisi", 20); }
创建一个IoC容器AnnotationConfigApplicationContext,传入配置类,获取Bean
//创建IoC容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); //获取Bean对象 Person bean = applicationContext.getBean(Person.class); //获取所以Person类型Bean的名字 String[] namesForType = applicationContext.getBeanNamesForType(Person.class); for (String name : namesForType) { System.out.println(name); }
@Component(包括@Controller@Service@Repository和@Bean目的一样)和@ComponentScan的区别
- @Component:将普通JavaBean实例化到spring容器中,Spring容器统一管理,用起来不用自己new了,相当于配置文件中的<bean id="" class=""/>
- @ComponentScan:一般用在核心配置类,由你来定义哪些包需要被扫描。
- @Component标在类上,@Bean标在方法上,两者使用在普通类或方法上时,都不起作用,只有放在配置类中才能注册组件(@Bean可以在写在配置类中,@Component由于本身标在类上,所以不能写在类中),而@ComponentScan就是给了@Component一种注册进容器的办法,只要扫描到@Component标注的类就直接注册进容器
自动扫描组件,可有多个@ComponentScan,或一个@ComponentScans
value:指定要扫描的包
所有指定的包下的类都要进入扫描,是否能够通过,需要看下面使用了什么规则,有排除、包含、指定类型、正则、和自定义规则,自定义规则通过反射获取包中所有类,然后判断是否能够进入ioc容器中。
excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
FilterType.CUSTOM:使用自定义规则,需要TypeFilter实现类
useDefaultFilters:不使用默认的 Filter进行扫描。在includeFilters中使用,取消默认扫描,指定扫描对象,可以在SpringMVC中使用
@ComponentScan(value="com.atguigu",includeFilters = { //扫描指定注解 @Filter(type=FilterType.ANNOTATION,classes={Controller.class}), //扫描指定类型 @Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}), //扫描自定义 @Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class}) },useDefaultFilters = false)
FilterType.CUSTOM:使用自定义规则,需要实现TypeFilter接口
public class MyTypeFilter implements TypeFilter { /** * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任何类信息的 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { // TODO Auto-generated method stub //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("--->"+className); //根据自定义规则,只让类名中包含er的类进入ioc容器。 if(className.contains("er")){ return true; } return false; } }
调整作用域,单实例,多实例
@Scope("singleton") @Lazy @Bean("person") public Person person(){ System.out.println("给容器中添加Person...."); return new Person("张三", 25); }
指定条件向容器中注入Bean
@Conditional({Condition}) : 按照一定的条件进行判断,满足条件才将Bean注册进容器,可以标在@Bean方法和@Configuration类上
创建两个判断条件,判断系统是Windows还是Linux,自定义注册不同的Bean,注解可以标在方法上,也可以标在类上
//判断是否linux系统 public class LinuxCondition implements Condition { /** * ConditionContext:判断条件能使用的上下文(环境) * AnnotatedTypeMetadata:注释信息 */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // TODO是否linux系统 //1、能获取到ioc使用的beanfactory ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); //2、获取类加载器 ClassLoader classLoader = context.getClassLoader(); //3、获取当前环境信息 Environment environment = context.getEnvironment(); //4、获取到bean定义的注册类 BeanDefinitionRegistry registry = context.getRegistry(); //5、获取系统名 String property = environment.getProperty("os.name"); //这里判断容器中的bean注册情况,也可以使用BeanDefinitionRegistry.registerBeanDefinition()给容器中注册bean boolean definition = registry.containsBeanDefinition("person"); //判断系统名是否包含linux if(property.contains("linux")){ return true; } return false; } }
//判断是否windows系统 public class WindowsCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { Environment environment = context.getEnvironment(); String property = environment.getProperty("os.name"); if(property.contains("Windows")){ return true; } return false; } }
@Configuration public class MainConfig2 { /** * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean * * 如果系统是windows,给容器中注册Person类型("bill") * 如果是linux系统,给容器中注册Person类型("linus") */ @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); } }
//类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效; @Conditional({WindowsCondition.class}) @Configuration public class MainConfig2 { //... }
使用,可配置VM参数:-Dos.name=linux
测试
public class IOCTest { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); @Test public void test03(){ //获取所有Person类型的Bean名字 String[] namesForType = applicationContext.getBeanNamesForType(Person.class); //获取运行时环境 ConfigurableEnvironment environment = applicationContext.getEnvironment(); //动态获取环境变量的值;Windows 10 String property = environment.getProperty("os.name"); System.out.println(property); for (String name : namesForType) { System.out.println(name); } //获取所有Person类型Bean的对象,名字是否是lunus或bill Map<String, Person> persons = applicationContext.getBeansOfType(Person.class); System.out.println(persons); } }
@Import的三种方法都是写在@Import注解中,都是向容器中导入组件
- @Import(要导入到容器中的组件):容器中就会自动注册这个组件,id默认是全类名
- ImportSelector:返回需要导入的组件的全类名数组
- ImportBeanDefinitionRegistrar:手动注册bean到容器中
@Import(要导入到容器中的组件):容器中就会自动注册这个组件,id默认是全类名com.xxx.bean.Color
@Configuration @Import({Color.class,Red.class}) //@Import导入组件,id默认是组件的全类名 public class MainConfig2 { /... }
测试
public class IOCTest { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //输出:除了Spring自己的Bean还有Import的Color、Red @Test public void testImport(){ printBeans(applicationContext); } //输出容器中所有的Bean名 private void printBeans(AnnotationConfigApplicationContext applicationContext){ String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } }
接口,可以选择注册指定的类,返回需要导入的组件的全类名数组;
//自定义逻辑返回需要导入的组件 public class MyImportSelector implements ImportSelector { //返回值,就是到导入到容器中的组件全类名 //AnnotationMetadata:当前标注@Import注解的类的所有注解信息 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { // TODO Auto-generated method stub //importingClassMetadata //方法不要返回null值,返回的值就是要注册的组件 return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"}; } }
配置
@Configuration //@Import导入组件,MyImportSelector是自定义选择的Bean @Import({Color.class,Red.class,MyImportSelector.class}) public class MainConfig2 { //... }
测试
public class IOCTest { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //输出:除了Spring自己的Bean还有MyImportSelector选中的Bean @Test public void testImport(){ printBeans(applicationContext); Blue bean = applicationContext.getBean(Blue.class); System.out.println(bean); } //输出容器中所有的Bean名 private void printBeans(AnnotationConfigApplicationContext applicationContext){ String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } }
自定义注入组件,在Spring注解源码中使用
实现接口,指定规则,手动注入新的bean
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { /** * AnnotationMetadata:当前类的注解信息 * BeanDefinitionRegistry内有BeanDefinition注册类,所有Bean都在这里注册; * 把所有需要添加到容器中的bean * 调用方法BeanDefinitionRegistry.registerBeanDefinition手工注册进来 */ @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //判断是否有红色,蓝色 boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red"); boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue"); //如果存在 if(definition && definition2){ //指定Bean定义信息BeanDefinition类型;(Bean的类型,Bean作用域等...) RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class); //注册一个新Bean,指定bean名 registry.registerBeanDefinition("rainBow", beanDefinition); } } }
配置
@Configuration //MyImportBeanDefinitionRegistrar是手动注册到Bean容器中,可以指定规则 @Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class}) //@Import导入组件,id默认是组件的全类名 public class MainConfig2 { //... }
测试
public class IOCTest { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //输出:除了Spring自己的Bean还有手动定制规则新导入的rainBow @Test public void testImport(){ printBeans(applicationContext); Blue bean = applicationContext.getBean(Blue.class); System.out.println(bean); } //输出容器中所有的Bean名 private void printBeans(AnnotationConfigApplicationContext applicationContext){ String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } }
多用于第三方组件
接口,给容器中注入组件,使用Spring提供的 FactoryBean(工厂Bean)创建的Bean
默认获取到的是工厂bean调用getObject创建的对象,而不是工厂对象本身
要获取工厂Bean本身,我们需要给id前面加一个"&"如:context.getBean("&xxx")
实现接口,创建工厂
//创建一个Spring定义的FactoryBean 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; @Override public boolean isSingleton() { // TODO Auto-generated method stub return false; } }
配置
@Configuration public class MainConfig2 { /** * 给容器中注册组件; * 4)、使用Spring提供的 FactoryBean(工厂Bean); * 1)、默认获取到的是工厂bean调用getObject创建的对象 * 2)、要获取工厂Bean本身,我们需要给id前面加一个& * &colorFactoryBean */ @Bean public ColorFactoryBean colorFactoryBean(){ return new ColorFactoryBean(); } }
测试
public class IOCTest { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); //直接使用输出的不是工厂本身,而是工厂内getObject创建的Bean @Test public void testImport(){ //工厂Bean获取的是调用getObject创建的Color对象 Object bean2 = applicationContext.getBean("colorFactoryBean"); Object bean3 = applicationContext.getBean("colorFactoryBean"); //输出:com.xxx.bean.Color System.out.println("bean的类型:"+bean2.getClass()); System.out.println(bean2 == bean3); //输出:com.xxx.bean.ColorFactoryBean Object bean4 = applicationContext.getBean("&colorFactoryBean"); System.out.println(bean4.getClass()); } //输出容器中所有的Bean名 private void printBeans(AnnotationConfigApplicationContext applicationContext){ String[] definitionNames = applicationContext.getBeanDefinitionNames(); for (String name : definitionNames) { System.out.println(name); } } }
public class Person { //使用@Value赋值; //1、基本数值 @Value("张三") private String name; }
public class Person { //使用@Value赋值; //1、基本数值 //2、可以写SpEL; #{} @Value("张三") private String name; //计算后值为18 @Value("#{20-2}") private Integer age; }
读取properties配置文件的值,例如读取数据库连接信息
- 读取文件
- 取值
使用@PropertySource读取外部配置文件
//使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;加载完外部的配置文件以后使用${}取出配置文件的值 @PropertySource(value={"classpath:/person.properties"}) @Configuration public class MainConfigOfPropertyValues { @Bean public Person person(){ return new Person(); } }
配置文件person.properties
person.nickName=法外狂徒
取出配置文件properties中的值
public class Person { //使用@Value赋值; //1、基本数值 //2、可以写SpEL; #{} //3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) @Value("张三") private String name; @Value("#{20-2}") private Integer age; @Value("${person.nickName}") private String nickName; }
运行时properties配置文件加载进环境变量,可以直接取出
public class IOCTest_PropertyValue { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfPropertyValues.class); @Test public void test01(){ //直接从applicationContext容器中获取运行时环境变量ConfigurableEnvironment ConfigurableEnvironment environment = applicationContext.getEnvironment(); //取值 String property = environment.getProperty("person.nickName"); System.out.println(property); applicationContext.close(); } }
AutowiredAnnotationBeanPostProcessor:解析完成自动装配功能
几种自动装配:
- @Autowired:自动注入
- Spring还支持使用@Resource(JSR250)和@Inject(JSR330)[java规范的注解]
- @Autowired:构造器,参数,方法,属性,都是从容器中获取参数组件的值
- 自定义组件想要使用Spring容器底层的一些组件(ApplicationContext,BeanFactory,xxx)
自动注入:
指定装配:
默认装配:
下面是两个Bean的情况
@Configuration @ComponentScan({"com.atguigu.service","com.atguigu.dao", "com.atguigu.controller","com.atguigu.bean"}) public class MainConifgOfAutowired { @Primary//首选装配 @Bean("bookDao2") public BookDao bookDao(){ BookDao bookDao = new BookDao(); bookDao.setLable("2"); return bookDao; } }
@Service public class BookService { @Qualifier("bookDao")//指定,大于首选 @Autowired(required=false)//可以不存在 private BookDao bookDao; }
可以和@Autowired一样实现自动装配功能,默认是按照组件名称进行装配的
没有能支持@Primary和@Autowired(reqiured=false)的功能
@Autowired跟spring强耦合,如果换成了JFinal等其他框架,功能就会失效。而@Resource是JSR-250提供的,它是Java标准,绝大部分框架都支持
@Resource(name="bookDao2") private BookDao bookDao;
需要导入javax.inject的包,和@Autowired的功能一样。没有@Autowired(reqiured=false)的功能
@Autowired:Spring定义的, @Resource、@Inject都是java规范,@Autowried不能脱离Spring
@Inject private BookDao bookDao;
@Autowired:构造器,参数,方法,属性都是从容器中获取参数组件的值
使用[@Bean+方法参数]的方式在SpringBoot中使用很多,在使用各种配置时,只配置方法,方法中的参数直接从容器中获取
方法上
@Autowired直接标在方法上
@Autowired //标注在方法,Spring容器创建当前对象,就会调用方法,完成赋值; //方法使用的参数,自定义类型的值从ioc容器中获取 public void setCar(Car car) { this.car = car; }
如果是@Bean标注的方法,参数位置不标@Autowired,参数也可以自动装配,都是从容器中获取
/** * @Bean标注的方法创建对象的时候,方法参数的值从容器中获取 * @param car car也是从容器中获取的值,前提是car已经注入容器 * @return */ @Bean public Color color(Car car){ Color color = new Color(); color.setCar(car); return color; }
构造器
标在构造器上
如果组件只有一个有参构造器,这个有参构造器的@Autowired
可以省略,参数位置的组件还是可以自动从容器中获取
//默认加在ioc容器中的组件,容器启动会调用无参构造器创建对象,再进行初始化赋值等操作 @Component public class Boss { private Car car; //构造器要用的组件,都是从容器中获取 //@Autowired public Boss(Car car){ this.car = car; System.out.println("Boss...有参构造器"); } }
参数上
标在参数前
public void setCar(@Autowired Car car) { this.car = car; }
自定义组件想要使用Spring容器底层的一些组件(如:ApplicationContext,BeanFactory,xxx)只要自定义组件实现xxxAware
在创建对象的时候,会调用接口规定的方法注入相关组件,把Spring底层一些组件注入到自定义的Bean中
xxxAware:对应功能使用xxxProcessor
ApplicationContextAware==>ApplicationContextAwareProcessor
实现过程在BeanPostProcessor后置处理器中
使用不同的底层组件
@Component public class Red implements ApplicationContextAware,BeanNameAware,EmbeddedValueResolverAware { private ApplicationContext applicationContext; //使用IOC @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // TODO Auto-generated method stub System.out.println("传入的ioc:"+applicationContext); this.applicationContext = applicationContext; } //使用BeanName @Override public void setBeanName(String name) { // TODO Auto-generated method stub System.out.println("当前bean的名字:"+name); } //使用EmbeddedValueResolve解析组件 @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { // TODO Auto-generated method stub String resolveStringValue = resolver.resolveStringValue("你好 ${os.name} 我是 #{20*18}"); System.out.println("解析的字符串:"+resolveStringValue); } }
@Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;
环境:开发环境、测试环境、生产环境,分别对应:
数据源:devDataSource、testDataSource、prodDataSource
@Profle:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
-Dspring.profiles.active=test
,会使用此环境创建多种数据源环境匹配不同数据库
//使用多种方法读取properties配置文件信息 //第一种方法读取配置文件 @PropertySource("classpath:/dbconfig.properties") @Configuration public class MainConfigOfProfile implements EmbeddedValueResolverAware{ //第二种方法读取配置文件 @Value("${db.user}") private String user; private StringValueResolver valueResolver; private String driverClass; //普通的Bean,任何时候都可以加载进容器 @Bean public Yellow yellow(){ return new Yellow(); } //测试环境标识 @Profile("test") //@Profile("default") @Bean("testDataSource") public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setDriverClass(driverClass); return dataSource; } //开发环境标识 @Profile("dev") @Bean("devDataSource") public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud"); dataSource.setDriverClass(driverClass); return dataSource; } //生产环境标识 @Profile("prod") @Bean("prodDataSource") public DataSource dataSourceProd(@Value("${db.password}")String pwd) throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser(user); dataSource.setPassword(pwd); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/scw_0515"); dataSource.setDriverClass(driverClass); return dataSource; } //第三种方法读取配置文件,解析组件读取配置文件 @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { // TODO Auto-generated method stub this.valueResolver = resolver; driverClass = valueResolver.resolveStringValue("${db.driverClass}"); } }
测试
//激活环境 public class IOCTest_Profile { //1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test //2、代码的方式激活某种环境; @Test public void test01(){ //1、创建一个applicationContext AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); //2、设置需要激活的环境 applicationContext.getEnvironment().setActiveProfiles("dev"); //3、注册主配置类 applicationContext.register(MainConfigOfProfile.class); //4、启动刷新容器 applicationContext.refresh(); //获取被激活注册容器的数据源名字 String[] namesForType = applicationContext.getBeanNamesForType(DataSource.class); for (String string : namesForType) { System.out.println(string); } //普通Bean不受影响,一直可用 Yellow bean = applicationContext.getBean(Yellow.class); System.out.println(bean); applicationContext.close(); } }
输出
devDataSource com.atguigu.bean.Yellow@1ab3a8c8