网站正在建设源码,wordpress 发码插件,外包做网站不满意,询广西南宁网站运营文章目录 【README】【1】基本概念#xff1a;Configuration与Bean【2】使用AnnotationConfigApplicationContext实例化spring容器【2.1】使用java配置简单构建spring容器【2.1.1】AnnotationConfigApplicationContext与Component及JSR-330注解类一起使用 【2.2】使用register… 文章目录 【README】【1】基本概念Configuration与Bean【2】使用AnnotationConfigApplicationContext实例化spring容器【2.1】使用java配置简单构建spring容器【2.1.1】AnnotationConfigApplicationContext与Component及JSR-330注解类一起使用 【2.2】使用register(Class?)以编程方式构建容器【2.2.1】AnnotationConfigApplicationContext#refresh()源码 【2.3】启用AnnotationConfigApplicationContext扫描功能【2.3.1】补充Configuration注解 【2.4】支持使用AnnotationConfigWebApplicationContext构建springweb容器 【3】组合基于java的配置【3.1】使用 Import注解组合多个配置类【3.1.1】对Import引入的BeanDefinition注入依赖装配Import注解引入的bean【3.1.2】全限定导入bean以便导航 【3.2】以xml配置为中心并使用component-scan元素扫描java配置类配置spring容器的2种方式【3.2.1】代码实践 【3.3】以java配置为中心并使用ImportResoure注解引入xml配置【3.3.1】代码实践 【3.4】使用PropertySource加载属性文件 【4】使用Bean注解【4.1】声明Bean【4.2】Bean标注方法返回的bean的装配注入依赖【4.3】接收生命周期回调【4.3.1】Bean注册的bean生命周期回调代码实践 【4.4】指定bean作用范围【4.4.1】使用Scope注解【4.4.2】查找方法注入Lookup method injection仅了解本文不展开【4.4.3】自定义bean的名称 【5】所有Configuration类使用CGLIB进行子类化【5.1】java配置类内部运行原理重要 【README】
本文内容总结自spring官方文档 spring-framework-reference 章节4.12【Java-based container configuration】 墙裂推荐
代码参见 github: springbootDiscover chapter00
1新增maven依赖因为 Configuration 与 Bean注解定义在org.springframework.context.annotation包下
dependenciesdependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion6.1.10/version/dependency/dependencies【1】基本概念Configuration与Bean
1java配置支持spirng容器的 核心组件是Configuration标注的类 。这些类主要包含Bean标注的方法这些方法定义springIOC容器管理对象的初始化配置以及实例化逻辑。
2使用Configuration标注的类表明这些类可以被spring IOC容器作为bean定义的数据源。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration
public class AppConfig {Beanpublic MyService myService() {return new MyServiceImpl();}
}AppConfig等价于下面的xml配置
beansbean idmyService classcom.acme.services.MyServiceImpl/
/beans3Bean的角色 如上述代码所示 Bean扮演了 bean元素的角色 接下来我们介绍使用java配置创建spring容器的多种方法 【2】使用AnnotationConfigApplicationContext实例化spring容器
1下面章节描述spring的AnnotationConfigApplicationContext它是spring3引入的
2作为ApplicationContext的实现类AnnotationConfigApplicationContext不仅接收Configuration类作为输入也可以接受Component类以及被JSR-330注解标注的类 (JSR, Java Specification Request java规范请求 )
JSR-330注解介绍JSR-330注解就是支持java实现依赖注入的注解如 Inject Qualilfier参见spirng 使用JSR-330标准注解 github JSR-330: Dependency Injection for Java. Java Community Process: JSR 330: Dependency Injection for Java 3当Configuration类作为输入Configuration类本身会被注册为bean定义该类中所有被Bean标注的方法返回的bean也会注册为bean定义
当Component与JSR-330注解标注的类作为输入这些类也会被注册为bean定义并且假定在必要时这些类中使用依赖注入元注解如Autowired or Inject 【2.1】使用java配置简单构建spring容器
1与使用spring xml文件作为输入数据源实例化ClassPathXmlApplicationContext容器类似Configuration也可以作为输入数据源实例化AnnotationConfigApplicationContext容器即注解配置的spring容器基于注解实例化容器可以完全不使用xml文件 其实 ClassPathXmlApplicationContext 与 AnnotationConfigApplicationContext 是兄弟它们都是spring容器ApplicationContext的子类只不过配置信息加载的方式不同一个是xml文件一个是注解
【JavaBasedContainerMain】基于java配置的spring容器构建
public class JavaBasedContainerMain {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(AppConfig00.class);HelloService helloService context.getBean(HelloService.class);helloService.sayHello(tom);}
}【运行结果】
hello tom【AppConfig00】 Configuration类配置类
Configuration
public class AppConfig00 {Beanpublic HelloService helloService() {return new HelloService();}
}【HelloService】服务类
public class HelloService {public void sayHello(String user) {System.out.println(hello user);}
}【2.1.1】AnnotationConfigApplicationContext与Component及JSR-330注解类一起使用
1AnnotationConfigApplicationContext不局限于与Configuration类一起使用任何 Component或 JSR330注解类也可以作为其构造参数传入。
【JavaBasedContainerUsingJsr330Main】 基于java的使用Jsr-330注解的容器
public class JavaBasedContainerUsingJsr330Main {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(Jsr330Service.class, Jsr330Depository.class);context.getBean(Jsr330Service.class).saveUser(zhangsan);}
}注意 上述代码中 Jsr330Service与Jsr330Depository需要使用spring的依赖注入注解Autowire进行装配如果使用JSR-330注解如Qualifier会报错
【Jsr330Service】
package com.tom.chapter00.classwithjsr330;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;Component
public class Jsr330Service {// Autowired是spring注解 这里不能使用 Qualifier (JSR-330注解)Autowiredprivate Jsr330Depository jsr330Depository;public void saveUser(String userName) {jsr330Depository.saveUser(userName);}
}【Jsr330Depository】
package com.tom.chapter00.classwithjsr330;import org.springframework.stereotype.Component;Component
public class Jsr330Depository {public void saveUser(String userName) {System.out.println(saveUser() name userName 成功);}
}【2.2】使用register(Class?)以编程方式构建容器
1AnnotationConfigApplicationContext可以通过使用无参构造器进行实例化实例化后使用register方法进行配置 当使用编程方式构建容器时这个方法特别有用
【SpringContainerUsingAnnotationConfigApplicationContextNoArgConstructor】 使用AnnotationConfigApplicationContext无参构造器实例化注解配置spring容器 ;
package com.tom.chapter00;import com.tom.chapter00.service.HelloService;
import com.tom.chapter00.service.HelloService02;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class SpringContainerUsingAnnotationConfigApplicationContextNoArgConstructor {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext();context.register(AppConfig00.class, AppConfig02.class); // 调用register()方法注册配置context.refresh(); // 注册完成后调用 refresh方法刷新 context.getBean(HelloService.class).sayHello(Musk);context.getBean(HelloService02.class).sayHello(Trump);}
}【步骤】 使用register(Class?)以编程方式构建容器步骤 ;
步骤1首先使用AnnotationConfigApplicationContext无参构造器实例化spring容器步骤2调用register()方法注册配置查看源码可知这是在收集BeanDefinition步骤3注册完成后调用 refresh方法刷新查看源码可知根据BeanDefinition实例化bean
【运行效果】
HelloService#sayHell(): hello Musk
HelloService2#sayHell(): hello Trump【AppConfig00】
Configuration
public class AppConfig00 {Beanpublic HelloService helloService() {return new HelloService();}
}【HelloService】
public class HelloService { public void sayHello(String user) {System.out.println(HelloService#sayHell(): hello user);}
}【2.2.1】AnnotationConfigApplicationContext#refresh()源码
public void refresh() throws BeansException, IllegalStateException {this.startupShutdownLock.lock();try {this.startupShutdownThread Thread.currentThread();StartupStep contextRefresh this.applicationStartup.start(spring.context.refresh);this.prepareRefresh();ConfigurableListableBeanFactory beanFactory this.obtainFreshBeanFactory();this.prepareBeanFactory(beanFactory);try {this.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess this.applicationStartup.start(spring.context.beans.post-process);this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);beanPostProcess.end();this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (Error | RuntimeException var12) {if (this.logger.isWarnEnabled()) {this.logger.warn(Exception encountered during context initialization - cancelling refresh attempt: var12);}this.destroyBeans();this.cancelRefresh(var12);throw var12;} finally {contextRefresh.end();}} finally {this.startupShutdownThread null;this.startupShutdownLock.unlock();}}【2.3】启用AnnotationConfigApplicationContext扫描功能
1使用xml配置扫描注解标注的bean如下
beanscontext:component-scan base-packagecom.acme/
/beans上述xml配置spring将会扫描com.acme包中Component标注的类这些类被作为spring beanDefinition在容器中注册
2AnnotationConfigApplicationContext公开了scan()方法支持相同的组件扫描功能
【SpringContainerUsingAnnotationConfigApplicationContextScan】
package com.tom.chapter00;import com.tom.chapter00.service.HelloService02WithComponentAnnotation;
import com.tom.chapter00.service.HelloServiceWithComponentAnnotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class SpringContainerUsingAnnotationConfigApplicationContextScan {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext();context.scan(com.tom.chapter00.service); // 调用scan()方法收集BeanDefinitioncontext.refresh(); // 根据BeanDefinition实例化bean context.getBean(HelloServiceWithComponentAnnotation.class).sayHello(Musk);context.getBean(HelloService02WithComponentAnnotation.class).sayHello(Trump);}
}【步骤】 AnnotationConfigApplicationContext使用组件扫描实例化bean
步骤1首先使用AnnotationConfigApplicationContext无参构造器实例化注解配置spring容器步骤2调用scan()方法扫描指定包下BeanDefinition查看源码可知这是在收集BeanDefinition步骤3注册完成后调用 refresh方法刷新查看源码可知根据BeanDefinition实例化bean
【执行效果】
HelloServiceWithComponentAnnotation#sayHello: hello Musk
HelloService02WithComponentAnnotation#sayHello: 02 hello Trump【HelloServiceWithComponentAnnotation】
package com.tom.chapter00.service;import org.springframework.stereotype.Component;Component
public class HelloServiceWithComponentAnnotation {public void sayHello(String user) {System.out.println(HelloServiceWithComponentAnnotation#sayHello: hello user);}
}【2.3.1】补充Configuration注解
1Configuration使用Component注解的注解而Component称为元注解因为它是标注其他注解的注解 因此Configuration类可以被scan()扫描到
假设Configuration类声明在com.a.b.c包下及任意子包下AnnotationConfigApplicationContext容器在调用scan()方法时会扫描到该Configuration类而调用refresh()方法时Configuration类中被Bean标注的方法会被处理把方法返回值注册为容器中的bean
【SpringContainerUsingAnnotationConfigApplicationContextScanConfiguration】 扫描Configuration类 扫描com.tom.chapter00包及其子包下Component及Configuration类
public class SpringContainerUsingAnnotationConfigApplicationContextScanConfiguration {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext();System.out.println(before scan());context.scan(com.tom.chapter00);System.out.println(after scan());System.out.println(before refresh());context.refresh();System.out.println(after refresh());context.getBean(HelloService02.class).sayHello(Musk);}
}【执行效果】
before scan()
after scan()
before refresh()
AppConfig00 构造器
AppConfig02 构造器
after refresh()
HelloService2#sayHell(): hello Musk 【AppConfig02】 Configuration类包名为com.tom.chapter00.config是com.tom.chapter00的子包
package com.tom.chapter00.config;import com.tom.chapter00.service.HelloService02;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration
public class AppConfig02 {public AppConfig02() {System.out.println(AppConfig02 构造器);}Beanpublic HelloService02 helloService02() {return new HelloService02();}
}
【HelloService02】
public class HelloService02 {public void sayHello(String user) {System.out.println(HelloService2#sayHell(): hello user);}
}【2.4】支持使用AnnotationConfigWebApplicationContext构建springweb容器
1AnnotationConfigApplicationContext的WebApplicationContext变体可以与AnnotationConfigWebApplicationContext一起使用
当配置spring ContextLoaderListener servlet监听器springmvc DispatcherServlet等可以使用AnnotationConfigWebApplicationContext作为参数
2以下web.xml代码片段配置了一个典型的springmvc web容器
web-app!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContextinstead of the default XmlWebApplicationContext --context-paramparam-namecontextClass/param-nameparam-valueorg.springframework.web.context.support.AnnotationConfigWebApplicationContext/param-value/context-param!-- Configuration locations must consist of one or more comma- or space-delimitedfully-qualified Configuration classes. Fully-qualified packages may also bespecified for component-scanning --context-paramparam-namecontextConfigLocation/param-nameparam-valuecom.acme.AppConfig/param-value/context-param!-- Bootstrap the root application context as usual using ContextLoaderListener --listenerlistener-classorg.springframework.web.context.ContextLoaderListener/listener-class/listener!-- Declare a Spring MVC DispatcherServlet as usual --servletservlet-namedispatcher/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContextinstead of the default XmlWebApplicationContext --init-paramparam-namecontextClass/param-nameparam-valueorg.springframework.web.context.support.AnnotationConfigWebApplicationContext/param-value/init-param!-- Again, config locations must consist of one or more comma- or space-delimitedand fully-qualified Configuration classes --init-paramparam-namecontextConfigLocation/param-nameparam-valuecom.acme.web.MvcConfig/param-value/init-param/servlet!-- map all requests for /app/* to the dispatcher servlet --servlet-mappingservlet-namedispatcher/servlet-nameurl-pattern/app/*/url-pattern/servlet-mapping
/web-app【3】组合基于java的配置
【3.1】使用 Import注解组合多个配置类
1Import注解支持从其他配置类加载bean Definition就像在xml文件中使用 import元素来帮助模块化配置一样
【ImportAnnotationMain】Import注解测试main
package com.tom.chapter00.config.importannotation;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class ImportAnnotationMain {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(AppConfigB.class);context.getBean(DiyA.class).print();}
}代码说明 在实例化AnnotationConfigApplicationContext容器时无需指定AppConfigA.class与AppConfigB.class 而仅需要指定AppConfigB.class即可 因为AppConfigB 通过Import注解引入了 AppConfigA中的BeanDefinition
【AppConfigA】
package com.tom.chapter00.config.importannotation;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration
public class AppConfigA { Beanpublic DiyA diyA() {return new DiyA();}
}public class DiyA {public void print() {System.out.println(this is diy a);}
}【AppConfigB】
Configuration
Import(AppConfigA.class) // AppConfigB使用Import引入或导入AppConfigA
public class AppConfigB {Beanpublic DiyB diyB() {return new DiyB();}
}public class DiyB {public void print() {System.out.println(this is diy b);}
} 【3.1.1】对Import引入的BeanDefinition注入依赖装配Import注解引入的bean
1大多数场景中一个Configuration配置类A中的Bean依赖另一个Configuration配置类B中的bean
这在xml配置文件中很好解决只需要配置类A中的bean元素使用ref元素引入配置类B的bean即可
2通过java配置的容器中需要使用类似于ref元素的语法对Import引入的bean进行装配
注意 Configuration标注的配置类始终是一个bean这意味着我们可以使用 Autowire注入元数据即装配Configuration类
【ImportAnnotationInjectDependencyMain】
public class ImportAnnotationInjectDependencyMain {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(SystemConfig01.class);context.getBean(UserService.class).saveUser(Trump);}
}【配置类】
Configuration
Import({UserServiceConfig.class, UserRepositoryConfig.class})
public class SystemConfig01 {
}Configuration
public class UserServiceConfig {private Autowired UserRepository userRepository; // 这个repository依赖于另一个配置类UserRepositoryConfig中的bean public Bean UserService userService() {return new UserService(userRepository);}
}Configuration
public class UserRepositoryConfig {public Bean UserRepository userRepository() {return new UserRepository(); }
}【业务bean】
public class UserService {private UserRepository userRepository;public UserService(UserRepository userRepository) {this.userRepository userRepository;}public void saveUser(String name) {userRepository.saveUser(name);}
}
public class UserRepository {public void saveUser(String name) {System.out.println(UserRepository#saveUser() 保存用户成功 name name);}
}【3.1.2】全限定导入bean以便导航
13.1.1中的问题 UserServiceConfig使用Autowired装配UserRepository但这个UserRepository具体定义在哪个Configuration类是不清楚的这不利于类的导航查找
解决方法明确装配的bean所属的配置类考虑装配另一个配置类B本身到当前配置类A;
【UserServiceConfig2】 修改后的版本使用另一个配置类本身装配当前配置类
Configuration
public class UserServiceConfig2 {// 装配的是Configuration类的bean而不是具体的Repository beanprivate Autowired UserRepositoryConfig userRepositoryConfig; public Bean UserService userService() {return new UserService(userRepositoryConfig.userRepository());}
}【UserServiceConfig】之前的版本使用具体bean装配当前配置类
Configuration
public class UserServiceConfig {private Autowired UserRepository userRepository;public Bean UserService userService() {return new UserService(userRepository);}
}2 UserServiceConfig2中的代码还有点问题 UserServiceConfig2 与 UserRepositoryConfig高度耦合了
解决方法Autowired UserRepositoryConfig userRepositoryConfig修改 UserRepositoryConfig 为抽象定义如接口或抽象类
【ImportAnnotationInjectDependencyMain03】
public class ImportAnnotationInjectDependencyMain03 {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(SystemConfig03.class);context.getBean(UserService.class).saveUser(Trump);}
}【总配置类-SystemConfig03】 引入的UserRepositoryConfigImpl是IUserRepositoryConfig接口的实现类
Configuration
// 导入的是用户仓库配置接口的实现类的class而非其接口IUserRepositoryConfig的class
Import({UserServiceConfig3.class, UserRepositoryConfigImpl.class})
public class SystemConfig03 {
}【其他配置类】
Configuration
public class UserServiceConfig3 {// 这里的属性类型为接口非具体实现以解耦private Autowired IUserRepositoryConfig userRepositoryConfig; public Bean UserService userService() { return new UserService(userRepositoryConfig.userRepository());}
}
// 用户仓库配置接口定义
public interface IUserRepositoryConfig { public UserRepository userRepository();
}
// 用户仓库配置接口的实现类
public class UserRepositoryConfigImpl implements IUserRepositoryConfig { Overridepublic UserRepository userRepository() {return new UserRepository();}
} 【3.2】以xml配置为中心并使用component-scan元素扫描java配置类配置spring容器的2种方式
1spring提供的Configuration配置类的支持并不是100%替换xml配置xml配置也有一些优点如xml命名空间就是一种完美的配置容器的方式
2配置spring容器有2种方式
使用xml文件方式且spring容器使用ClassPathXmlApplicationContext类使用java配置方式且spring容器使用AnnotationConfigApplicationContext 类且可以按需使用ImportResource注解引入xml配置文件 【3.2.1】代码实践
【ComposeJavaAndXmlMain】
public class ComposeJavaAndXmlMain {public static void main(String[] args) {ApplicationContext container new ClassPathXmlApplicationContext(chapter00/beans00.xml);container.getBean(ComposeJavaAndXmlService.class).saveUser(Trump);}
}【执行效果】
ComposeJavaAndXmlRepository#saveUser(): nameTrump, urlwww.baidu.com【beans00.xml】
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdbeans!-- 扫描Component注解标注的类包括Configuration注册到spring容器 --context:component-scan base-packagecom.tom.chapter00.composejavaandxml/context:property-placeholder locationclasspath:/chapter00/jdbc.properties/bean classcom.tom.chapter00.composejavaandxml.ComposeJavaAndXmlRepositoryproperty nameurl value${jdbc.url}//bean/beans
/beans【/chapter00/jdbc.properties】 属性文件
jdbc.urlwww.baidu.com【ComposeJavaAndXmlService】
public class ComposeJavaAndXmlService {private ComposeJavaAndXmlRepository composeJavaAndXmlRepository;public ComposeJavaAndXmlService(ComposeJavaAndXmlRepository composeJavaAndXmlRepository) {this.composeJavaAndXmlRepository composeJavaAndXmlRepository;}public void saveUser(String name) {composeJavaAndXmlRepository.saveUser(name);}
}【ComposeJavaAndXmlRepository】
public class ComposeJavaAndXmlRepository {private String url;public void saveUser(String name) {System.out.println(ComposeJavaAndXmlRepository#saveUser(): name name , url url);}public String getUrl() {return url;}public void setUrl(String url) {this.url url;}
}【补充】文件目录结构 【3.3】以java配置为中心并使用ImportResoure注解引入xml配置
1在spring应用中 Configuration注解配置spring容器是主要方式但仍然有可能使用到xml配置可以通过ImportResoure注解引入xml配置
【3.3.1】代码实践
【ImportResourceComposeJavaAndXmlMain】
public class ImportResourceComposeJavaAndXmlMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(UserRepositoryConfigUsingImportResource.class);container.getBean(ComposeJavaAndXmlRepository.class).saveUser(Trump);}
}【执行效果】
ComposeJavaAndXmlRepository#saveUser(): nameTrump, urlwww.baidu.com【UserRepositoryConfigUsingImportResource】 使用 ImportResource引入xml配置的配置类
Configuration
ImportResource(classpath:/chapter00/beans01.xml)
public class UserRepositoryConfigUsingImportResource {private Value(${jdbc.url}) String url;public Bean ComposeJavaAndXmlRepository composeJavaAndXmlRepository() {ComposeJavaAndXmlRepository composeJavaAndXmlRepository new ComposeJavaAndXmlRepository();composeJavaAndXmlRepository.setUrl(url);return composeJavaAndXmlRepository;}
}【beans01.xml】
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdbeanscontext:property-placeholder locationclasspath:/chapter00/jdbc.properties//beans
/beans【jdbc.properties】
jdbc.urlwww.baidu.com【3.4】使用PropertySource加载属性文件
1使用PropertySource加载属性文件以访问所有定义的属性
【属性文件】
// kafka.properties
kafka.cluster.url192.168.1.1
kafka.cluster.nameTomKafka// redis.properties
redis.cluster.url192.168.1.2
redis.cluster.nameTomRedis【属性文件路径】 【PropertySourceMain】
public class PropertySourceMain {public static void main(String[] args) {AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(PropertySourceConfig.class);// 获取beanTomCluster tomCluster context.getBean(TomCluster.class);System.out.println(tomCluster);System.out.println(tomCluster.getKafkaClusterUrl());// 通过Environment获取属性ConfigurableEnvironment env context.getEnvironment();System.out.println(env.getProperty(kafka.cluster.name));System.out.println(env.getProperty(kafka.cluster.url));System.out.println(env.getProperty(redis.cluster.name));System.out.println(env.getProperty(redis.cluster.url));}
}【运行效果】
TomCluster{kafkaClusterUrl192.168.1.1, kafkaClusterNameTomKafka, redisClusterUrl192.168.1.2, redisClusterNameTomRedis}
192.168.1.1
TomKafka
192.168.1.1
TomRedis
192.168.1.2【PropertySourceConfig】
package com.tom.chapter00.propertysource;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;Configuration
PropertySource(value {classpath:chapter00/kafka.properties, classpath:chapter00/redis.properties})
public class PropertySourceConfig {Beanpublic TomCluster tomCluster() {return new TomCluster();}
}
【TomCluster】
package com.tom.chapter00.propertysource;import org.springframework.beans.factory.annotation.Value;public class TomCluster {Value(${kafka.cluster.url}) private String kafkaClusterUrl;Value(${kafka.cluster.name})private String kafkaClusterName;Value(${redis.cluster.url})private String redisClusterUrl;Value(${redis.cluster.name})private String redisClusterName;public String getKafkaClusterUrl() {return kafkaClusterUrl;}public String getKafkaClusterName() {return kafkaClusterName;}public String getRedisClusterUrl() {return redisClusterUrl;}public String getRedisClusterName() {return redisClusterName;}Overridepublic String toString() {return TomCluster{ kafkaClusterUrl kafkaClusterUrl \ , kafkaClusterName kafkaClusterName \ , redisClusterUrl redisClusterUrl \ , redisClusterName redisClusterName \ };}
}【4】使用Bean注解
1Bean注解定义 Bean注解类似于xml文件中的bean元素该注解支持bean元素的一些属性如inin-method , destory-method, autowiring ,name 等。你可以在 Configuration类或Component类中使用 Bean注解 【4.1】声明Bean
1可以使用Bean标注一个方法以此来定义bean 你使用这个方法在ApplicationContext中注册bean的定义 bean类型指定为方法返回类型 默认情况下方法名称就是bean名称
【BeanAnnotationMain】
public class BeanAnnotationMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(BeanAnnotationConfig.class);System.out.println(container.getBean(UserService.class));System.out.println(container.getBean(userService));}
}
// com.tom.chapter00.config.importannotation.injectdependency.UserService6aeb35e6
// com.tom.chapter00.config.importannotation.injectdependency.UserService6aeb35e6container.getBean(“userService”) 与 container.getBean(UserService.class) 获取的是同一个bean所以UserService userService()方法注册的bean的名称就是方法名称userService
【BeanAnnotationConfig】
package com.tom.chapter00.beanannotation;import com.tom.chapter00.config.importannotation.injectdependency.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration
public class BeanAnnotationConfig {Beanpublic UserService userService() {return new UserService();}
} 【4.2】Bean标注方法返回的bean的装配注入依赖
1当Bean注册的bean依赖另一个bean时仅需要调用另一个bean的注册方法即可注入依赖如下
【BeanAnnotationMain2】
public class BeanAnnotationMain2 {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(BeanAnnotationConfig2.class);System.out.println(container.getBean(repositoryA));container.getBean(ServiceA.class).printRepository();}
}【执行效果】 显然 容器中名为repositoryA的bean与ServiceA中装配的类型为RepositoryA的bean是同一个bean
com.tom.chapter00.beanannotation.repository.RepositoryA4c583ecf
repositoryAcom.tom.chapter00.beanannotation.repository.RepositoryA4c583ecf 【BeanAnnotationConfig2】配置类 调用方法即可注入依赖
Configuration
public class BeanAnnotationConfig2 {public Bean RepositoryA repositoryA() {return new RepositoryA();}public Bean ServiceA serviceA() {return new ServiceA(repositoryA()); // 调用方法即可注入依赖}
}【ServiceA】
public class ServiceA {private RepositoryA repositoryA;public ServiceA(RepositoryA repositoryA) {this.repositoryA repositoryA;}public void printRepository() {System.out.println(repositoryA repositoryA);}
}【4.3】接收生命周期回调
1任何使用Bean定义的类都可以使用 来自JSR-250的PostConstruct 与 PreDestory注解
参见 JSR-250的PostConstruct 与 PreDestory注解
2spring支持bean的生命周期回调如果bean实现了InitializingBean DisposableBean或者 Lifecycle 它们对应的方法就会被容器调用
以Aware为后缀的标准接口集合也支持生命周期回调如BeanFactoryAware, BeanNameAware, MessageSourceAware, ApplicationContextAware
3Bean注解支持指定任意初始化与销毁的回调方法这类似于xml配置文件中 bean元素中 init-method 与 destroy-method属性
【Bean注解定义】 package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
public interface Bean {AliasFor(name)String[] value() default {};AliasFor(value)String[] name() default {};boolean autowireCandidate() default true;String initMethod() default ;String destroyMethod() default (inferred);
} 【4.3.1】Bean注册的bean生命周期回调代码实践
【BeanAnnotationLifeCycleMain】
public class BeanAnnotationLifeCycleMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(BeanAnnotationLifeCycleConfig.class);// 注册了关闭钩子当容器销毁bean时才会调用 destory()方法container.registerShutdownHook();}
}【执行效果】
BeanWithLifeCycleCallback init()
BeanWithLifeCycleCallback destory()【BeanAnnotationLifeCycleConfig】 Bean注解指定bean初始时方法与销毁方法
Configuration
public class BeanAnnotationLifeCycleConfig {Bean(initMethod init, destroyMethod destory)public BeanWithLifeCycleCallback beanWithInitMethod() {return new BeanWithLifeCycleCallback();}
}【BeanWithLifeCycleCallback】带有生命周期回调的Bean
public class BeanWithLifeCycleCallback {public void init() {System.out.println(BeanWithLifeCycleCallback init());}public void destory() {System.out.println(BeanWithLifeCycleCallback destory());}}【补充】上述注册生命周期初始化回调方法init等同于在构造器中执行init()方法
Configuration
public class BeanAnnotationLifeCycleConfig2 {Beanpublic BeanWithLifeCycleCallback beanWithInitMethod() {BeanWithLifeCycleCallback beanWithLifeCycleCallback new BeanWithLifeCycleCallback();beanWithLifeCycleCallback.init(); // 类似与显式调用 init() 方法 return beanWithLifeCycleCallback;}
}【提示】如果我们直接使用java配置容器我们可以直接对目标对象或bean执行任何操作而并不是总是依赖spring的生命周期回调 如BeanAnnotationLifeCycleConfig2那样 【4.4】指定bean作用范围
【4.4.1】使用Scope注解
1你可以指定使用Bean定义的bean的作用范围。你可以使用任何一种标准的作用范围。beans-factory-scopes 2默认scope作用范围是singleton-单例可以使用Scope注解重写scope
【ScopeOverwriteMain】重写bean的作用域main入口
public class ScopeOverwriteMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(ScopeOverrideConfig.class);// 获取 RepositoryA实例每次获取都会创建一个新实例因为其scope为prototypeSystem.out.println(container.getBean(RepositoryA.class));System.out.println(container.getBean(RepositoryA.class));// 获取 RepositoryB实例每次获取都会返回同一个实例因为其scope为singletonSystem.out.println(container.getBean(RepositoryB.class));System.out.println(container.getBean(RepositoryB.class));}
// com.tom.chapter00.beanannotation.repository.RepositoryA59474f18
// com.tom.chapter00.beanannotation.repository.RepositoryA65fb9ffc
// com.tom.chapter00.beanannotation.repository.RepositoryB3e694b3f
// com.tom.chapter00.beanannotation.repository.RepositoryB3e694b3f
}【ScopeOverrideConfig】
package com.tom.chapter00.beanannotation.scope.scopeoverride;import com.tom.chapter00.beanannotation.repository.RepositoryA;
import com.tom.chapter00.beanannotation.repository.RepositoryB;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;Configuration
public class ScopeOverrideConfig {BeanScope(prototype) // 原型作用域每次获取bean都会创建1个新实例 public RepositoryA repositoryA() {return new RepositoryA();}BeanScope(singleton) // 单例作用域自始至终都只有1个实例 public RepositoryB repositoryB() {return new RepositoryB();}
}【4.4.2】查找方法注入Lookup method injection仅了解本文不展开
1查找方法注入是你很少使用到的高级功能 查找方法注入在单例范围bean依赖原型范围bean场景下有用
public abstract class CommandManager {public Object process(Object commandState) {// grab a new instance of the appropriate Command interfaceCommand command createCommand();// set the state on the (hopefully brand new) Command instancecommand.setState(commandState);return command.execute();}// okay... but where is the implementation of this method?protected abstract Command createCommand();
}2使用java配置支持你可以创建CommandManager子类其抽象方法createCommand()被重写以便查找新的原型command对象
Bean
Scope(prototype)
public AsyncCommand asyncCommand() {AsyncCommand command new AsyncCommand();// inject dependencies here as requiredreturn command;
}Bean
public CommandManager commandManager() {// return new anonymous implementation of CommandManager with command() overridden// to return a new prototype Command objectreturn new CommandManager() {protected Command createCommand() {return asyncCommand();}}
}解说CommandManager是单例的但其createCommand()方法返回的Command对象是原型的 【4.4.3】自定义bean的名称
1默认情况下Bean注解的方法名作为实例化bean的名称即bean名称repositoryA1参见下文的BeanNameConfig但若代码指定了Bean注解的name属性userRepo所以bean名称userRepo
2Bean注解的name属性接收一个string数组以便为一个bean指定多个别名
【BeanNameConfigMain】bean名称配置测试main
public class BeanNameConfigMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(BeanNameConfig.class);System.out.println(container.getBean(repositoryA1));System.out.println(container.getBean(userRepo));System.out.println(container.getBean(userRepo1));System.out.println(container.getBean(userRepo2));System.out.println(container.getBean(userRepo3));}
}【执行结果】
com.tom.chapter00.beanannotation.repository.RepositoryA692f203f
com.tom.chapter00.beanannotation.repository.RepositoryA48f2bd5b
com.tom.chapter00.beanannotation.repository.RepositoryA7b2bbc3 // 相同
com.tom.chapter00.beanannotation.repository.RepositoryA7b2bbc3// 相同
com.tom.chapter00.beanannotation.repository.RepositoryA7b2bbc3// 相同 【BeanNameConfig】
Configuration
public class BeanNameConfig {Beanpublic RepositoryA repositoryA1() {return new RepositoryA();}Bean(userRepo) // 为bean指定1个别名 public RepositoryA repositoryA2() {return new RepositoryA();}Bean(name {userRepo1, userRepo2, userRepo3}) // 为bean指定3个别名public RepositoryA repositoryAWithMultipleName() {return new RepositoryA();}
}【Bean注解源码】
Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
Retention(RetentionPolicy.RUNTIME)
Documented
public interface Bean {AliasFor(name)String[] value() default {};AliasFor(value)String[] name() default {};boolean autowireCandidate() default true;String initMethod() default ;String destroyMethod() default (inferred);
}【5】所有Configuration类使用CGLIB进行子类化
1Bean标注的方法被调用2次会有什么效果
【BeanMoreInfoConfigMain】
public class BeanMoreInfoConfigMain {public static void main(String[] args) {AnnotationConfigApplicationContext container new AnnotationConfigApplicationContext(BeanMoreInfoConfig.class);System.out.println(container.getBean(userService1, UserService.class).getUserRepository());System.out.println(container.getBean(userService2, UserService.class).getUserRepository());}
}【打印效果】 调用2次userRepository() 获取的是同一个UserRepository bean 为什么 ;
// com.tom.chapter00.config.importannotation.injectdependency.UserRepository4c583ecf
// com.tom.chapter00.config.importannotation.injectdependency.UserRepository4c583ecf【BeanMoreInfoConfig】
Configuration
public class BeanMoreInfoConfig {Beanpublic UserService userService1() {return new UserService(userRepository()); // 第1次调用userRepository()}Beanpublic UserService userService2() {return new UserService(userRepository()); // 第2次调用userRepository()}Beanpublic UserRepository userRepository() {return new UserRepository();}
}【5.1】java配置类内部运行原理重要
1由上述可知调用2次userRepository() 获取的是同一个UserRepository bean
原理解说所有 Configuration类在启动时使用CGLIB子类化。 简单理解是 Configuration类作为父类CGLIB通过字节码增强产生一个子类 在获取bean时首先子类中子方法会校验缓存中是否存在该bean若有则直接返回否则再调用父方法并创建新实例
2本例的注意点
本例演示的场景是基于单例bean若bean范围不是单例则运行原理不同注意到为了让java配置的容器可以正常运行你必须在依赖清单中包含CGLIB jar由于CGLIB在启动时动态增强了一些功能所以有一些限制条件 Configuration类不应该是final否则不能被继承Configuration类应该有一个无参构造器