组件添加
@Configuration+@Bean
xml文件的方式:
实体类
/** * @Description 实体类 * @Date 2021/12/17 15:50 * @Created Prannt */ @Data public class Person { private String name; private Integer age; private String nickName; }
bean1.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"> <context:component-scan base-package="com.pan.entity" use-default-filters="false"></context:component-scan> <bean id="person" class="com.pan.entity.Person"> <property name="age" value="18"></property> <property name="name" value="zhangsan"></property> </bean> </beans>
测试类
public class MainTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml"); Person bean = (Person) context.getBean("person"); System.out.println(bean); } }
输出
Person(name=zhangsan, age=18, nickName=null)
注解的方式:
实体类
@Data @AllArgsConstructor public class Person { private String name; private Integer age; private String nickName; }
配置类
// 配置类 == 配置文件 @Configuration // 告诉Spring这是一个配置类 public class MainConfig { //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id(就是bean的名字),在这里就是person @Bean(name = "person") public Person person(){ return new Person("李四",20,"二狗"); } }
测试类
public class MainTest { public static void main(String[] args) { ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class); Person bean = context.getBean("person",Person.class); System.out.println(bean); } }
输出
Person(name=李四, age=20, nickName=二狗)
@ComponentScans
实体类(同上)
指定包扫描规则
public class MyTypeFilter implements TypeFilter { /** * metadataReader:读取到的当前正在扫描的类的信息 * metadataReaderFactory:可以获取到其他任何类信息的 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //获取当前类注解的信息 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //获取当前正在扫描的类的类信息 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //获取当前类资源(类的路径) Resource resource = metadataReader.getResource(); String className = classMetadata.getClassName(); System.out.println("--->"+className); if(className.contains("er")){ return true; } return false; } }
配置类
// 配置类 == 配置文件 @Configuration // 告诉Spring这是一个配置类 @ComponentScans(value = {@ComponentScan(value="com.pan",includeFilters = { // @Filter(type=FilterType.ANNOTATION,classes={Controller.class}), // @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}), @ComponentScan.Filter(type= FilterType.CUSTOM,classes={MyTypeFilter.class})}, useDefaultFilters = false)}) // @ComponentScan value:指定要扫描的包 // excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 // includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 // FilterType.ANNOTATION:按照注解 // FilterType.ASSIGNABLE_TYPE:按照给定的类型; // FilterType.ASPECTJ:使用ASPECTJ表达式 // FilterType.REGEX:使用正则指定 // FilterType.CUSTOM:使用自定义规则 public class MainConfig2 { //给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id @Bean("person") public Person person(){ return new Person("王五", 20,"三狗"); } }
测试类
@Test public void test2(){ ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class); Person bean = context.getBean("person",Person.class); System.out.println(bean); }
输出
--->com.pan.entity.Person Person(name=王五, age=20, nickName=三狗)
@Scope
实体类(同上)
配置类
@Configuration public class MainConfig3 { /** * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE 任何环境都可以使用 * @see ConfigurableBeanFactory#SCOPE_SINGLETON 任何环境都可以使用 * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request 只能在web容器里用 * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion 只能在web容器里用 * * @Scope:调整作用域 * prototype:多实例的:IOC容器启动并不会去调用方法创建对象放在容器中。 * 每次获取的时候才会调用方法创建对象; * singleton:单实例的(默认值):IOC容器启动会调用方法创建对象放到 IOC容器中。 * 以后每次获取就是直接从容器(map.get())中拿, * request:同一次请求创建一个实例 * session:同一个session创建一个实例 * * 默认是单实例的 * */ @Scope("prototype") @Lazy @Bean("person") public Person person(){ System.out.println("给容器中添加Person...."); return new Person("王王", 25,"汪汪叫"); } }
测试类
@Test public void test3(){ ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class); Person bean = context.getBean("person",Person.class); System.out.println(bean); }
输出
给容器中添加Person.... Person(name=王王, age=25, nickName=汪汪叫)
@Lazy
@Configuration
public class MainConfig4 {
/**
* 懒加载:
* 单实例bean:默认在容器启动的时候创建对象;
* 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化。
*/
@Lazy
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("乔六", 25,"乔乔");
}
}
@Conditional
需求:根据当前操作系统注入Person实例
实体类(同上)
判断是否是Linux系统
// 判断是否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(); String property = environment.getProperty("os.name"); // 可以判断容器中的bean注册情况,也可以给容器中注册bean boolean definition = registry.containsBeanDefinition("person"); 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; } }
配置类
// 类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效; @Conditional({WindowsCondition.class}) @Configuration public class MainConfig5 { /** * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册 bean * * 如果系统是 windows,给容器中注册("bill") * 如果是 linux系统,给容器中注册("linus") */ @Bean("bill") public Person person01(){ return new Person("Bill Gates",62,"Bill"); } @Conditional(LinuxCondition.class) @Bean("linus") public Person person02(){ return new Person("linus", 48,"linux"); } }
测试类
@Test public void test4(){ ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class); Map<String,Person> map = context.getBeansOfType(Person.class); System.out.println(map); }
输出
{bill=Person(name=Bill Gates, age=62, nickName=Bill)} // 以上输出也能证明:本机是Windows系统
@Import
@Import
注解的作用是给容器中导入组件。可以通过Spring的xm配置方式,可以通过注解,如@Component
等,也可以通过java配置类的方式给容器中导入注解,现在绍介绍另一个注解,其作用也是给容器中导入组件。
@Configuration
// 将这两个类导入到容器中
@Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
// @Import导入组件,id默认是组件的全类名
public class MainConfig5 {
/**
* 给容器中注册组件;
* 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[只能注册自己写的类]
* 2)、@Bean[导入的第三方包里面的组件]
* 3)、@Import[快速给容器中导入一个组件]
* 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
* 2)、ImportSelector:返回需要导入的组件的全类名数组;
* 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
*/
}
// 自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
// 返回值:导入到容器中的组件全类名
// AnnotationMetadata:@Import引入MyImportSelector的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// importingClassMetadata.get
// 方法不要返回null值,不然会报错
return new String[]{"com.pan.entity.Blue","com.pan.entity.Yellow"};
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition注册类;
* 把所有需要添加到容器中的bean;调用
* BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.pan.entity.Red");
boolean definition2 = registry.containsBeanDefinition("com.pan.entity.Blue");
if(definition && definition2){
// 指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
// 注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
FactoryBean
public class Car {
}
public class Color {
private Car car;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Color [car=" + car + "]";
}
}
// 创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
// 返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
// 是单例?
// true:这个bean是单实例,在容器中保存一份
// false:多实例,每次获取都会创建一个新的bean
@Override
public boolean isSingleton() {
return false;
}
}
@Configuration
public class MainConfig2 {
/**
* 给容器中注册组件;
* 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类]
* 2)、@Bean[导入的第三方包里面的组件]
* 3)、@Import[快速给容器中导入一个组件]
* 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
* 2)、ImportSelector:返回需要导入的组件的全类名数组;
* 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
* 4)、使用Spring提供的 FactoryBean(工厂Bean);
* 1)、默认获取到的是工厂bean调用getObject创建的对象
* 2)、要获取工厂Bean本身,我们需要给id前面加一个&
* &colorFactoryBean
*
* 虽然这里装配的是ColorFactoryBean,但实际上beand的类型是Color
*/
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}
public class IOCTest {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
@Test
public void testImport(){
printBeans(applicationContext);
Blue bean = applicationContext.getBean(Blue.class);
System.out.println(bean);
// 工厂Bean获取的是调用getObject创建的对象
Object bean2 = applicationContext.getBean("colorFactoryBean");
System.out.println("bean的类型:"+bean2.getClass()); // pos_1 输出:bean的类型:class com.atguigu.bean.Color
Object bean4 = applicationContext.getBean("&colorFactoryBean");
System.out.println(bean4.getClass()); // pos_2 输出:class com.atguigu.bean.ColorFactoryBean
}
private void printBeans(AnnotationConfigApplicationContext applicationContext){
String[] definitionNames = applicationContext.getBeanDefinitionNames();
for (String name : definitionNames) {
System.out.println(name);
}
}
}
输出:
colorFactoryBean
ColorFactoryBean...getObject...
bean的类型:class com.atguigu.bean.Color
class com.atguigu.bean.ColorFactoryBean
推荐阅读文章:
生命周期
@Bean指定初始化和销毁方法
测试类
public class IOCTest_LifeCycle {
@Test
public void test01(){
// 1.创建ioc容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
System.out.println("容器创建完成...");
// 2.关闭容器
applicationContext.close();
}
}
MainConfigOfLifeCycle
@ComponentScan("com.atguigu.bean")
@Configuration
public class MainConfigOfLifeCycle {
// 指定初始化和销毁方法
@Bean(initMethod="init",destroyMethod="detory")
public Car car(){
return new Car();
}
}
InitializingBean和DisposableBean
InitializingBean
接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet()
方法,凡是实现该接口的子类,在初始化bean的时候会执行该方法。
DisposableBean
中的destroy()
会在调用的程序结束以后,调用该方法内的程序
@PostConstruct和@PreDestroy
主要实现Bean在初始化之后、销毁之前执行自定义业务。 Spring负责管理Bean的初始化和销毁,但同时也提供方式让我们在bean初始化之后、销毁之前执行特定业务。之前一般需要实现相应接口实现,InitializingBean
接口并实现 afterPropertiesSet()
方法, DisposableBean
接口的destroy()
方法。前者用于初始化bean业务,后者通常用于清理资源逻辑。
其实除了实现接口,Spring也提供了@PostConstruct 和 @PreDestroy 注解,使得实现更简单。
@PostConstruct
Spring会在初始化bean属性之后,调用一次拥有@PostConstruct
注解的方法。该方法可以为任何访问级别,但不能为static。
下面示例使用@PostConstruct
注解填充数据库,即在开发阶段,增加一些确实用户:
@Component
public class DbInit {
@Autowired
private UserRepository userRepository;
@PostConstruct
private void postConstruct() {
User admin = new User("admin", "admin password");
User normalUser = new User("user", "user password");
userRepository.save(admin, normalUser);
}
}
// 该示例首先初始化userRepository,然后调用postConstruct()方法。
@PreDestroy
使用@PreDestroy
注解的方法仅执行异常,即在Spring从应用上下文中删除bean之前。与@PostConstruct
注解一样,该方法可以为任何访问级别,但不能为static。
@Component
public class UserRepository {
private DbConnection dbConnection;
@PreDestroy
public void preDestroy() {
dbConnection.close();
}
}
// 该方法一般用于在bean销毁之前释放资源或执行其他清理任务,如关闭数据库连接。
BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口。接口声明如下:
public interface BeanPostProcessor {
// bean初始化方法调用前被调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
// bean初始化方法调用后被调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
运行顺序:
Spring IOC容器实例化Bean → 调用BeanPostProcessor的postProcessBeforeInitialization
方法 → 调用bean实例的初始化方法 → 调用BeanPostProcessor的postProcessAfterInitialization
方法
BeanPostProcessor实例
/**
* 后置处理器:初始化前后进行处理工作
* 将后置处理器加入到容器中
*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization..."+beanName+"=>"+bean);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization..."+beanName+"=>"+bean);
return bean;
}
}
BeanFactoryPostProcessor简介
bean工厂的bean属性处理容器,说通俗一些就是可以管理我们的bean工厂内所有的beandefinition(未实例化)数据,可以随心所欲的修改属性。
属性赋值
@Value
该注解的作用是将配置文件的属性读出来,有**@Value(“${}”)和@Value(“#{}”)**两种方式。
${}:注入的是外部配置文件对应的property
#{}:SpEL表达式对应的内容
@Data
@AllArgsConstructor
@NoArgsConstructo
public class Person {
// 使用@Value赋值;
// 1、基本数值
// 2、可以写#{}
// 3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)
@Value("张三")
private String name;
@Value("#{20-2}")
private Integer age;
@Value("${person.nickName}")
private String nickName;
}
@PropertySource
// 使用@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中;
// 加载完外部的配置文件以后使用${}取出配置文件的值
@PropertySource(value={"classpath:/person.properties"})
@Configuration
public class MainConfigOfPropertyValues {
@Bean
public Person person(){
return new Person();
}
}
自动装配
@Autowired
默认按类型装配,找不到或者找到多个则报错。如果要按名称装配,需要结合Spring另外一个注解Qualifier(“name”)使用。默认必须装配requred=true,如果可以为空,可以设置为false,在Spring4.x
结合jdk8.x
的情况下还可以使用Optional和false同等的效果
@Controller
public class BookController {
@Autowired // 自动注入属性
private BookService bookService;
}
@Resource
有两个关键的属性:name-名称,type-类型
如果指定了name,type,则从Spring容器中找一个名称和类型相当应的一个bean,找不到则报错。
如果只指定了name,则从Spring容器中找一个名称和name一样的bean,找不到则报错。
如果只指定了type,则从Spring容器中找一个类型和type一样的bean,找不到或者找到多个则报错。
如果没有指定参数,则默认找字段名称装配,找不到则按类型装配,找不到则报错。
@Inject
与@Autowired类似,可以完全代替@Autowired,但这个没有required属性,要求bean必须存在。如果要按名称装配,需要结合javax另外一个注解Named使用
@Qualifier
限定哪个bean应该被自动注入
假设有两个bean定义为Person类的实例
<beanid="user1"class="com.test.User">
<property name="name"value="zhangsan"/></bean>
<beanid="user2"class="com.test.User">
<property name="name"value="lisi"/></bean>
然后注入属性:
public class Staff{
@Autowired
private user user;
}
Spring 知道哪个bean应该自动注入吗?不。当运行上面的例子时,抛出如下异常:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:No unique bean of type [com.test.User] is defined:expected single matching bean but found2: [user1, user2]
要解决以上问题,需要使用@Quanlifier
注解告诉Spring 哪个bean应该被Autowired。
public class Staff {
@Autowired
@Qualifier("user1")
private User user;
}
@Primary
在Spring中使用注解@Autowired
, 默认是根据类型Type来自动注入的。但有些特殊情况,同一个接口可能会有几种不同的实现类,而默认只会采取其中一种,@Primary 的作用就出来了。
// 有一个Singer接口
public interface Singer {
String sing(String lyrics);
}
// Singer接口有两个实现类
@Component
public class MetalSinger implements Singer{
@Override
public String sing(String lyrics) {
return "I am singing with DIO voice: " + lyrics;
}
}
@Component
public class OperaSinger implements Singer {
@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: "+lyrics;
}
}
// 注入
@Component
public class SingerServiceImpl {
private static final Logger logger = LoggerFactory.getLogger(SingerServiceImpl.class);
@Autowired
private Singer singer;
public String sing(){
return singer.sing("song lyrics");
}
}
报错:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [main.service.Singer] is defined: expected single matching bean but found 2: metalSinger,operaSinger
原因:
Spring 根据类型无法选择到底注入哪一个。这个时候**@Primay** 可以闪亮登场了。
@Primary
@Component
public class OperaSinger implements Singer{
@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: " + lyrics;
}
}
当然也可以使用上面提到的@Qualifier注解
@Resource
@Component
@Qualifier("metalSinger")
public class MetalSinger implements Singer{
@Override
public String sing(String lyrics) {
return "I am singing with DIO voice: "+lyrics;
}
}
@Component
@Qualifier("opreaSinger")
public class OperaSinger implements Singer {
@Override
public String sing(String lyrics) {
return "I am singing in Bocelli voice: "+lyrics;
}
}