Spring FrameWork从入门到NB -classpath扫描和组件托管

通过@Configuration注解指定包扫描路径后,注解方式可以完全代替xml配置的方式实现Bean的注入,

@Configuration & @ComponentScan

@Configuration注解作用在class上,指定当前类为配置类:

@Configuration
@ComponentScan(value={"springTest"})
@PropertySource("classpath:application.properties")
public class MyConfiguration {
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

@ComponentScan指定包扫描路径,Spring IoC容器将自动扫描该路径,加载路径下的基于@Component的组件注解(包括@Component、@Service、@Controller、@Repository)的类,作为Bean加入到Spring IoC容器中。

定制化包扫描

可以通过@ComponentScan注解的includeFilters和excludeFilters过滤器定制化Spring的扫描行为:

@Configuration
@ComponentScan(basePackages = "org.example",
		includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
		excludeFilters = @Filter(Repository.class))
public class AppConfig {
	// ...
}

Spring支持的过滤器类型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVDRsepB-1687309708067)(/img/bVc757N)]

也就是说,可以通过includeFilters或excludeFilters指定某一个class文件、aspectj类型、正则表达式、或者实现org.springframework.core.type.TypeFilter指定某一个或某一类组件加载或者不加载到Spring IoC容器中。

Spring IoC的通过注解自动扫描加载Bean的方式还是非常灵活的。如果再结合SpringBoot的一系列@Conditional注解,自动装配的能力又上了一个台阶,给开发带来的灵活性就更大了。

组件注解

Spring提供了包括@Component,@Service,@Controller和@Repository等属性注解,等同于xml配置文件的:

<bean id=“…” …>

这类注解加到class上,只要该class文件处于Spring的包扫描路径下,就会在Spring启动过程中被自动注入到Spring IoC容器中。

其中,@Component是基础的组件注解,@Service表示module层的服务组件,@Controller表示是controller层的控制组件,@Repository表示是dao层的数据组件。

其实@Component,@Service,@Controller和@Repository注解底层都是基于@Component实现的,@Service、@Controller和@Repository很大意义上只是一种使用规范,除了@Repository注解有特定的异常处理机制之外,其实注入Spring的时候并没有太大的区别。

@Bean注解

@Bean是方法注解,可以和@Component以及@Configuration配合使用。被@Bean注解的方法的返回对象会作为bean加入Spring IoC容器。所以加了@Bean方法的类就相当于一个工厂类,@Bean方法相当于工厂方法,负责生成Spring Bean对象。

@Component
public class FactoryMethodComponent {

	private static int i;

	@Bean
	@Qualifier("public")
	public TestBean publicInstance() {
		return new TestBean("publicInstance");
	}

	// use of a custom qualifier and autowiring of method parameters
	@Bean
	protected TestBean protectedInstance(
			@Qualifier("public") TestBean spouse,
			@Value("#{privateInstance.age}") String country) {
		TestBean tb = new TestBean("protectedInstance", 1);
		tb.setSpouse(spouse);
		tb.setCountry(country);
		return tb;
	}

	@Bean
	private TestBean privateInstance() {
		return new TestBean("privateInstance", i++);
	}

	@Bean
	@RequestScope
	public TestBean requestScopedInstance() {
		return new TestBean("requestScopedInstance", 3);
	}
}

以上代码会生成一个name为prototypeInstance、type为TestBean的原型对象,注入到Spring IoC容器中。

@Bea也可以作用在静态方法上,主要与@Configuration配合使用,作用在静态方法上允许宿主对象尚未实例化的情况下被调用。这一场景应该只有特殊情况下有用,Spring官网就说了一种特殊情况:定制化实现post-processor bean(比如BeanFactoryPostProcessor or BeanPostProcessor),由于post-processor bean在Spring初始化的早期就会被调用,那个时候我们的@Configuration还没完成创建了,所以正好可以利用这一特性、定义为static方法。

但是静态方法不会被拦截、所以无法实现AOP,因为Spring使用CGLIB实现AOP,而CGLIB通过子类实现功能扩展的,因此静态方法是不会被拦截、无法实现AOP的。

同样的原因,因为@Configuration中的@Bean方法是需要被CGLIB扩展实现的,所以,@Configuration中的@Bean方法必须是可扩展的,不能是私有方法。

@Configuration和@Component下的@Bean注解的区别?

先看Spring官网的解释:

