Spring Boot IoC(三)控制反转IoC

版权声明:From Lay https://blog.csdn.net/Sadlay/article/details/83277098

上一篇 Spring Boot IoC(二)IoC简介和BeanFactory

通过配置文件装配

1.首先定义一个简单的java对象(Plain Ordinary Java Object) User

package com.lay.ioc.pojo;

public class User {

	private Long id;
	private String userName;
	private String message;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

2.然后定义一个java配置文件AppConfig

package com.lay.ioc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.lay.ioc.pojo.User;

@Configuration
public class AppConfig {

	@Bean(name = "user")
	public User initUser() {
		User user = new User();
		user.setId(1L);
		user.setUserName("lay");
		user.setMessage("hello world");
		return user;
	}
}

需要注意两个注解,@Configuration和@Bean

@Configuration:代表这是一个Java配置文件,Spring会根据它来生成IoC容器去装配Bean

@Bean:代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有配置它,则将方法名initUser作为Bean的名称保存到IoC容器中。

3.构建Ioc容器测试

这里我注释掉了主类的@SpringBootApplicationSpringApplicationrun()方法,取消了SpringBoot默认的自动加载。通过手动装配Bean的方式加载到 AnnotationConfigApplicationContext这个基于注解的IoC容器中。

package com.lay.ioc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.lay.ioc.config.AppConfig;
import com.lay.ioc.pojo.User;

//@SpringBootApplication
public class IocApplication {
	private static Logger log=LoggerFactory.getLogger(IocApplication.class);
	
	public static void main(String[] args) {
		// SpringApplication.run(IocApplication.class, args);
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		User user=ctx.getBean(User.class);
		log.info(user.getMessage());
	}
}

输出

-------省略-------
16:09:57.868 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'initUser'
16:09:57.868 [main] INFO com.lay.ioc.IocApplication - hello world

user的message信息hello world已经输出,说明名称为user的Bean已经被加载到IoC容器中。

如果在Bean很多的情况下,一个个的使用@Bean来装配会很麻烦,Spring允许我们扫描进行Bean的装配。

通过扫描装配

扫描装配

两个注解,@Component@ComponentScan

@Component:标注哪个类被扫描进入Spring IoC容器。

@ComponentScan:标注采用何种策略去扫描装配Bean。

User.java移动到包com.lay.ioc.config配置包下,

package com.lay.ioc.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
 * @Component 表明会被Ioc容器扫描
 * user为Bean的名称,如果不配置默认会把类名第一个小写作为Bean名称
 * @author Lay
 */
@Component("user")
public class User {
	/**
	 * @Value 指定具体的值,然后IoC注入属性
	 */
	@Value("1")
	private Long id;
	@Value("lay")
	private String userName;
	@Value("hello scan world")
	private String message;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}

}

配置文件AppConfig.java

package com.lay.ioc.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * @ComponentScan 标明会进行扫描
 * @author Lay
 */
@ComponentScan
@Configuration
public class AppConfig {
}

这里需要注意的是, @ComponentScan不加任何参数的话只会扫描类AppConfig所在的当前包和其子包,第一步把User.java移动到com.lay.ioc.config包下就是这个原因。配置类中去掉了之前的使用@Bean方法来配置的内容。

测试

package com.lay.ioc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.lay.ioc.config.AppConfig;
import com.lay.ioc.config.User;

public class IocApplication {
	private static Logger log=LoggerFactory.getLogger(IocApplication.class);
	
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		User user=ctx.getBean(User.class);
		log.info(user.getMessage());
	}
}

打印结果

17:08:58.890 [main] DEBUG org.springframework.core.env.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
17:08:58.893 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'user'
17:08:58.893 [main] INFO com.lay.ioc.IocApplication - hello scan world

可以看到成功输出了hello scan world ,采用扫描装配成功。

移动java文件的方式显得不太合理,所以我们可以采用自定义配置扫描。

自定义配置扫描

查看@ComponentScan源码

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)//在一个类中可重复定义
public @interface ComponentScan {
    
	// 定义扫描的包
	@AliasFor("basePackages")
	String[] value() default {};
 
	// 定义扫描的包
	@AliasFor("value")
	String[] basePackages() default {};
    
	// 定义扫描的类
	Class<?>[] basePackageClasses() default {};

    // Bean name生成器
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	// 作用域解析器
	Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;

	// 作用域代理模式
	ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;

	// 资源匹配模式
	String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;

