spring-ioc

Spring的IoC理解

IOC相关知识见博客

(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的(自己new对象),而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。

(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

spring Bean的三种配置方式

常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置类(基于类的Java Config)。

SpringBean的创建和注入有三种,XML、注解、java配置文件。

因为XML配置较为繁琐,现在大部分开始用注解和java配置。

一般什么时候用注解或者java配置呢?

基本原则是:全局配置、第三方类库用java配置类(如数据库配置,MVC,redis等相关配置),业务Bean的配置用注解(@Service @Component@Repository@Controlle)。

IOC 操作 : 什么是 Bean 管理

Bean 管理指的是两个操作
(1)Spring 创建对象
(2)Spirng 注入属性

一、传统的XML配置方式

1、基于 xml 方式创建对象

(1)在 spring 配置文件中,使用 bean 标签,标签里面添加对应属性,就可以实现对象创建
(2)在 bean 标签有很多属性,介绍常用的属性
* id 属性:唯一标识
* class 属性:类全路径(包类路径)
(3)创建对象时候,默认也是执行无参数构造方法完成对象创建

2、基于 xml 方式注入属性

(1)DI:依赖注入,就是注入属性

示例:

xml配置文件:applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean id="beanFactroy" class="com.stonegeek.service.impl.BeanFactroyImpl" />
</beans>
读取配置文件,获取bean
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestBean1 {
    @Test
public void test(){
   // 上下文加载配置文件
        ApplicationContext ctx= new ClassPathXmlApplicationContext("applicationContext.xml");
        BeanFactory beanFactory=(BeanFactory) ctx.getBean("beanFactroy");
        beanFactory.Beantest(); //----------------This is a 传统的XML配置的bean!-------------------
    }
}

二、基于java注解的配置(需要开启组件扫描)

1. 使用注解注入bean:

Spring 针对 Bean 管理中创建对象提供注解
(1)@Component 泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
自定义bean的id @Component(value=“cat”)
(2)@Service 用于标注业务层组件
(3)@Controller 用于标注控制层组件(如struts中的action)
(4)@Repository 用于标注数据访问组件,即DAO组件
上面四个注解功能是一样的,都可以用来创建 bean实例

如果一个类使用了@Service,那么此类将自动注册成一个bean,不需要再在applicationContext.xml文件定义bean了,类似的还包括@Component、@Repository、@Controller。

2. 使用注解获取bean:

@Resource 默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
@Autowired @Qualifier(“personDaoBean”) 存在多个实例配合使用

2. 开启组件扫描
开启组件扫描的2中方式:

(1)在applicationContext.xml文件中加一行,作用是自动扫描base-package包下的注解:<context:component-scan base-package=“com.stonegeek” />

(2)使用java配置类来开启包扫描(完全注解开发)
创建配置类,替代 xml 配置文件

@Configuration  // 作为配置类,替代 xml 配置文件 
@ComponentScan(basePackages = {"com.stonegeek"}) 
public class SpringConfig {
} 
测试:获取配置的bean:
package com.stonegeek;

import com.stonegeek.service.BeanFactory;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestBean2 {
    @Test
    public void test(){
        ApplicationContext ctx= new ClassPathXmlApplicationContext("applicationContext.xml");
        BeanFactory beanFactory=(BeanFactory) ctx.getBean("beanFactory");
        beanFactory.Beantest();  //This is a 基于java注解的bean!
    }
}

三、基于类的Java Config – @Configuration 和 @Bean 注解

通过java类定义spring配置元数据,且直接消除xml配置文件

使用java配置类也需要开启自动扫描功能

Spring3.0基于java的配置直接支持下面的注解:

扩展:Spring注解常用汇总

@Configuration 把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
  @Configuration标注在类上,相当于把该类作为spring的xml配置文件中的,作用为:配置spring容器(应用上下文)
  带有 @Configuration 的注解类表示这个类可以使用 Spring IoC 容器作为 bean 定义的来源。
  
@Bean
@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的,作用为:注册bean对象
方法名等于beanId
@Bean 注解告诉 Spring,一个带有 @Bean 的注解方法将返回一个对象,该对象应该被注册为在 Spring 应用程序上下文中的 bean。

@Scope注解 作用域
@DependsOn 定义Bean初始化及销毁时的顺序
@Primary 自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
@Lazy(true) 表示延迟初始化
@Import
@ImportResource
@Value
@PostConstruct用于指定初始化方法(用在方法上)
@PreDestory用于指定销毁方法(用在方法上)

使用示例
// @Configuration告知spring,该类下将被定义一个或多个Bean。
@Configuration //@Component
public class BeanConfig {

// @Bean在spring上下文中注册了一个Bean,方法名为bean的id。
// 因为spring默认单例模式,因此该方法一开始就只会被调用一次。
// 方法中所有的代码都是为了构建一个合法的Bean返回。因此依赖关系我们也必须在代码中手动构建BeanFactory 。	
@Bean
// 可以指定初始化和销毁方法
//@Bean(name="testNean",initMethod="start",destroyMethod="cleanUp")
// @Scope(value = "prototype")
    public BeanFactory beanFactory(){
        return new BeanFactoryImpl();
    }
}

上面代码相当于:
<beans>
   <bean id="beanFactory" class="com.stonegeek.service.BeanFactory" />
</beans>
获取bean
package com.stonegeek;

import com.stonegeek.service.BeanFactory;
import com.stonegeek.service.config.BeanConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TestBean3 {
    @Test
    public void test(){
        ApplicationContext applicationContext=new AnnotationConfigApplicationContext(BeanConfig.class);
        BeanFactory beanFactorys=applicationContext.getBean(BeanFactory.class);
        beanFactorys.Beantest();  //This is a 基于类的Java Config Bean!
    }
}

@Import注解:配置类之间的依赖

@Import({SpringConfig.class,OtherConfig.class})

多个配置类如何一次注入?即在引用上下中注册多个配置类?

无参构造,手动在引用上下中注册多个配置类,注意要调用refresh()方法

如果是手动进行配置类注册的话,那么配置类上不需要添加@Configuration注解。反之如果是扫包自动注册的形式就需要注解。

// @Configuration告知spring,该类下将被定义一个或多个Bean。
public class SpringConfig {
    @Bean
    public Dog dog(){
        Dog dog = new Dog();
        dog.setName("dog wangwang..");
        return dog;
    }
}
//@Configuration
public class OtherConfig {
    @Bean
    public Data data() {
        return new Data();
    }
}

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(SpringConfig.class);
context.register(OtherConfig.class);
context.refresh();

Person p1 = (Person)context.getBean("p");
System.out.println(p1.getName());
System.out.println(p1.getPet().getName());
Data data = (Data)context.getBean("data");
System.out.println(data.getUser());

有参构造,结合包扫描自动注册单个配置类

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(CommonService.class);

// 源码:
 public AnnotationConfigApplicationContext(Class... annotatedClasses) {
        this();
        this.register(annotatedClasses);
        this.refresh();
    }
    
// Java中可变参数方法
public void register(Class... annotatedClasses) {
        Class[] var2 = annotatedClasses;
        int var3 = annotatedClasses.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Class<?> annotatedClass = var2[var4];
            this.registerBean(annotatedClass);
        }
    }

自动注册多个配置类

spring3.0提供@Import注解可以让我们为配置文件之类建立一种依赖关系。

如下代码。

//@Configuration
@Import({SpringConfig.class,OtherConfig.class})
public class AppConfig {
}

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Person p1 = (Person)context.getBean("p");
 System.out.println(p1.getName());
 System.out.println(p1.getPet().getName());
 Data data = (Data)context.getBean("data");
 System.out.println(data.getUser());

ok,应用上下文现在只需要注册AppConfig就可以了。

java配置类可以和xml混合使用

1.spring上下文加载xml配置。
2.扫描xml配置的package。
3.注册@Bean到上下文中。

java配置类

java提供一种基于java代码的方式完全替代xml配置。

java中只有类,那我们把一个类映射成xml就可以了。这也符合面向对象的思想。

java配置可以和xml配置配合使用,也可以完全废弃xml。

看源码可知@Configuration是一个@Component。因此我们可以尝试配置用直接用@Conponent注解。

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.stereotype.Component;
 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    String value() default "";
}