The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods. In contrast, invoking a method or field in a @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying.

大概意思是说,@Bean方法在@Configuration和@Component类中是不同的,不同之处在于:由于Spring中的@Component类并没有采用CGLIB进行增强,所有他的方法不会被拦截,而@Configuration在Spring中是经过CGLIB增强的,其中的方法是可以被拦截的,因此,@Configuration中的@Bean创建的类是可以加入到Spring的IoC容器中并且可以被Spring的Bean生命周期管理的,但是,@Component中的虽然可以被加入到Spring IoC容器中但是无法被Spring的Bean声明周期管理。

看明白了吗?解释一下。

关于Spring的Bean生命周期,我们后面会详细说,就是Spring容器中Bean的创建、初始化、BeanPostProcessor处理、销毁等等一系列过程,按照官网文档所说,通过@Component的@Bean方法由于宿主类不能被Spring按照生命周期进行管理、也就无法通过CGLIB实现的代理。

后果是什么呢?

@Configuration类的底层原理其实是,在Spring初始化的过程中通过CGLIB实现代理,对@Bean方法做了增强,将@Bean方法创建的对象作为Spring的Bean放入IoC容器,下次应用再次调用@Bean方法的时候,增强过的@Bean方法实际是从IoC容器获取Bean后返回、不会再次执行@Bean方法中的内容再次创建对象了。

再看一下例子,就一目了然了。

还是用我们前面的例子,首先在MyConfiguration中加入@Bean:

@Configuration
@ComponentScan(value={"springTest"})
@PropertySource("classpath:application.properties")
public class MyConfiguration {
  
    @Value("${dependencyA.name}")
    public String dependencyAName;
    @Bean
    @Primary
    public DependencyA createDependencyA(){
        return new DependencyA(dependencyAName);
    }
}

然后在Component类中加入同样的内容:

@Component
public class DependencyA2 implements IDependencyA{
    @Override
    public void test() {
        System.out.println("This is dependencyA2 running...");
    }

    @Value("${dependencyA.name}")
    public String dependencyAName;
    @Bean
    @Primary
    public DependencyA createDependencyA(){
        return new DependencyA(dependencyAName);
    }

最后,在DependencyB类中加入测试方法:

@Component
public class DependencyB implements IDependencyB{
    @Autowired
    //@Qualifier(value="dependencyA3")
    private IDependencyA dependencyA;

    @Autowired
    DependencyA2 dependencyA2;
    @Autowired
    MyConfiguration myConfiguration;
    @Autowired
    private TestBean prototypeInstance;
    public static void main(String[] args) {
        System.out.println("I am a new comer...hello world ");
    }
    @Override
    public void testB(){
        DependencyA da = dependencyA2.createDependencyA();
        DependencyA da1 = dependencyA2.createDependencyA();
        System.out.println("dependencyA"+dependencyA);
        System.out.println("da"+da);
        System.out.println("da1"+da1);
        DependencyA da2 = myConfiguration.createDependencyA();
        DependencyA da3 = myConfiguration.createDependencyA();
        System.out.println("myConfiguration"+myConfiguration);
        System.out.println("da2"+da2);
        System.out.println("da3"+da3);
    }

解释一下:在Configuration类和Component类中加入了同样的@Bean方法,创建DependencyA对象。

我们在测试类中首先通过@Autowired注解自动封装了Configuration和Component对象,在测试方法中分别调用了他们的@Bean方法,并且分别打印了Configuration对象和Component对象。

运行启动类,测试:

dependencyAspringTest.DependencyA@d706f19
daspringTest.DependencyA@79efed2d
da1springTest.DependencyA@2928854b
myConfigurationspringTest.MyConfiguration$$EnhancerBySpringCGLIB$$97084d15@27ae2fd0
da2springTest.DependencyA@d706f19
da3springTest.DependencyA@d706f19

运行结果:

  1. 打印的DependencyA对象是DependencyA@d706f19,是普通的java对象。
  2. 打印的MyConfiguration对象是MyConfiguration E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB$97084d15@27ae2fd0,是CGLIB代理对象。
  3. 两次调用Component类的@Bean方法,返回的是不同的DependencyA对象。
  4. 两次调用Configuration的@Bean方法,返回的是相同的DependencyA对象。

例子很好的验证了我们上面对官网文档的解释。

上一篇 Spring FrameWork从入门到NB -@primary、@Qualifier、@Resource&@Value

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值