【Spring IOC&DI】知识点详细介绍

  • Spring是一个IOC(DI)和AOP 容器框架
  • IOC(DI) :依赖注入
  • AOP :面向切面编程

Spring Framework 系统架构

【Spring IOC&DI】知识点详细介绍
  • Text :Spring的单元测试模块
  • Core Container :核心容器(IOC);黑色代表这部分的功能由哪些jar包组成
  • AOP+Aspects(面向切面编程模块)
  • ORM (Object Relation Mapping):对象关系映射
  • Transaction :事务

IOC & DI

简介

IOC:(Inversion(反转) Of Control):控制反转

DI:(Dependency Injection)依赖注入

通过IOC容器创建对象,并为属性赋值

<!--一个Bean标签可以注册一个组件(类、对象)-->
<!-- class:组件的全类名  id:唯一标识 -->
<bean id="person1" class="com.bean.Person">
    <!--使用property标签为Person对象的属性赋值-->
    <!-- name="" : 指定属性名 value="" :指定属性的值-->
	<property name="personName" value="张三" ></property>
    <property name="personAge" value="18" ></property>
</bean>
复制代码
public void test(){
    //ApplicationContext 代表ioc容器
    //ClassPathXmlApplicationContext:当前应用的xml配置文件在ClassPath下
    //根据配置文件得到ioc容器对象
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    Person person = (Person)ioc.getBean("person1");
}
复制代码

注意!

  1. 容器中对象的创建在容器创建的时候就已经创建好了
  2. 同一个组件在ioc容器中是单实例
  3. ioc容器在创建这个组件对象的时候,会利用setter方法为javabean的属性进行赋值
  4. javaBean的属性名是由getter/setter方法决定的

根据bean的类型从IOC容器中获取bean的实例

<bean id="person1" class="com.bean.Person">
	<property name="personName" value="张三" ></property>
    <property name="personAge" value="18" ></property>
</bean>
<bean id="person2" class="com.bean.Person">
	<property name="personName" value="小花" ></property>
    <property name="personAge" value="18" ></property>
</bean>
复制代码
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //如果ioc容器中这个类型的bean有多个,查找就会报错
    Person person = ioc.getBean(Person.class);
    //这样使用 即便ioc容器中这个类型的bean有多个,查找也不会报错
    Person person = ioc.getBean("person1",Person.class);
}
复制代码

通过构造器为bean的属性赋值

调用有参构造器创建对象并赋值

<bean id="person1" class="com.bean.Person">
	<constructor-arg name="personName" value="张三"></constructor-arg>
    <constructor-arg name="personAge" value="18"></constructor-arg>
    <!--此处可以省略name属性,但需要按照构造器参数的顺序指定value值-->
    <constructor-arg  value="张三"></constructor-arg>
    <constructor-arg  value="18"></constructor-arg>
    <!--index="0" 为参数指定索引 从0开始-->
    <constructor-arg  value="张三" index="0"></constructor-arg>
    <constructor-arg  value="18" index="1"></constructor-arg>
    <!--如果有多个有参构造器  使用type指定参数类型-->
    <constructor-arg  value="张三" index="0"></constructor-arg>
    <constructor-arg  value="18" index="1" type="java.lang.Integer"></constructor-arg>
</bean>
复制代码
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    Person person = ioc.getBean("person1");
}
复制代码

通过p名称空间为bean赋值

名称空间:在xml中名称空间是用来防止标签重复的

<!--使用p名称空间赋值时,需先导入p名称空间-->
<bean id="person1" class="com.bean.Person" p:personName="张三" p:personAge="18">
    
</bean>
复制代码

为复杂类型的属性赋值

//实体类
public class Person{
    private String name;
    private int age;
    
    private Car car;//Car是一个实体类
    private List<Book> books;//Book是一个实体类
    private Map<String,Object> maps;
    private Properties properties;
    
}
复制代码
<bean id="car" class="com.bean.Car">
	<property name="carNmae" value="宝马"></property>
</bean>
<bean id="book" class="com.bean.Book">
	<property name="bookNmae" value="西游记"></property>
</bean>