ClassPathXmlApplicationContext与AnnotationConfigApplicationConetxt切换

spring容器启动到底用的是ClassPathXmlApplicationContext还是AnnotationConfigWebApplicationConetxt?

ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
XmlWebApplicationContext

Spring为ApplicationContext提供了很多实现类,其中有三个比较常用的:ClassPathXmlApplicationContext,FileSystemXmlApplicationContext,XmlWebApplicationContext。那么,我们项目启动的时候到底加载的那个类呢?答案是通过web.xml配置的。

java –为什么组件扫描没有拿到我的@Configuration? spring组件扫描配置到上一个包,而不是文件所在包

我有一个配置文件

package com.mypackage.referencedata.config;
@Configuration
@ComponentScan ("com.mypackage.referencedata.*")
public class ReferenceDataConfig {
}

如果我有配置xml

<context:component-scan base-package="com.mypackage.referencedata.config.*" />

它没有加载.

但是如果我使用

<context:component-scan base-package="com.mypackage.referencedata.*" />

则有用.
解答原因:
<context:component-scan base-package=“com.mypackage.referencedata.config.*” />

将扫描com.mypackage.referencedata.config中的包,因为它是包(包扫描,扫描的是包,不是java文件).

使用com.mypackage.referencedata.config会工作得很好.

Spring @Configuration 和 @Component 区别

一句话概括
@Configuration 中所有带 @Bean 注解的方法都会被动态代理,作用:每次调用该方法返回的都是同一个实例。
@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,每次调用返回不同的实例

从定义来看, @Configuration 注解本质上还是 @Component,因此 context:component-scan/或者 @ComponentScan 都能处理@Configuration 注解的类。

@Configuration 标记的类必须符合下面的要求(cglib 动态代理是通过生成子类的形式实现):

  1. 配置类必须以类的形式提供(不能是工厂方法返回的实例),允许通过生成子类在运行时增强(cglib 动态代理)。
  2. 配置类不能是 final 类(没法动态代理,final不能继承)。
  3. 配置注解通常为了通过 @Bean 注解生成 Spring 容器管理的类,
  4. 配置类必须是非本地的(即不能在方法中声明,不能是 private)。
  5. 任何嵌套配置类都必须声明为static。
  6. @Bean 方法可能不会反过来创建进一步的配置类(也就是返回的 bean 如果带有 @Configuration,也不会被特殊处理,只会作为普通的 bean)。
@Configuration
public class MyBeanConfig {
    @Bean
    public Country country(){
        return new Country();
    }
    @Bean
	public UserInfo userInfo(){
		// 这里直接调用 country() 方法返回的和上面 @Bean 方法返回的Country 是同一个实例
        return new UserInfo(country());
    }
}

这里说的是方法被代理了,是代理方法。。。而不是说返回的对象被代理了。

@Component 注意

@Component 注解并没有通过 cglib 来代理@Bean 方法的调用,因此像下面这样配置时,就是两个不同的 country。

@Component
public class MyBeanConfig {
    @Bean
    public Country country(){
        return new Country();
    }
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country());
    }
}

为了不被cglib动态代理的情况下,但是还想要使用同一个实例

有些特殊情况下,我们不希望 MyBeanConfig 被代理(代理后会变成WebMvcConfig E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB8bef3235293)时,就得用 @Component,这种情况下,上面的写法就需要改成下面这样:

@Component
public class MyBeanConfig {
    @Autowired
    private Country country;
    @Bean
    public Country country(){
        return new Country();
    }
    @Bean
    public UserInfo userInfo(){
        return new UserInfo(country);
    }
}

xml配置、JavaConfig配置、组件扫描的对比

在java项目中

(1):xml配置:在xml中通过bean标签来定义bean以及bean和bean之间的依赖关系,最后在启动程序时,用ClassPathXmlApplicationContext类,根据xml路径去加载spring容器。

(2):JavaConfig配置:在@Configuration标注的类中用@Bean定义bean以及bean和bean之间的依赖关系,最后在启动程序时,用AnnotationConfigApplicationContext类,根据类名来加载spring容器。

(3):组件扫描:在需要产生bean的类上加@Component等4个注解,在需要注入其他bean依赖的类中加上@Autowired注解;但是需要指定让spring去扫描哪些包,从而发现@Component和@Autowired注解;可以在xml配置里通过或者在JavaConfig中通过@ComponentScan来指定让spring扫描哪些包。最终在启动程序时,需要加载xml或者@Configuration标注的类来加载spring容器。(组件扫描相比xml配置和JavaConfig配置来说方便了,但是离不开它们,因为需要在它们中配置扫描路径)

在web项目中

(1)xml配置:在xml中通过bean标签来定义bean以及bean和bean之间的依赖关系,但是需要在web.xml中配置xml的路径,来让web项目去加载spring容器。

(2)JavaConfig配置:在@Configuration标注的类中用@Bean定义bean以及bean和bean之间的依赖关系,但是需要继承AbstractAnnotationConfigDispatcherServletInitializer类,并重写其方法来加载@Configuration标注的类,用以让web项目去加载spring容器。

(3)组件扫描:具体的扫描策略上面已经介绍了;第一种情况:需要在xml中配置扫描的包,最后在web.xml中配置xml的路径,从而让web去加载spring容器;第二种情况:需要在JavaConfig中通过@ComponentScan来指定让spring扫描哪些包,最后需要继承AbstractAnnotationConfigDispatcherServletInitializer类,并重写其方法来加载@Configuration标注的类,用以让web项目去加载spring容器。

三者的优缺点对比:

1:使用xml配置来产生bean和注入bean依赖时,如果需要产生的bean有很多,那么就需要在xml配置中写很多标签,这样相当繁琐。

2:使用JavaConfig配置来产生bean和注入bean依赖时,如果需要产生的bean有很多,那么就需要在@Configuration标注的类中写很多@Bean标注的方法,这样也相当繁琐。