	// 是否启用默认的过滤模式
	boolean useDefaultFilters() default true;

	// 当满足过滤器的条件时扫描
	Filter[] includeFilters() default {};

	// 当不满足过滤器条件时扫描
	Filter[] excludeFilters() default {};

	// 是否延迟初始化
	boolean lazyInit() default false;

	// 定义过滤器
	@Retention(RetentionPolicy.RUNTIME)
	@Target({})
	@interface Filter {

		// 过滤器类型,可以按注解类型或者正则表达式等过滤
		FilterType type() default FilterType.ANNOTATION;

		// 定义过滤器的类		
		@AliasFor("classes")
		Class<?>[] value() default {};

		// 定义过滤器的类
		@AliasFor("value")
		Class<?>[] classes() default {};

		// 匹配方式
		String[] pattern() default {};
	}
}

其中,下面这些是常用的配置项

	// 定义扫描的包
	@AliasFor("basePackages")
	String[] value() default {};
 
	// 定义扫描的包
	@AliasFor("value")
	String[] basePackages() default {};
    
	// 定义扫描的类
	Class<?>[] basePackageClasses() default {};
     
	// 是否启用默认的过滤模式
	boolean useDefaultFilters() default true;

	// 当满足过滤器的条件时扫描
	Filter[] includeFilters() default {};

	// 当不满足过滤器条件时扫描
	Filter[] excludeFilters() default {};

我们把User.java在放回com.lay.ioc.pojo包下,这样User和AppConfi就在不同的包下。修改AppConfig

package com.lay.ioc.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.lay.ioc.pojo.User;
/**
 * @ComponentScan 标明会进行扫描
 * @author Lay
 */
@ComponentScan("com.lay.ioc.*")
// 或 @ComponentScan(basePackages= {"com.lay.ioc.pojo"})
// 或 @ComponentScan(basePackageClasses= {User.class})
@Configuration
public class AppConfig {
}

过滤装配

有时候我们的需求是扫描一些包,将一些Bean装配到Spring IoC容器中,而不是想加载这个包里的其他Bean。例如有一个UserService类,为了标注它为服务类,给它添加了@Service注解,如果再次使用扫描策略

@ComponentScan("com.lay.ioc.*")
package com.lay.ioc.service;

import org.springframework.stereotype.Service;

import com.lay.ioc.pojo.User;

@Service
public class UserService {
	public void showUser(User user) {
		System.out.println(user.getId());
		System.out.println(user.getUserName());
		System.out.println(user.getMessage());
	}
}

测试类

package com.lay.ioc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.lay.ioc.config.AppConfig;
import com.lay.ioc.pojo.User;
import com.lay.ioc.service.UserService;

public class IocApplication {
	private static Logger log=LoggerFactory.getLogger(IocApplication.class);
	
	public static void main(String[] args) {
		ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
		User user=ctx.getBean(User.class);
		log.info(user.getMessage());
		UserService userService=ctx.getBean(UserService.class);
		userService.showUser(user);
	}
}

输出

18:10:37.537 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'user'
18:10:37.538 [main] INFO com.lay.ioc.IocApplication - hello scan world
18:10:37.538 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of singleton bean 'userService'
1
lay
hello scan world

这样UserService包也会被扫到,为了不被装配,需要把AppConfig.java扫描的策略修改为

@ComponentScan(basePackages="com.lay.ioc.*",excludeFilters= {@Filter(classes= {Service.class})})

由于加了excludeFilters的配置 ,使标注了@Service的 类都将不会配IoC容器扫描注入,这样就可以把UserService排除到Spring IoC容器中了。

@SpringBootApplication源码

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// 自定义排除的扫描类
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    // 通过类型配出自动配置类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

    // 通过名称排除自动配置类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	// 定义扫描包
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	// 定义被扫描的类
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

}

通过它就能够定义扫描哪些包,按时这里需要特别注意的是,@SpringBootApplication提供的excludeexcludeName两个方法是对于其内部的自动配置类才会生效的。为了能够排除其他类,还可以再加入@ComponengScan以达到我们的目的。例如,扫描User和不扫描UserService。可以把启动配置文件写成:

@SpringBootApplication(scanBasePackages="com.lay.ioc.*")
@ComponentScan(excludeFilters= {@Filter(classes= {Service.class})})
@Configuration
public class AppConfig {
}

下一篇Spring Boot IoC(四)依赖注入DI

猜你喜欢

转载自blog.csdn.net/Sadlay/article/details/83277098
今日推荐