<bean id="person1" class="com.bean.Person">
    <!--赋值为null-->
    <property name="name">
    	<null/>
    </property>
    
    <!--ref="car" 这是一个严格的引用 person中的car跟 直接从容器中获取的car是一样的-->
    <property name="car" ref="car"></property>
    
    <!--为list类型赋值-->
    <property name="books">
    	<list>
            <!--内部bean 写id和不写id是一样的  外部获取不到-->
        	<bean id="book" class="com.bean.Book" p:bookName="西游记"></bean>
            
            <ref bean="book"/>
        </list>
    </property>
    
    <!--为map类型赋值-->
    <bean id="maps">
        <!--底层用的是LinkedHashMap-->
		<map>
            <!--一个entry代表一个键值对-->
        	<entry key="key1"value="value1"></entry>
            <entry key="key2"value="value2"></entry>
            <entry key="key3"value-ref="book"></entry>
            <entry key="key4">
                <!--内部bean无法用id获取 无论是否有id-->
            	<bean id="" class="com.bean.Car">
                	<property name="carName" value="宝马"></property>
                </bean>
            </entry>
        </map>
	</bean>
    
    <!--为Properties类型赋值-->
    <bean name="properties">
        <!--properties 里面 所有的k=v都是String类型-->
    	<props>
        	<prop key="username">root</prop>
            <prop key="username">123456</prop>
        </props>
    </bean>
 
</bean>
复制代码

级联属性赋值

<bean id="car01" class="cam.bean.Car">
    <property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="cam.bean.Person">
	<property name="car" ref="car01"></property>
    <property name="car.carName" value="奔驰"></property>
</bean>
复制代码

注意

  1. 级联属性可以修改属性的属性,但是原来的bean值将会被修改

通过继承实现bean配置信息的重用

<bean id="person1" class="com.bean.Person">
	<property name="perName" value="张三"></property>
    <property name="perAge" value="15"></property>
    <property name="perGender" value="男"></property>
</bean>

<!--parent="" : 指定当前的bean的配置信息继承于哪个bean    class=""可以省略不写 -->
<bean id="person2" class="com.bean.Person" parent="person1">
	<property name="perName" value="张三"></property>
</bean>

<!--abstract="true" 表示这个bean只能被继承 不可以获取-->
<bean id="person3" class="com.bean.Person" parent="person1" abstract="true">
	<property name="perGender" value="男"></property>
</bean>
复制代码

IOC容器中改变bean的创建顺序

<!--原来是按照配置的顺序创建bean-->
<bean id="person" class="com.bean.Person"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>

<!-- depends-on="car,book" 改变bean的创建顺序-->
<bean id="person" class="com.bean.Person" depends-on="car,book"></bean>
<bean id="car" class="com.bean.Car"></bean>
<bean id="book" class="com.bean.Book"></bean>

复制代码

bean的作用域

<bean id="book" class="com.bean.Book" scope=""></bean>
复制代码

scope="" 设置作用域 默认所有的bean都是单实例的

  • prototype :多实例的
    • 多实例的bean,容器启动默认不会去创建多实例的bean
    • 只有在获取的时候才会创建这个bean
    • 每次获取都会创建一个新的对象
  • singleton :单实例的 默认的
    • 单实例的bean在容器启动完成之前就已经创建好对象,并保存在容器中了
    • 单实例的bean从始至终 获取到的都是之前创建好的那个对象
  • request :在web环境下,同一个请求创建一个Bean实例(没用)
  • session :在web环境下,同一次会话创建一个Bean实例(没用)

静态工厂和实例工厂创建bean

静态工厂:工厂本身不用创建对象,通过静态方法调用,对象 = 工厂类.工厂方法名();

public class AirPlaneStaticFactory{
    //这个方法是静态方法
    public static AirPlane getAirPlane(String planeName){
        AirPlane airplane = new AirPlane();
    	airplane.setPlaneName(planeName);
        return airPlane;
    }
}
复制代码
<!--静态工厂(不需要创建工厂本身)
	1.class指定静态工厂全类名,
	2.factory-method 指定工厂方法, 
	3.constructor-arg 可以为方法传参	-->
<bean id="airPlane" class="com.factory.AirPlaneStaticFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飞机1号" ></constructor-arg>         
</bean>
复制代码
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //获取到的是飞机  并不是工厂
    AirPlane airplane = ioc.getBean("airPlane");
}
复制代码

**实例工厂:**工厂本身需要创建对象,