3:如果使用组件扫描,产生bean和为bean注入依赖的写法就比较简单方便了;但是当想把引入的第三方类库也产生bean或者为其注入依赖时,就不能使用组件扫描了,因为无法给第三方类库中的类添加@Component和@Autowired;此时就需要使用xml配置或者JavaConfig配置来完成。

SpringBoot java配置类@Configuration 的两种写法

首先在Springboot项目中,建一个java类,使用注解@Configuration ,则这个类是SpringBoot bean的创建的配置文件类,这种配置文件类有两种写法

1.使用包扫描

创建bean @ComponentScan 被扫描的类不需要加@Component注解

2.使用函数创建bean @Bean

  1. 通过包扫描,将包下所有注解类,注入到spring容器中
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration //1使用配置注解 ,表示这个类是配置文件
@ComponentScan("com.wisely.highlight_spring4.ch1.di") //2使用扫描注解
public class DiConfig {
}

2.不使用扫描,注解。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //1表示配置文件 
public class JavaConfig {
  @Bean //2spring调用这个方法直接把FunctionService这个类实例加入到spring容器中
  public FunctionService functionService(){
    return new FunctionService();
  }
}

FunctionService也是没有使用注解

//1没有加Service注解
public class FunctionService {
  public String sayHello(String word){
       return "Hello " + word +" !";
}
}

以上两种方法在开发中常用的应该是第一种,使用注解可以大量减少代码量。

@Configuration注解的类的加载(注意不是处理@Configuration注解处理)实现原理

概述
· 这里主要分析@Configuration注解的类自身的加载的实现,即生成该类对应的BeanDefinition,注册到BeanFactory,此时该类就跟一个普通使用了如@Component注解的类一样,是在创建和初始化BeanFactory的过程中完成的。而@Configuration注解的处理是通过ConfigurationClassPostProcessor来完成的,ConfigurationClassPostProcessor是一个BeanFactoryPostProcessor接口实现类。

· 以下先从我们熟悉的@Configuration注解的处理实现说起,然后再回过头来分析在执行@Configuration的注解处理之前,@Configuration注解的类是怎么注册到BeanFactory中的。通俗来说,就以先分析怎么煎鸡蛋,再分析这个鸡蛋怎么被母鸡生出来的思路来分析。

BeanFactory和ApplicationContext有什么区别?

    BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

(1)ApplicationContext是BeanFactory的子接口

BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

①继承MessageSource,因此支持国际化。

②统一的资源文件访问方式。

③提供在监听器中注册bean的事件。

④同时加载多个配置文件。

⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

(2)加载bean的时间

①BeanFactroy采用的是延迟加载形式来注入Bean的
即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。
这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean , 确保当你需要的时候,你就不用等待,因为它们已经创建好了。

③占用内存空间
相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢

(3)创建方式

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

(4)注册方式 自动注册BeanPostProcessor

BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

BeanFactory和ApplicationContext的联系和区别

BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一个更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。

BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化。
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true,来让Bean延迟实例化

如果要创建ApplicationContext的实例,有两种方法:一种是使用代码的方式,另一种是使用声明的方式。

创建ApplicationContext实例的2种方式

如果要创建ApplicationContext的实例,有两种方法:一种是使用代码的方式,另一种是使用声明的方式

1. 使用代码的方式创建ApplicationContext实例
ApplicationContext ac = new ClassPathXmlApplicationContext("com/rk/spring/applicationContext.xml");
2. 在web.xml中,使用ContextLoader的声明方式创建ApplicationContext实例
	<!-- spring 配置 -->
 	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>/WEB-INF/classes/beans-*.xml</param-value>
	</context-param>
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

在web.xml文件中,使用ContextLoader的声明方式来对ApplicationContext进行实例化,有两种方式:ContextLoaderListener和ContextLoaderServlet。

重点介绍ContextLoaderListener,我们可以看到,它实现了ServletContextListener接口。

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
	//
}

请解释Spring Bean的生命周期?

1、生命周期

从对象创建到对象销毁的过程

2、bean 生命周期

(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4)bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

3、演示 bean 生命周期

public class Orders { 
	// 无参数构造     
	public Orders() {         
		System.out.println("第一步 执行无参数构造创建 bean 实例");     
	}     
	private String oname;  
   
	public void setOname(String oname) {         
		this.oname = oname;         
		System. out .println("第二步 调用 set 方法设置属性值");     
	}     
	// 创建执行的初始化的方法    
	public void initMethod() {        
		System. out .println("第三步 执行初始化的方法");     
	}     
	// 创建执行的销毁的方法     
	public void destroyMethod() {         
		System. out .println("第五步 执行销毁的方法");     
	} 
} 
 
<bean id="orders" class="com.atguigu.spring5.bean.Orders" initmethod="initMethod" destroy-method="destroyMethod">     
<property name="oname" value="手机"></property>
</bean> 
 
@Test     
public void testBean3() { 
	//  ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");         
	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");            
	Orders orders = context.getBean("orders", Orders.class);        
	System.out.println("第四步 获取创建 bean 实例对象");         
	System.out.println(orders);         
	// 手动让 bean 实例销毁         
	context.close();     
} 

4、bean 的后置处理器,bean 生命周期有七步

(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)把 bean 实例传递 bean 后置处理器的方法 postProcessBeforeInitialization 在初始化之前执行的方法
(4)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(5)把 bean 实例传递 bean 后置处理器的方法 postProcessAfterInitialization 在初始化之后执行的方法
(6)bean 可以使用了(对象获取到了)
(7)当容器关闭时,调用 bean 的销毁的方法(需要进行配置销毁的方法)

5、演示添加后置处理器效果

(1)创建类,实现接口 BeanPostProcessor,创建后置处理器

public class MyBeanPost implements BeanPostProcessor {     
	@Override     
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {         	
		System.out.println("在初始化之前执行的方法");         
		return bean;     
	}    
	 @Override    
	 public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {         
	 	System.out.println("在初始化之后执行的方法");         
		return bean;     
	} 
}

<! -- 配置后置处理器 -- > 
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>

Servlet的生命周期:

实例化,初始init,接收请求service,销毁destroy;

Spring上下文中的Bean生命周期也类似,如下:

(1)实例化Bean:

对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

(2)设置对象属性(依赖注入):

实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成依赖注入。

(3)处理Aware接口:
(aware 记忆技巧:a 表加强 + ware 注视 → 一直注视 → 意识到的)

接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给Bean:

①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

②如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。

③如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;

(4)BeanPostProcessor:

如果想对Bean进行一些自定义的处理,那么可以让Bean实现BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。

(5)初始化:InitializingBean 与 init-method:

InitializingBean接口方法先于init-method运行

如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
注意:通常InitializingBean接口的使用是能够避免的(而且不鼓励,因为没有必要把代码同Spring耦合起来)。Bean的定义支持指定一个普通的初始化方法。在使用XmlBeanFactory的情况下,可以通过指定init-method属性来完成

实现org.springframework.beans.factory.InitializingBean 接口允许一个bean在它的所有必须的属性被BeanFactory设置后,来执行初始化的工作。InitializingBean接口仅仅制定了一个方法:

