1、Spring 容器繼承圖
2、控制反轉和依賴注入
- 什麼是控制反轉?我覺得有必要先了解軟體設計的一個重要思想:依賴倒置原則(Dependency Inversion Principle )
- 什麼是依賴倒置原則?假設我們設計一輛汽車:先設計輪子,然後根據輪子大小設計底盤,接着根據底盤設計車身,最後根據車身設計好整個汽車。這裡就出現了一個“依賴”關系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。
從上圖來看好像沒什麼問題,但萬一輪胎尺寸改了,那麼地盤需要改,地盤改了,車身也改了,讓後整個汽車構造都改了,牽一發而動全身。但如果反過來假如汽車公司決定修改輪胎的 我們就隻需要改動輪子的設計,而不需要動底盤,車身,汽車 的設計了。
IOC容器的核心思想在于資源不由使用資源的雙方管理,而由不使用資源的第三方管理,這可以帶來很多好處。第 一,資源集中管理,實作資源的可配置和易管理。第二,降低了使用資源雙方的依賴程度,也就是我們說的耦合度。舉個例子,假如我們需要找個房子,我們需要租個房子,我們可以自己去找房源(但我們需要依賴房源等因素),也可以交由中介進行管理(這樣子我們就是不直接依賴房源了,這個交給中介管理)。
3、Spring IOC 容器注解使用
1、我們很多時候通過基于xml的形式定義Bean的資訊。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.cym.bean.compent.User"></bean>
</beans>
讀取容器中的bean的方式
public class BeanMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
Object user = classPathXmlApplicationContext.getBean("user");
System.out.println(user);
// AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
// Object user1 = annotationConfigApplicationContext.getBean("person");
// System.out.println(user1);
}
}
2、基于讀取配置類的形式定義Bean資訊
@Configuration
public class BeanConfig {
@Bean(name = "person")
public User user(){
return new User();
}
}
注意 : 通過 @Bean 的形式是使用的話, bean 的預設名稱是方法名,若 @Bean(value="bean 的名稱 ") 那麼 bean 的名稱是指定的 去容器中讀取 Bean 的資訊( 傳入配置類 )
public class BeanMain {
public static void main(String[] args) {
// ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("bean.xml");
// Object user = classPathXmlApplicationContext.getBean("user");
// System.out.println(user);
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Object user1 = annotationConfigApplicationContext.getBean("person");
System.out.println(user1);
}
}
3、在配置類上寫 @CompentScan 注解來進行包掃描
@Configuration
@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {
}
從容器中擷取bean
public class UserMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext acx = new AnnotationConfigApplicationContext(UserConfig.class);
String[] beans = acx.getBeanDefinitionNames();
for (String bean :beans){
System.out.println("bean定義的資訊:"+bean);
}
}
}
排除用法 excludeFilters( 排除 @Controller 注解的 , 和UserService的bean )
@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
@ComponentScan(basePackages = {"com.cym.componentscan"},
excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {
}
包含用法 includeFilters , 注意,若使用包含的用法, 需要把 useDefaultFilters 屬性設定為 false ( true 表 示掃描全部的)
@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
@ComponentScan(basePackages = {"com.cym.componentscan"},
includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
public class UserConfig {
}
@ComponentScan.Filter type 的類型
注解形式的 FilterType.ANNOTATION | @Controller @Service @Repository @Compent |
指定類型的 FilterType.ASSIGNABLE_TYPE | |
aspectj 類型的 | FilterType.ASPECTJ( 不常用 ) |
正規表達式的 | FilterType.REGEX(不常用 ) |
自定義的 | FilterType.CUSTOM |
package org.springframework.context.annotation;
public enum FilterType {
//注解形式 比如@Controller @Service @Repository @Compent
ANNOTATION,
//指定的類型
ASSIGNABLE_TYPE,
//aspectJ形式的
ASPECTJ,
//正規表達式的
REGEX,
//自定義的
CUSTOM;
private FilterType() {
}
}
FilterType.CUSTOM 自定義類型如何使用
public class MyFilterType implements TypeFilter {
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//擷取目前類的注解源資訊
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//擷取目前類的class的源資訊
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//擷取目前類的資源資訊
Resource resource = metadataReader.getResource();
//掃描UserController、UserService
if (classMetadata.getClassName().contains("UserController") || classMetadata.getClassName().contains("UserService")) {
return true;
}
return false;
}
}
配置類
@Configuration
//@ComponentScan(basePackages = {"com.cym.componentscan"})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = {Controller.class}),
// @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})})
//@ComponentScan(basePackages = {"com.cym.componentscan"},
// includeFilters = {@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {UserService.class})}, useDefaultFilters = false)
@ComponentScan(basePackages = {"com.cym.componentscan"},
includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = MyFilterType.class)}, useDefaultFilters = false)
public class UserConfig {
}
4、配置 Bean 的作用域對象 在不指定 @Scope 的情況下,所有的 bean 都是單執行個體的 bean, 而且是餓漢加載 ( 容器啟動執行個體就建立 好了 )
@Configuration
public class BeanConfig {
@Bean
public User user(){
return new User();
}
}
@Setter
@Getter
public class User {
public User() {
System.out.println("在不指定@Scope的情況下,所有的bean都是單執行個體的bean,而且是餓漢加載(容器啟動執行個體就建立好了)");
}
private String userName;
private String age;
private String gender;
private String phone;
private String address;
private Date birthday;
}
擷取容器中的bean
指定 @Scope 為 prototype 表示為多執行個體的,而且還是懶漢模式加載( IOC 容器啟動的時候,并不會建立對象,而是 在第一次使用的時候才會建立)
@Configuration
public class BeanConfig {
@Bean
@Scope(value = "prototype")
public User user(){
return new User();
}
}
@Scope 指定的作用域方法取值
singleton | 單執行個體的 ( 預設 ) |
prototype | 多執行個體的 |
request | 同一次請求 |
session | 同一個會話級别 |
5、Bean的懶加載@Lazy(主要針對單執行個體的bean 容器啟動的時候,不建立對象,在第一次使用的時候才會建立該對象)
@Configuration
public class BeanConfig {
@Bean
@Lazy
public User user(){
return new User();
}
}
6、@Conditional進行條件判斷等(可以用于自動裝配)
場景,有二個元件Parents 和Childrents ,我的Childrents元件是依賴于Parents的元件
應用: 自己建立一個My Condition 的類 實作 Condition接口
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//判斷容器中是否有Parent的元件
if (conditionContext.getBeanFactory().containsBean("parents")){
return true;
}
return false;
}
}
配置類
@Configuration
public class BeanConfig {
@Bean
public Parents parents() {
return new Parents();
}
@Bean
@Conditional(value = MyCondition.class)
public Childrens childrens() {
return new Childrens();
}
}
7、往IOC 容器中添加元件的方式
通過 @CompentScan [email protected] @Service @Respository @compent,适用場景 : 針對我們自己寫的元件可以通過該方式來進行加載到容器中。 |
通過 @Bean 的方式來導入元件 ( 适用于導入第三方元件的類 ) |
通過 @Import 來導入元件 (導入元件的 id為全類名路徑),通過 @Import 的 ImportSeletor 類實作元件的導入 ( 導入元件的 id 為全類名路徑 ) |
通過實作 FacotryBean 接口來實作注冊 元件 |
通過@Import 的ImportSeletor類實作元件的導入 (導入元件的id為全類名路徑,如:com.cym.Import.compent.Parents)
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.cym.Import.compent.Dog"};
}
}
通過@Import的 ImportBeanDefinitionRegister導入元件 (可以指定bean的名稱)
public class MyBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
//建立一個bean定義對象
RootBeanDefinition parents = new RootBeanDefinition(Parents.class);
RootBeanDefinition childrens = new RootBeanDefinition(Childrens.class);
RootBeanDefinition dog = new RootBeanDefinition(Dog.class);
//把bean定義對象導入到容器中
registry.registerBeanDefinition("parents",parents);
registry.registerBeanDefinition("childrens",childrens);
registry.registerBeanDefinition("dog",dog);
}
}
配置類
@Configuration
//@Import({Parents.class,Childrens.class, MyImportSelector.class})
@Import(MyBeanDefinitionRegister.class)
public class BeanConfig {
}
通過實作 FacotryBean 接口來實作注冊 元件
public class MyrFactoryBean implements FactoryBean<User> {
//傳回bean對象
@Override
public User getObject() throws Exception {
return new User();
}
//傳回bean的類型
@Override
public Class<?> getObjectType() {
return User.class;
}
//是否為單例
@Override
public boolean isSingleton() {
return false;
}
配置類
@Configuration
public class BeanConfig {
//注冊我們自己factoryBean
@Bean
public MyrFactoryBean myrFactoryBean(){
return new MyrFactoryBean();
}
}
7、Bean的初始化方法和銷毀方法
- 什麼是bean的生命周期?bean的建立----->初始化----->銷毀方法由容器管理Bean的生命周期,我們可以通過自己指定bean的初始化方法和bean的銷毀方法。
public class User {
public User() {
System.out.println("我是構造器");
}
public void init(){
System.out.println("我是bean的生命周期的初始化方法init");
}
public void destroy(){
System.out.println("我是bean的生命周期的銷毀方法destroy");
}
}
@Configuration
public class BeanConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public User user() {
return new User();
}
}
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Object user1 = annotationConfigApplicationContext.getBean("user");
System.out.println(user1);
annotationConfigApplicationContext.close();
}
}
針對單執行個體bean的話,容器啟動的時候,bean的對象就建立了,而且容器銷毀的時候,也會調用Bean的銷毀方法。針對多執行個體bean的話,容器啟動的時候,bean是不會被建立的而是在擷取bean的時候被建立,而且bean的銷毀不受 IOC容器的管理
- 通過 InitializingBean和DisposableBean 的二個接口實作bean的初始化以及銷毀方法
/**
* @description:
* @author: 通過 InitializingBean和DisposableBean 的二個接口實作bean的初始化以及銷毀方法
* @date: 2020-04-12 12:27
*/
public class Person implements InitializingBean, DisposableBean {
public Person() {
System.out.println("我是Person構造器");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean的destroy()方法 ");
}
/**
* 內建了InitializingBean這個類的在bean建立完成後會自動調用該該方法
*
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的 afterPropertiesSet方法");
}
}
@Configuration
public class BeanConfig {
@Bean
public Person person(){
return new Person();
}
}
/**
* @description: 基于讀取配置類的形式定義Bean資訊
* @author:
* @date: 2020-04-11 19:45
*/
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Object user1 = annotationConfigApplicationContext.getBean("person");
System.out.println(user1);
annotationConfigApplicationContext.close();
}
}
- 通過JSR250規範 提供的注解@PostConstruct 和@ProDestory标注的方法
@Component
public class Dog {
public Dog() {
System.out.println("Dog構造方法");
}
@PostConstruct
public void init() {
System.out.println("Dog 的PostConstruct标志的方法");
}
@PreDestroy
public void destroy() {
System.out.println("Dog 的PreDestory标注的方法");
}
}
@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {
}
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Object dog = annotationConfigApplicationContext.getBean("dog");
System.out.println(dog);
annotationConfigApplicationContext.close();
}
}
- 通過Spring的BeanPostProcessor的 bean的後置處理器會攔截所有bean建立過程(postProcessBeforeInitialization在init方法之前調用 postProcessAfterInitialization 在 init 方法之後調用)
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessBeforeInitialization:"+beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessAfterInitialization:"+beanName);
return bean;
}
}
@Configuration
@ComponentScan(basePackages = {"com.cym.init_destroy"})
public class BeanConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public User user() {
return new User();
}
}
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
// Object dog = annotationConfigApplicationContext.getBean("cat");
// System.out.println(dog);
annotationConfigApplicationContext.close();
}
}
8、通過@Value [email protected]來給元件指派
@Setter
@Getter
public class User {
@Value("張三")//通過普通的方式
private String userName;
@Value("#{28-3}")//spel方式來指派
private String age;
@Value("${user.gender}")//通過讀取外部配置檔案的值
private String gender;
@Value("${user.phone}")//通過讀取外部配置檔案的值
private String phone;
@Value("${user.address}")//通過讀取外部配置檔案的值
private String address;
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age='" + age + '\'' +
", gender='" + gender + '\'' +
", phone='" + phone + '\'' +
", address='" + address + '\'' +
'}';
}
}
@Configuration
@PropertySource(value = {"classpath:user.properties"})//指定外部檔案的位置
public class BeanConfig {
@Bean
public User user(){
return new User();
}
}
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
Object user1 = annotationConfigApplicationContext.getBean("user");
System.out.println(user1);
}
}
9、自動裝配@AutoWired的使用
@Component
public class UserDao {
public void printUserName(String userName){
System.out.println("my name is " + userName);
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void printName(String userName){
userDao.printUserName(userName);
}
}
@Configuration
@ComponentScan(basePackages = {"com.cym.value"})
public class BeanConfig {
@Bean
public User user(){
return new User();
}
}
public class BeanMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);
UserService userService = (UserService) annotationConfigApplicationContext.getBean("userService");
userService.printName("張三");
}
}
- 自動裝配首先時按照類型進行裝配,若在IOC容器中發現了多個相同類型的元件,那麼就按照 屬性名稱來進行裝配比如,我容器中有二個UserDao類型的元件 一個叫UserDao 一個叫UserDao2那麼我們通過@AutoWired 來修飾的屬性名稱時UserDao,那麼拿就加載容器的UserDao元件,若屬性名稱為UserDao2 那麼他就加載的時UserDao2元件。
- 假設我們需要指定特定的元件來進行裝配,我們可以通過使用@Qualifier("UserDao")來指定裝配的元件或者在配置類上的@Bean加上@Primary注解
@Autowired
@Qualifier("userDao")
private UserDao userDao2;
- 假設我們容器中即沒有userDao 和userDao2,那麼在裝配的時候就會抛出異常若我們想不抛異常 ,我們需要指定 required為false的時候可以了
@Autowired(required = false) @Qualifier("userDao") private UsergDao userDao2;
10、@Resource(JSR250規範)
- 功能和@AutoWired的功能差不多一樣,但是不支援@Primary 和@Qualifier的支援
11、@InJect(JSR330規範)
- 需要導入jar包依賴功能和支援@Primary功能 ,但是沒有Require=false的功能
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
12、通過@Profile注解 來根據環境來激活辨別不同的Bean
- @Profile辨別在類上,那麼隻有目前環境比對,整個配置類才會生效,@Profile辨別在Bean上 ,那麼隻有目前環境的Bean才會被激活沒有标志為@Profile的bean 不管在什麼環境都可以被激活。
@Configuration
@PropertySource(value = {"classpath:ds.properties"})
public class MainConfig implements EmbeddedValueResolverAware {
@Value("${ds.username}")
private String userName;
@Value("${ds.password}")
private String password;
private String jdbcUrl;
private String classDriver;
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.jdbcUrl = resolver.resolveStringValue("${ds.jdbcUrl}");
this.classDriver = resolver.resolveStringValue("${ds.classDriver}");
}//辨別為測試環境才會被裝配
@Bean
@Profile(value = "test")
public DataSource testDs() {
return buliderDataSource(new DruidDataSource());
}
//辨別開發環境才會被激活
@Bean
@Profile(value = "dev")
public DataSource devDs() {
return buliderDataSource(new DruidDataSource());
}
//辨別生産環境才會被激活
@Bean
@Profile(value = "prod")
public DataSource prodDs() {
return buliderDataSource(new DruidDataSource());
}
private DataSource buliderDataSource(DruidDataSource dataSource) {
dataSource.setUserName(userName);
dataSource.setPassword(password);
dataSource.setClassDriver(classDriver);
dataSource.setJdbcUrl(jdbcUrl);
return dataSource;
}
}
激活切換環境的方法 方法一 : 通過運作時 jvm 參數來切換 -Dspring.profiles.active=test|dev|prod 方法二 : 通過代碼的方式來激活
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("test", "dev");
ctx.register(MainConfig.class);
ctx.refresh();
Object testDs = ctx.getBean("testDs");
System.out.println(testDs);
Object devDs = ctx.getBean("devDs");
System.out.println(devDs);
// String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
// for (String item : beanDefinitionNames){
// System.out.println(item);
// }
}
}