​ 工厂类 工厂对象 = new 工厂类();

​ 对象 =工厂对象.工厂方法名();

public class AirPlaneInstanceFactory{
    //这个方法不是静态方法
    public AirPlane getAirPlane(String planeName){
        AirPlane airplane = new AirPlane();
    	airplane.setPlaneName(planeName);
        return airPlane;
    }
}
复制代码
<!--实例工厂(需要创建工厂本身)-->
<bean id="airPlaneInstanceFactory" class="com.factory.AirPlaneInstanceFactory">      
</bean>
<!--factory-bean="" 指定当前对象由哪个工厂创建 factory-method=""指定工厂方法-->
<bean id="airPlane" class="com.bean.AirPlane" factory-bean="airPlaneInstanceFactory" factory-method="getAirPlane">
<constructor-arg name="planeName" value="大飞机2号" ></constructor-arg>
</bean>
复制代码
public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //获取到的是飞机  并不是工厂
    AirPlane airplane = ioc.getBean("airPlane");
}
复制代码

实现FactoryBean接口的工厂

FactoryBean 是Spring规定的一个接口,只要是这个接口的实现类 ,spring都认为是一个工厂,Spring会自动调用工厂方法创建实例

第一步:需要写一个实现了FactoryBean接口的类

public class MyFactoryBeanImpl implements FactoryBean<Book>{
    //getObject:工厂方法 返回创建的对象
    @Override
    public Book getObject() throws Exception{
        Book book = new Book();
        book.setId(1);
        return book;
    }
    //返回 创建的对象的类型
    @Override
    public Class<?> getObjectType(){
        return Book.class;
    }
    //返回  是否是单例
    //false : 不是单例  true: 是单例
    @Override
    public boolean isSingleton(){
        return false;
    }
}
复制代码

第二步:在配置文件中进行注册

<!-- 注意!:无论 isSingleton()这个方法返回值是什么  ioc容器启动的时候不会创建这个实例-->
<bean id="myFactoryBeanImpl" class="com.factory.MyFactoryBeanImpl"></bean>
复制代码

**注意 获取到的是book对象 **

public void test(){
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    //获取到的是book对象
    Book book = ioc.getBean("myFactoryBeanImpl");
}
复制代码

创建带有生命周期方法的bean

  • 生命周期:bean的创建到销毁,我们可以自定义一些生命周期方法,spring在创建或销毁的时候会调用指定的方法
  • 自定义初始化方法和销毁方法, 但是不可以有参数 可以抛异常
  • 单例Bean 的生命周期
    • (容器启动)构造器——>>>初始化方法——>>>(容器关闭)销毁方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" >
</bean>
复制代码
  • 多例Bean 的生命周期
    • (获取bean)构造器——>>>初始化方法——>>>(容器关闭)不会调用bean的销毁方法
<bean id="book" class="com.bean.Book" destory-method="" init-method="" scope="protorype" >
</bean>
复制代码

spring管理连接池

数据库链接池作为单实例是最好的,一个项目就一个连接池,连接池里面管理很多链接,链接是直接从链接池里面拿

​ 可以让Spring帮我们创建连接池对象 管理连接池

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="user" value="root"></property>
    <property name="password" value="123456"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
复制代码

spring管理连接池引用外部配置文件

jdbc.properties

#username=root
jdbc.username=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test
jdbc.driverClass=com.mysql.jdbc.Driver
复制代码

在ApplicationContext.xml中配置时 需要引入context命名空间

注意!

  • username 是spring中的一个关键字 在这里不可以使用username**
  • value="${jdbc.username}" 引号前后不能有空格
<!--加载外部配置文件的 classpath: 表示引用类路径下的配置文件-->
<context:property-placeholder location="classpath:jdbc.properties" />
<!--username 是spring中的一个关键字 在这里不可以使用username-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<!--<property name="user" value="${username}"></property>-->
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
复制代码

基于xml的自动装配

**注意!自动装配仅限于自定义类型的属性 **

<bean id="car" class="com.bean.Car">
	<property name="carName" value="宝马"></property>
    <property name="color" value="白色"></property>