void afterPropertiesSet() throws Exception;

(6)BeanPostProcessor:
如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。

(7)销毁方法:DisposableBean和destroy-method:

DisposableBean接口方法先于destroy-method指定方法运行
当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;

DisposableBean / destroy-method
实现org.springframework.beans.factory.DisposableBean接口允许一个bean,可以在包含它的BeanFactory销毁的时候得到一个回调。DisposableBean也只指定了一个方法:

void destroy() throws Exception;

注意:通常DisposableBean接口的使用能够避免的(而且是不鼓励的,因为它不必要地将代码耦合于Spring)。 Bean的定义支持指定一个普通的析构方法。在使用XmlBeanFactory使用的情况下,它是通过destroy-method属性完成。

(8)destroy-method:自定义的destroy方法

最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。

实现InitializingBean接口与在配置文件中指定init-method有什么不同?

import org.springframework.beans.factory.InitializingBean; 
public class TestInitializingBean implements InitializingBean{ 

    @Override 
    public void afterPropertiesSet() throws Exception { 
        System.out.println("ceshi InitializingBean");         
    } 
    
    public void testInit(){ 
        System.out.println("ceshi init-method");         
    } 
} 

配置文件如下: 
 <bean id="testInitializingBean" class="com.TestInitializingBean" init-method="testInit"></bean> 

运行Main程序,打印如下结果: 
ceshi InitializingBean 
ceshi init-method 
由结果可看出,在spring初始化bean的时候,如果该bean是实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法。 

总结:

1:spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现接口的afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用

2:实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率相对来说要高点。但是init-method方式消除了对spring的依赖

3:如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。

4:TransactionTemplate实现InitializingBean接口,主要是判断transactionManager是否已经初始化,如果没有则抛出异常。源码如下:

public void afterPropertiesSet() { 
        if (this.transactionManager == null) { 
            throw new IllegalArgumentException("Property 'transactionManager' is required"); 
        } 
}

Spring的后置处理器: BeanPostProcessor

BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。

InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。

BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。

BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。

深入理解spring之Aware接口的相关实现

需要用到spring底层的一些功能那么我们该怎么实现?

了解spring容器的IOC(依赖注入)的同学应该知道,我们的所有的bean对于spring容器是无意识的。

啥叫无意识呢,就是你完全可以把spring容器替换成其他的容器,而不需要改变你的代码,并且bean之间也是没有耦合的。

既然这样,那问题就来,假如现在我们需要对spring有意识,换句话说就是现在我们的业务可能需要用到spring底层的一些功能那么我们该怎么实现?这个其实优秀的spring框架早就帮我们想到了,那就是spring提供了一系列的xxxAware接口供我们自己来实现使用

spring给我们提供了多达12种Aware接口,主要演示一下BeanNameAware, ResourceLoaderAware, ApplicationContextAware, BeanFactoryAware的使用

【SpringBoot笔记12】实现xxxAware接口

1 Aware接口

Aware 的本意是感知。当bean实现了对应的Aware接口,BeanFactory 就会在产生这个bean的时候根据对应的Aware接口,给这个bean注入相应的属性,这样bean就能够获取外界资源的引用了。

1.1 ApplicationContextAware 和 BeanNameAware

1.1.1 ApplicationContextAware接口
当一个bean实现了org.springframework.context.ApplicationContextAware接口,那么这个实力bean就会被注入ApplicationContext对象,下面是这个接口的定义:

public interface ApplicationContextAware {
    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

使用它的一个例子:

@Component
public class BeanA implements ApplicationContextAware {
    
    private ApplicationContext ctx;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
    
    // ...
}

从Spring 2.5开始,可以通过@Autowired自动注入ApplicationContext对象:

@Component
public class BeanA {
    
    @Autowired
    private ApplicationContext applicationContext;
    
    // ...
}

1.1.2 BeanNameAware接口
在bean 的内部,它是不知道容器给自己取的id是什么。当一个bean实现了org.springframework.beans.factory.BeanNameAware接口,就可以在创建这个bean的时候将id注入进来。下面是该接口的定义:

public interface BeanNameAware {
    void setBeanName(String name) throws BeansException;
}

这些回调是在属性填充完毕之后,在初始化回调之前调用。

使用测试

定义一个继承了BeanNameAware, ResourceLoaderAware, ApplicationContextAware, BeanFactoryAware 的bean MyAware

public class MyAware implements BeanNameAware,ResourceLoaderAware,ApplicationContextAware,BeanFactoryAware{
    private String beanName;
    private ResourceLoader loader;
    private ApplicationContext applicationContext;
    private BeanFactory beanFactory;
    
    // BeanNameAware中的方法
    public void setBeanName(String name) {
        this.beanName = name;
    }
    // BeanFactoryAware中的方法
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    // ApplicationContextAware中的方法
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }
    // ResourceLoaderAware中的方法
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.loader = resourceLoader;
    }
    
    public void printResult() throws IOException {
        System.out.println("BeanNameAware Bean的名称是:" + beanName);
        Resource resource = loader.getResource("classpath:test.txt");
        System.out.println("----------------------------------------" );
        System.out.print("ResourceLoaderAware ResourceLoader加载的内容是:");
        String line = null;
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                resource.getInputStream()));
        while ((line = reader.readLine()) != null) {
            System.out.println(line);
        }
        reader.close();
        Environment environment = applicationContext.getEnvironment();
        System.out.println("----------------------------------------" );
        System.out.println("ApplicationContextAware 系统是:" + environment.getProperty("os.name"));
        boolean flow = beanFactory.isSingleton(beanName);
        System.out.println("----------------------------------------" );
        System.out.println("BeanFactoryAware bean【" + beanName+" 】是单例嘛:"+flow);
    }
}

测试方法如下:

	AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(MainScanConfig.class);
	MyAware myAware = (MyAware) applicationContext2.getBean("myAware");
	myAware.printResult();

运行结果如下:

BeanNameAware Bean的名称是:myAware
----------------------------------------
ResourceLoaderAware ResourceLoader加载的内容是:i'm test data
----------------------------------------
ApplicationContextAware 系统是:Windows 7
----------------------------------------
BeanFactoryAware bean【myAware 】是单例嘛:true

总结xxxAware接口

  1. Spring提供的这些Aware接口,就是根据需要依赖的属性来命名的(命名规则)

  2. 都是Aware接口的子接口,即都继承了Aware接口

  3. 父接口Aware中没有定义任何方法

  4. 接口内均定义了一个set方法,set参数就是我们需要获取的对象

Spring bean单例、多例模式与生命周期

单例模式

每个bean定义只生成一个对象实例, 每次getBean请求获得的都是此实例
单例默认饿汉模式:启动即加载

单例模式分为饿汉模式和懒汉模式

饿汉模式:

spring singleton的缺省是饿汉模式: 启动容器时(即实例化容器时),为所有spring配置文件中定义的bean都生成一个实例

懒汉模式:

在第一个请求时才生成一个实例,以后的请求都调用这个实例