</bean>
<!-- 
autowire=""
	default/no:不自动装配,不自动为car属性赋值
  	byName:以属性名作为id去容器中找到一个组件,给他赋值,如果找不到就赋值null
	byType:以属性的类型作为查找依据去容器中找到这个组件,如果容器中有多个这样的类型会报错
	constructor:按照有参构造器为car赋值
				1.先按照有参构造器参数类型进行装配,没有就直接为组件装配null
				2.如果按照类型找到了多个bean:以参数的名作为id继续装配,找不到就null
	假设有一个List<Book> books属性,容器可以把容器中所有的book封装进list
-->
<bean id="person" class="com.bean.Person" autowire="default"></bean>
复制代码

SpEL(Spring Expression Language)spring表达式语言

表达式语言

  • 字面量: #{12*5}
  • 引用其他bean的某个属性值: #{car.carName}
  • 引用其他bean: #{car}
  • 调用非静态方法#{对象.方法名(arg1,arg2)} eg: #{car.getCarName()}
  • 调用静态方法: #{T(全类名).静态方法名(arg1,arg2)} eg: #{T(java.util.UUID).randomUUID().toString()}
<bean id="car" class="com.bean.Person">
	<property name="carName" value="宝马"></property>
</bean>
<bean id="person" class="com.bean.Person">
	<property name="age" value="#{12*5}"></property>
    <property name="perName" value="#{car.carName}"></property>
    <property name="car" value="#{car}"></property>
    <property name="email" value="#{T(java.util.UUID).randomUUID().toString()}"></property>
    <property name="testName" value="#{car.getCarName()}"></property>
</bean>
复制代码

通过注解分别创建Controller、Dao、Service

spring有四个注解某个类上注解任何一个都可以讲这个组件加入到ioc容器中

  • @Controller
    • 推荐给控制器层(servlet)的组件加这个注解
  • @Service
    • 推荐给业务逻辑层的组件加这个注解 Service层
  • @Repository
    • 推荐给数据库层(持久化层,dao层)的组件添加这个注解
  • @Component
    • 给不属于以上几层的组件添加这个注解

使用注解将组价快速加入到容器中需要几步

  1. 给要添加的组件上标上以上四个注解之一

  2. 告诉spring,自动扫描加了注解的组件,依赖context命名空间

    <!--自动组件扫描-->
    <!--base-package="" 指定扫描的基础包-->
    <context:component-scan base-package=""></context:component-scan>
    复制代码
    • 使用context:exclude-filter指定扫描包时不包含的类

      • 扫描的时候可以排除一些不要的组件

      • type="annotation":按照注解进行排除–>标注了指定注解的组建不要

        expression="":注解的全类名

      • type="assignable":按照类进行排除

        expression="":类的全类名

      • type="aspectj":aspectj表达式(很少用)

      • type="custom":自定义一个TypeFilter;自己写代码决定哪些使用(很少用)

      • type="regex":正则表达式(很少用)

    <context:component-scan base-package="">
        <!--表示标注了@controller的注解不进行扫描-->
    	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--表示com.controller.BookController这个类不进行扫描-->
    	<context:exclude-filter type="assignable" expression="com.controller.BookController"/>
    </context:component-scan>
    复制代码
    • 使用context:include-filter指定扫描包时包含的类

      • 指定只扫描哪些组件,

      • 注意!需要使用 use-default-filters="false" 取消默认行为

      • type="annotation":按照注解进行排除–>标注了指定注解的组建不要

        expression="":注解的全类名

      • type="assignable":按照类进行排除

        expression="":类的全类名

      • type="aspectj":aspectj表达式(很少用)

      • type="custom":自定义一个TypeFilter;自己写代码决定哪些使用(很少用)

      • type="regex":正则表达式(很少用)

    <context:component-scan base-package="" use-default-filters="true">
        <!--表示只有标注了@controller的注解进行扫描-->
    	<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <!--表示只有com.controller.BookController这个类进行扫描-->
    	<context:include-filter type="assignable" expression="com.controller.BookController"/>
    </context:component-scan>
    复制代码
  3. 一定要导入AOP包 支持加注解模式

    这里bean的id默认就是类名首字母小写

    使用注解加入到容器中的组件,和使用配置加入到容器中的组件行为都是默认一样的:

    • 组件的id,默认就是类名首字母小写
    • 组件的作用域,默认就是单例的
    public void test(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
        Object bean = ioc.getBean("");
    }
    复制代码

注意,如果想要修改默认行为

  • 修改bean的Id
@Repository("你想要的id名")  四个注解都是一样的
复制代码
  • 修改作用域
@Scope(value="prototype")  
复制代码

使用@Autowired注解实现自动装配

@Autowired为bookservice自动赋值

@Controller
public class BookServlet{ 
    @Autowired
    private BookService bookService;
}
复制代码

@Autowired原理

  1. 按照类型 去容器中找到对应的组件;bookService = ioc.getBean("BookService.class");

    1. 如果找到了就直接赋值
    2. 没找到就抛出异常
    3. 如果找到多个,就按照变量名作为id继续匹配
      1. 如果匹配上就赋值
      2. 如果没有匹配上,就抛出异常。
      3. 注意!这里可以使用@Qualifier注解 作用是可以指定一个名称作为id,取消spring使用变量名作为id的行为。eg:@Qualifier(“newbookservice“)
        1. 如果找到就装配
        2. 如果找不到,就抛出异常

    注意!

    可以使用required=false 来解决 如果@Autowired找不到指定bean 就赋值为null

    @Autowired(required=false)

在方法上使用@Autowired注解并在形参位置使用@Qualifier

  • 方法上有@Autuwired
    • 这个方法会在容器创建的时候自动运行
    • 这个方法的每一个参数都会自动注入值
@Autowired
public void methods(@Qualifier("newbookservice")BookService bookservice){
    System.out.println("")
}
复制代码

@Autowired 和 @Resource的区别

  • @Autowired 和 @Resource 和 @Inject都是自动装配的意思
  • @Autowired最强大:Spring自己的注解
  • @Resource:j2ee java的标准 ,扩展性更强 因为如果我们切换另外一个容器框架@Resource还是可以使用,但是@Autowired离开了spring 就不能使用了

Spring的单元测试

  1. 导包 导入spring-test-4.0.0

  2. @ContextConfiguration使用这个注解来指定spring的配置文件的位置

  3. @RunWith()指定用哪种驱动进行单元测试。默认就是junit

    ​ @RunWith(SpringJUnit4ClassRunner.class)

    ​ 使用spring的单元测试模块来执行标注了@Test注解的测试方法

    ​ 以前的@Test只是由JUnit执行

@ContextConfiguration(locations="classpath:spring.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringTest{
    ApplicationContext ac = null;
    
    @Autowired
    BookServlet bookServlet;
}
复制代码

泛型依赖注入

泛型依赖注入,注入一个组件的时候,他的泛型也是参考标准

【Spring IOC&amp;DI】知识点详细介绍
  • 首先BookDao和UserDao都继承BaseDao这个抽象类并实现抽象方法
public abstract class BaseDao<T>{
    public abstract void save();
    ...
}
复制代码
@Repository
public class BookDao extends BaseDao<Book>{
    @Override
    public void save(){
        ...
    }
    ...
}
复制代码
@Repository
public class UserDao extends BaseDao<User>{
    @Override
    public void save(){
        ...
    }
    ...
}
复制代码
  • 然后BookService和UserService都继承了BaseService,并且在继承的时候,确定了泛型的类型。
@Service
public class BookService extends BaseService<Book>{
    ...
}
复制代码
@Service
public class UserService extends BaseService<User>{
    ...
}
复制代码
public class BaseService<T>{
    @Autowired
    private BaseDao<T> baseDao;
    
    public void test(){
        baseDao.save();
    }
    ...
}
复制代码

流程分析

  • 当在BookService的对象调用test()方法的时候,虽然BaseService没有在容器中,但是BookService在容器中,所以继承了BaseService的BookService会自动为baseDao属性赋值,此时相当于BookService中 有一个 BaseDao baseDao ; 它便会去容器中找一个 BaseDao 类型的bean为 baseDao 赋值。所以将容器中的BookDao赋值给 baseDao ,当BookService调用test()方法时其实调用的就是bookDao中的save()方法。
@Test
public void test() {
    	
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring.xml");
    	BookService bookService = ioc.getBean(BookService.class);
        bookService.test();
    }
复制代码

原文 

https://juejin.im/post/5e683185e51d4526ef5fa36a

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 【Spring IOC&DI】知识点详细介绍

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址