在初始化容器时,helloService的bean对象实例就已经被创建了,后面的两次getBean都无法创建新的实例,而是直接使用这个实例,所以返回了true

以上,即可证明饿汉模式:启动容器时(即实例化容器时),为所有spring配置文件中定义的bean都生成一个实例,每次getBean请求获得的都是此实例

单例如何使用懒汉模式:beans 标签的default-lazy-init属性

修改application.xml,添加default-lazy-init属性

<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true" 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 class="top.bigking.bean.HelloService" id="helloService" scope="singleton" />
</beans>

很明显可以看到,在容器初始化结束后,helloService实例才随着getBean被创建了出来,这样就证明了懒汉模式:在第一个请求时才生成一个实例,以后的请求都调用这个实例

多例模式:scope=“prototype” 只有懒汉

任何一个实例都是新的实例,调用getBean时,就new一个新实例
应该注意的是:多例模式中,实例只会随着getBean而创建,不会随着容器初始化而创建!也就是说,多例模式只有懒汉!

默认情况下scope=“singleton”,那么该Bean是单例;若写成scope=“prototype”,则为多例,任何一个实例都是新的实例

<bean class="top.bigking.bean.HelloService" id="helloService" scope="prototype" /></beans>

有人可能会问,为什么控制台中,没有打印出 对象被实例化的相关信息呢?
这是因为:多例模式的对象,不归IOC容器管理!

bean生命周期

对于普通的Java对象,当new的时候创建对象,当它没有任何引用的时候被垃圾回收机制回收。而由Spring IoC容器托管的对象,它们的生命周期完全由容器控制。Spring中每个Bean的生命周期如下:
在这里插入图片描述

Spring生命周期注解之@PostConstruct,@PreDestroy 实现初始化和销毁bean之前进行的操作

在spring 容器初始化 bean 和销毁前所做的操作定义方式有三种:

Spring @PostConstruct和@PreDestroy实例

第一种:通过注解:@PostConstruct 和 @PreDestroy 方法 实现初始化和销毁bean之前进行的操作
注:@PostConstruct和@PreDestroy 标注不属于 Spring,它是在J2EE库- common-annotations.jar。

第二种是:通过 在xml中定义init-method 和 destory-method方法; 或者在@Bean注解中指定

@Bean(name="testNean",initMethod="start",destroyMethod="cleanUp")

第三种是:通过bean实现InitializingBean和 DisposableBean接口

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
 
public class PersonService {
  
	private String  message;
 
	public String getMessage() {
		return message;
	}
 
	public void setMessage(String message) {
		this.message = message;
	}
	
	@PostConstruct
	public void  init(){
		System.out.println("I'm  init  method  using  @PostConstrut...."+message);
	}
	
	@PreDestroy
	public void  dostory(){
		System.out.println("I'm  destory method  using  @PreDestroy....."+message);
	}
	
}

解释Spring支持的几种bean的作用域: 5种

Spring容器中的bean可以分为5个范围:

(1)singleton:单例
默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。

(2)prototype:多例
为每一个bean请求提供一个实例。

(3)request:请求
为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)session:
与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。

(5)global-session:全局作用域
global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

工厂 bean : IOC 操作 Bean 管理(FactoryBean)

1、Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)

2、普通 bean:在配置文件中定义 bean 类型就是返回类型

3、工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样

示例

第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean

第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型

public class MyBean implements FactoryBean<Course> {     
	// 定义返回 bean     
	@Override     
	public Course getObject() throws Exception {         
		Course course = new Course();         
		course.setCname("abc");         
		return course;     
	}     

	@Override     
	public Class<?> getObjectType() {        
	 	return null;     
	}     

	@Override     
	public boolean isSingleton() {         
		return false;     
	}
} 
 
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"> </bean> 
 
@Test 
public void test3() {     
	ApplicationContext context =  new ClassPathXmlApplicationContext("bean3.xml");     
	Course course = context.getBean("myBean", Course.class);     
	System.out.println(course); 
} 

Spring框架中的单例Beans是线程安全的么?

Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”。

Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

同步机制

采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。

ThreadLocal

采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

Spring中依赖注入有三种注入方式

Spring注解依赖注入的三种方式的优缺点以及优先选择

当我们在使用依赖注入的时候,通常有三种方式:
1.构造器注入:通过构造器来注入(xml或注解);
2.设值注入(setter方式注入):通过setter方法来注入(xml或注解);
3.Feild方式注入(注解方式注入):通过filed变量来注入(注解,xml里和setter同一个);
使用注解注入属性时,可以去掉set方法

总结:

1.强制性的依赖性或者当目标不可变时,使用构造函数注入(应该说尽量都使用构造器来注入)

2.可选或多变的依赖使用setter注入(建议可以使用构造器结合setter的方式来注入)

3.在大多数的情况下避免field域注入(感觉大多数同学可能会有异议,毕竟这个方式写起来非常简便,但是它的弊端确实远大于这些优点)

4.Spring 4.3+ 的同学可以试一试构造器的隐式注入,采用此方式注入后,使得我们的代码更优雅,更独立,减少了对Spring的依赖性。

Spring学习(十八)Bean 的三种依赖注入方式介绍 详细版

Spring基于xml注入bean的几种方式(IOC)

(1)Set方法注入;property标签
无参构造创建对象,然后通过属性的set方法设置属性值

设值注入采用的是标签元素,其中的name属性对应的是要注入的变量名,type属性值对应的该变量的类型,可以是自定义类或者包装类型。value属性对应的是相应的值,还有一个ref属性,该属性值对应的是bean。

<bean id="personDao" class="com.fredia.service.impl.PersonDaoBean">  
	<property name="springDao" ref="springDao"></property>
    <property name="id" type="java.lang.Integer" value="1"/>  
    <property name="list" type="java.util.List">  
        <list>  
            <value>list1</value>  
            <value>list2</value>  
            <value>list3</value>  
        </list>  
    </property>  
    <property name="map" type="java.util.Map">  
        <map>  
            <entry key="key1" value="value1"></entry>  
            <entry key="key2" value="value2"></entry>  
        </map>  
    </property>  
</bean>  

(2)构造器注入:有参构造,constructor-arg
解决构造方法参数的不确定性:你可能会遇到构造方法传入的两参数都是同类型的,为了分清哪个该赋对应值,则需要进行一些小处理:
①通过index设置参数的位置;
②通过type设置参数类型;

<bean id="personService" class="com.fredia.service.impl.PersonServiceBean">  
    <constructor-arg index="0" type="cn.glzaction.service.impl.PersonDaoBean" ref="personDao"/>  
    <constructor-arg index="1" type="java.lang.String" value="glzaction"/>  
    <constructor-arg index="2" type="java.util.List">  
        <list>  
            <value>list1</value>  
            <value>list2</value>  
            <value>list3</value>  
        </list>  
    </constructor-arg>  
</bean>  

(3)静态工厂注入:factory-method属性
调用静态工厂的方法来获取自己需要的对象,但为了让Spring管理所有对象,我们不能直接通过”工程类.静态方法()”来获取对象,而是依然通过spring注入的形式获取
依然需要提供属性的set方法,只是获取bean的方式不一样

public class DaoFactory { 
	//静态工厂 
	public static final FactoryDao getStaticFactoryDaoImpl(){ 
	    return new StaticFacotryDaoImpl(); 
	} 
}

public class SpringAction { 
	//注入对象 
	private FactoryDao staticFactoryDao; 
	 
	public void staticFactoryOk(){ 
	    staticFactoryDao.saveFactory(); 
	} 
	//注入对象的set方法 
	public void setStaticFactoryDao(FactoryDao staticFactoryDao) { 
	    this.staticFactoryDao = staticFactoryDao; 
	} 
} 

<!--配置bean,配置后该类由spring管理--> 
<bean name="springAction" class="com.bless.springdemo.action.SpringAction" > 
<!--(3)使用静态工厂的方法注入对象,对应下面的配置文件(3)--> 
<property name="staticFactoryDao" ref="staticFactoryDao"></property> 
</property> 
</bean> 
<!--(3)此处获取对象的方式是从工厂类中获取静态方法--> 
<!--注意看指向的class并不是FactoryDao的实现类,而是指向静态工厂DaoFactory,并且配置 factory-method=”getStaticFactoryDaoImpl”指定调用哪个工厂方法--> 
<bean name="staticFactoryDao" class="com.bless.springdemo.factory.DaoFactory" factory-method="getStaticFactoryDaoImpl"></bean> 

(4)实例工厂:
实例工厂的意思是获取对象实例的方法不是静态的,所以你需要首先new工厂类,再调用普通的实例方法:

public class DaoFactory { 
	//实例工厂 
	public FactoryDao getFactoryDaoImpl(){ 
	    return new FactoryDaoImpl(); 
	} 
}

<!--配置bean,配置后该类由spring管理--> 
<bean name="springAction" class="com.bless.springdemo.action.SpringAction"> 
<!--(4)使用实例工厂的方法注入对象,对应下面的配置文件(4)--> 
<property name="factoryDao" ref="factoryDao"></property> 
</bean> 
 
<!--(4)此处获取对象的方式是从工厂类中获取实例方法--> 
<bean name="daoFactory" class="com.bless.springdemo.factory.DaoFactory"></bean> 
<bean name="factoryDao" factory-bean="daoFactory" factory-method="getFactoryDaoImpl"></bean> 

5.总结:

Spring IOC注入方式用得最多的是(1)(2)种,多写多练就会非常熟练。

另外注意:通过Spring创建的对象默认是单例的,如果需要创建多实例对象可以在标签后面添加一个属性:

<bean name="..." class="..." scope="prototype">

相同的参数类型列表,构造函数只能有一个 参数名称不同也不行(参数列表对应的是参数类型,与参数名称无关)

在这里插入图片描述
Java中的方法签名
方法签名就由方法名+形参列表构成,也就是说,方法名和形参数据类型列表可以唯一的确定一个方法,与方法的返回值一点关系都没有,这是判断重载重要依据

NoSuchMethodException异常

知识点四:依赖注入-使用Field注入(用于注解方式)

注入依赖对象可以采用手工装配和自动装配,在实际应用中建议使用手工装配,因为自动装配会产生未知情况,开发人员无法预见装配结果

手工装配依赖对象,有两种编程方式:基于xml和注解方式

1. 基于xml注入

在xml配置文件中,通过在bean节点下配置,如:

<bean id="personDao" class="cn.itcast.service.PersonDaoBean">
<constructor-arg index="0" type="java.lang.String"
value="xxx"/>//使用构造器注入
<property name="name" value="探索"/>//使用setter方法注入
</bean>

2. 基于注解注入

在java代码里使用@Resource和@AutoWired的注解方式进行装配。但我们需要在xml文件里配置如下信息

<beans xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
</bean>

其中<context:annotation-config/> 这个配置隐式注册了多个对注释进行解析处理的处理器
比如:
对@AuotWired :AutowireAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor

@Resource注解 在spring的安装目录lib/j2ee/comm-annotation.jar

@Autowired或者@Resource注解方式进行装配,这两个注解的区别是:

@Autowired默认按照类型装配(这个是spring提供的),@Resource默认按照名称装配(这个是jdk1.6提供的),当找不到名称匹配的bean才会按照类型装配。通常建议使用@Resource, 因为这样不会与spring框架耦合

@Resource和@Autowired注解,可以标注在属性上、构造函数上、字段或属性的setter方法上(3种注入方式)

@Autowired

默认是按照类型查找要注入的对象的。要改成按照名称查找注入可以结合@Qualifier:

	@Autowired(required=false )  // 不检查依赖对象是否存在,默认必须存在
	@Qualifier("personDao") //按照指定名字注入
	private PersonDao personDao;

required=true :表示如果在配置文件中找不到指定的要注入的值,会抛出异常
required=false :表示如果在配置文件中找不到指定的要注入的值,赋一个null
@Resource

默认按名称注入,如果找不到,则回退到按照类型装配,但一旦指定了name属性,就只能按照名称装配了。

名称可以通过@Resource的name属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称做为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名称做为bean名称寻找依赖对象。

@Resource(name=”personDaoBean”)
Private PersonDao personDao;//用于字段上

Spring的自动装配:

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配

Spring中自动装配的4种方式
Spring 自动装配

简单理解

自动装配,就是将一个Bean注入到其他Bean的Property中。Spring框架式默认不支持自动装配的,要想使用自动装配需要修改spring配置文件中标签的autowire属性,或者设置beans标签的default-autowire属性为byName,byType等,来设置所有bean都进行自动装配。

(1)no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。

(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
该模式表示根据Property的Name自动装配,如果一个bean的name,和另一个bean中的Property的name相同,则自动装配这个bean到Property中。当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。

<bean id="cat" class="com.spring.auto.autowire.Cat"></bean>
    <bean id="dog" class="com.spring.auto.autowire.Dog"></bean>
    <bean id="test" class="com.spring.auto.autowire.Person" autowire="byName">
        <property name="say" value="测试"/>
    </bean>

(3)byType:通过参数的数据类型进行自动装配。
该模式表示根据Property的数据类型(Type)自动装配,Spring会总动寻找与属性类型相同的bean,若一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。

注意:使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常

(4)constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
通过构造器自动注入。在定义Bean时,在bean标签中,设置autowire属性为constructor,那么,Spring会寻找与该Bean的构造函数各个参数类型相匹配的Bean,通过构造函数注入进来。

<bean id="cat" class="com.spring.auto.autowire.Cat"></bean>
    <bean id="dog" class="com.spring.auto.autowire.Dog"></bean>
    <bean id="test" class="com.spring.auto.autowire.Person" autowire="constructor">
        <constructor-arg index="2" value="55"></constructor-arg>
    </bean>

(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。
自动装配。如果想进行自动装配,但不知道使用哪种类型的自动装配,那么就可以使用autodetect,让容器自己决定。这是通过在定义Bean时,设置bean标签的autowire属性为autodetect来实现的。设置为autodetect时,Spring容器会首先尝试构造器注入,然后尝试按类型注入。

基于注解的方式:

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。

解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

注:@Autowired和@Resource之间的区别

(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。

(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。

注解@Inject 和@Named的例子

@Inject 注解初识

@Inject-限定器@Named

@Inject和@Named注解来源

import javax.inject.Inject;
import javax.inject.Named;

两个注解是JSR-330的一部分。在Spring 3中,开始支持JSR-330的注解。

这些注解在使用上和Spring的注解一样,所不同的只是需要额外的相关jar包。你可以使用下面的注解在spring 3应用中。

  • @Inject替代@Autowired来执行注入
  • @Named替代@Component来声明一个Bean
  • 注意@Inject是没有required属性的

用@Inject 注解构造方法

在构造方法上使用 @Inject 时,其参数在运行时由配置好的IoC容器提供。比如,在下面的代码中,运行时调用MurmurMessage类的构造方法时,IoC 容器会注入其参数 Header 和Content 对象。

@Inject
public MurmurMessage(Header header, Content content)
{
    this.headr = header;
    this.content = content;
}

规范中规定向构造方法注入的参数数量是0个或多个,所以在不含参数的构造方法上使用 @Inject 注解也是合法的。(注意:因为JRE无法决定构造方法注入的优先级,所以规范中规定类中只能有一个构造方法带@Inject注解

用@Inject注解方法

与构造方法一样,运行时可注入的参数数量为0个或多个。但使用参数注入的方法不能声明为抽象方法也不能声明其自身的类型参数。下面这段代码在set方法前使用@Inject,这是注入可选属性的常用技术。

@Named("myCake")
public class Cake {
	private String name = "";
}

 
public class Chief {
	private Cake cake = null;
	@Inject
	@Named("myCake") // 标注注入myCake这个类型
	public Chief(Cake cake) {
		this.cake = cake;
	}
}

@Inject和@Autowired以及@Resource区别

  • @Autowired和@Inject基本是一样的,因为两者都是使用AutowiredAnnotationBeanPostProcessor来处理依赖注入。

  • 但是@Resource是个例外,它使用的是CommonAnnotationBeanPostProcessor来处理依赖注入。

  • 当然,两者都是BeanPostProcessor。

@Autowired和@Inject默认 autowired by type
可以 通过@Qualifier 显式指定 autowired by qualifier name。

@Resource

默认 autowired by field name
如果 autowired by field name失败,会退化为 autowired by type

总结

个人在使用上,更偏重使用@Inject,这是jsr330规范的实现,而@Autowired是spring的实现,如果不用spring一般用不上这个,而@Resource则是jsr250的实现,这是多年前的规范。

spring自动装配

@Profile:

Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能;

/**
 * @Profile:指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件
 * 1)、加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中。默认是default环境
 * 2)、写在配置类上,只有是指定的环境的时候,整个配置类里面的所有配置才能开始生效
 * 3)、没有标注环境标识的bean在,任何环境下都是加载的;
 */
 
@PropertySource("classpath:/dbconfig.properties")
@Configuration
public class MainConfigOfProfile implements EmbeddedValueResolverAware{
     
    @Value("${db.user}")
    private String user;
     
    @Profile("test")
    @Bean("testDataSource")
    public DataSource dataSourceTest(@Value("${db.password}")String pwd) throws Exception{
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
     
    @Profile("dev")
    @Bean("devDataSource")
    public DataSource dataSourceDev(@Value("${db.password}")String pwd) throws Exception{
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setUser(user);
        dataSource.setPassword(pwd);
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_crud");
        dataSource.setDriverClass(driverClass);
        return dataSource;
    }
     
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
        // TODO Auto-generated method stub
        this.valueResolver = resolver;
        driverClass = valueResolver.resolveStringValue("${db.driverClass}");
    }
} 
修改运行环境,测试

1、使用命令行动态参数: 在虚拟机参数位置加载 -Dspring.profiles.active=test
2、代码的方式激活某种环境;

@Test
public void test01(){
    AnnotationConfigApplicationContext applicationContext =
            new AnnotationConfigApplicationContext();
    //1、创建一个applicationContext
    //2、设置需要激活的环境
    applicationContext.getEnvironment().setActiveProfiles("dev");
    //3、注册主配置类
    applicationContext.register(MainConfigOfProfile.class);
    //4、启动刷新容器
    applicationContext.refresh();
   }

Spring @Value("#{}")和@Value("${}")

一.@Value("#{}")
其实是SpEL表达式的值,可以表示常量的值,或者获取bean中的属性

@RestController
@RequestMapping("/login")
@Component
public class LoginController {
	
	@Value("#{1}")
	private int number; //获取数字 1
	
	@Value("#{'Spring Expression Language'}") //获取字符串常量
	private String str;
	
	@Value("#{dataSource.url}") //获取bean的属性
	private String jdbcUrl;
	
	@Autowired
	private DataSourceTransactionManager transactionManager;
 
	@RequestMapping("login")
	public String login(String name,String password) throws FileNotFoundException{
		System.out.println(number);
		System.out.println(str);
		System.out.println(jdbcUrl);
		return "login";
	}
}

二.@Value("${}")
用于获取配置文件中的属性值,通常用于获取写在application.properties中的内容,例如在配置文件中:

jdbc.driverClass=com.mysql
jdbc.url=3306@local
jdbc.user=admin
则在类中可以通过`@Value(""${jdbc.url})`来获取相应的值

SpringEl表达式,以及Property文件属性的注解注入到bean中

#{}是SrpingEl表达式的语法规则.

        例如:#{bean.属性?:默认值},注意bean.属性必须是要存在的,当为null时匹配

${}是Spring占位符的语法规则,请注意它是否能用,跟bean的初始化时间有关.

        例如:${属性:默认值},如果属性为null或者不存在的话,就是用默认值填充

Spring3系列6-Spring 表达式语言(Spring EL)

Spring Expression Language —— 即Spring3中功能丰富强大的表达式语言,简称SpEL。SpEL是类似于OGNL和JSF EL的表达式语言,能够在运行时构建复杂表达式,存取对象属性、对象方法调用等。

所有的SpEL都支持XML和Annotation两种方式,格式:#{ SpEL expression }

Spring EL——XML

#{}获取的是bean,而不是配置文件中的属性(用${})

<beans>
    <bean id="itemBean" class="com.lei.demo.el.Item">
        <property name="name" value="itemA" />
        <property name="total" value="10" />
    </bean>
 
    <bean id="customerBean" class="com.lei.demo.el.Customer">
        <property name="item" value="#{itemBean}" />
        <property name="itemName" value="#{itemBean.name}" />
    </bean>
</beans>

注解:

  1. #{itemBean}——将itemBean注入到customerBean的item属性中。

  2. #{itemBean.name}——将itemBean 的name属性,注入到customerBean的属性itemName中。

Spring EL——Annotation

SpEL的Annotation版本。

注意:要在Annotation中使用SpEL,必须要通过annotation注册组件。如果你在xml中注册了bean和在java class中定义了@Value,@Value在运行时将失败。

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("itemBean")
public class Item {

    @Value("itemA")//直接注入String
    private String name;
    
    @Value("10")//直接注入integer
    private int total;
    
    //getter and setter...
}

Spring EL Method Invocation——SpEL 方法调用

@Value("#{'lei'.toUpperCase()}")
private String name;
@Value("#{priceBean.getSpecialPrice()}")
private double amount;

Spring EL 三目操作符condition?true:false

	@Value("#{itemBean.qtyOnHand < 100 ? true : false}")
	private boolean warning;
	
Xml配置如下,注意:应该用“&lt;”代替小于号“<
 <bean id="customerBean" class="com.lei.demo.el.Customer">
        <property name="warning" 
                          value="#{itemBean.qtyOnHand &lt; 100 ? true : false}" />
    </bean>

Spring 自动装配及其注解

属性自动装配
@Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配,其中@Qualifier不能单独使用。

类自动装配
包扫描

注解替换bean节点
    @Controller注解       只能用控制器类上
    @Service注解           只能用在业务类上
    @Repository注解      只能用在dao类上
    @Component注解     无法按照上面三个注解分类,就用此注解
   <!-- 解析注解
     @Controller
     @Service
     @Repository
     @Component
     component-scan:组件扫描
     base-package:基本包,指定包名,多个包名可以用逗号间隔
     也可以写多个<context:component-scan
     如果用了组件扫描后<context:annotation-config></context:annotation-config>就不用写了,组件扫描已经包含了 <context:annotation-config>
-->
<context:component-scan base-package="com.hdu.autowire"/>   

spring配置注解context:annotation-config和context:component-scan区别

1.context:annotation-config

< context:annotation-config> 是用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册

AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor

这四个Processor,注册这4个BeanPostProcessor的作用,就是为了你的系统能够识别相应的注解。BeanPostProcessor就是处理注解的处理器。

比如我们要使用@Autowired注解,那么就必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。传统声明方式如下

<bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor "/>

一般来说,像@ Resource 、@ PostConstruct、@Antowired这些注解在自动注入还是比较常用,所以如果总是需要按照传统的方式一条一条配置显得有些繁琐和没有必要,于是spring给我们提供< context:annotation-config/>的简化配置方式,自动帮你完成声明。

思考1:假如我们要使用如@Component、@Controller、@Service等这些注解,使用能否激活这些注解呢?

答案:单纯使用< context:annotation-config/>对上面这些注解无效,不能激活!

2.context:component-scan

Spring 给我提供了context:component-scan配置,如下

<context:component-scan base-package=”XX.XX”/> 

该配置项其实也包含了自动注入上述 四个processor 的功能,因此当使用 < context:component-scan/> 后,就可以将 < context:annotation-config/> 移除了。

通过对base-package配置,就可以把controller包下 service包下 dao包下的注解全部扫描到了!

3.总结

(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。

(2)< context:component-scan base-package=“XX.XX”/> :除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。

思考2:如果同时使用这两个配置会不会出现重复注入的情况呢?

答案:因为< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!

彩蛋:< mvc:annotation-driven/>

< mvc:annotation-driven/>从 标签的shecma就能看出来,mvc,主要就是为了Spring MVC来用的,提供Controller请求转发,json自动转换等功能。相比上面的两个shecma是context开头,那么主要是解决spring容器的一些注解。

< mvc:annotation-driven /> 是一种简写形式,完全可以手动配置替代这种简写形式,简写形式可以让初学都快速应用默认配置方案。 会自动注册DefaultAnnotationHandlerMapping与AnnotationMethodHandlerAdapter 两个bean,是spring MVC为@Controllers分发请求所必须的。

并提供了:数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB),读写JSON的支持(Jackson)。

在实际开发使用SpringMVC开启这个配置,否则会出现一些功能不能正常使用!

学习笔记:Spring中default-autowire与autowire区别

配置统一的自动装配方式:beans标签设置default-autowire参数

在spring的配置文件中可以参照如下设置default-autowire参数

<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"
       default-autowire="byName">

为bean配置自己的自动装配方式

<bean id="student" class="com.haiwi.spring.Student" autowire="byName">
             <property name="stu_name" value="Tom" />
</bean>

xsi:schemaLocation有何作用

总结:

  • xsi:schemaLocation 即像java中的import命令 其中值为一对一对的 即 xxxx xxxxxx.dtd 即相当于引入类 前者为名称 后者为来源

  • xmlns:xsi 该文件语法说明 使用了XMLSchema-instance

  • xmlns: 即为别名

  • 使用了xmlns:xsi 才能识别xsi:schemaLocation和xmlns

  • 在xsi:schemaLocation引入了beans,xmlns创建beans后 才允许在下方使用标签 否则会报unknowTag
      
    相信很多人和我一样,在编写Spring或者Maven或者其他需要用到XML文档的程序时,通常都是将这些XML文档头拷贝过来,并没有理解其中元素(比如xmlns,xmlns:xsi,xsi:schemaLocation)的真正含义,不知道哪些元素是多余的,也不知道为什么要加那些元素。这样当有时候网上Copy的XML头有错的时候自己却不知道怎么下手。

xmlns

xmlns="http://www.springframework.org/schema/beans"

xmlns其实是XML Namespace的缩写,可译为“XML命名空间”

为什么需要xmlns?

假如使用两个 XML 文档,但两个文档都包含带有不同内容和定义的

元素,就会发生命名冲突。XML 解析器是无法确定如何处理这类冲突。为了解决上述问题,xmlns就产生了。

xmlns:xsi

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  • 这样就为前缀赋予了一个与某个命名空间相关联的限定名称。

  • abc:xxx/

  • <context:component-scan >

  • context:annotation-config

xmlns和xmlns:xsi有什么不同?

  • xmlns表示默认的Namespace。
  • xmlns:xsi 表示使用xsi作为前缀的Namespace,当然前缀xsi需要在文档中声明。

xsi:schemaLocation

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
xsi:schemaLocation有何作用?

xsi:schemaLocation(当然一般都使用这个前缀)。它定义了XML Namespace和对应的XSD(Xml Schema Definition)文档的位置的关系。它的值由一个或多个URI引用对组成,两个URI之间以空白符分隔(空格和换行均可)。第一个URI是定义的XML Namespace的值,第二个URI给出Schema文档的位置,Schema处理器将从这个位置读取Schema文档,该文档的targetNamespace必须与第一个URI相匹配。例如:
在这里插入图片描述
由此可见:一切以 xsi:schemaLocation=“”为准,也就是说xsi:schemaLocation包含的部分一定要出现在名字空间中。
而实际上写的只需要>=xsi:schemaLocation中的内容即可,这就是删除的依据。

  • maven中的pom.xml文件
  • web service中xml的

[XML中的文档声明类型:dtd](http://www.yiibai.com/xml/xml_dtds.html

)

  • dtd是一种XML的约束,说白了就是我定义了dtd文件,下面的xml编写必须按照我的约束条件来写。分为内部dtd和外部dtd(系统标识符(SYSTEM)和公共标识符(PUBLIC)),和css文件的内部,外部一个意思
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值