一个菜鸡的「Spring学习笔记」-1

283 阅读18分钟

Spring概述

「Spring介绍」

Spring是分层的Java SE/EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制,把创建对象过程交给Spring进行管理)AOP(Aspect Oriented Programming:面向切面编程,不修改源代码进行功能增强 为内核,提供了展现层Spring MVC持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架。

如何在IDEA创建Spring项目可参考文章:juejin.cn/post/715988…

「Spring体系结构」

image.png

引入Spring IOC

「程序耦合」

我们在开发中,会写很多的类,而有些类之间不可避免的产生依赖关系,这种依赖关系称之为耦合。有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。如下示例代码:

// 客户的业务层实现类
public class CustomerServiceImpl implements ICustomerService {	
    private ICustomerDao customerDao = new CustomerDaoImpl();	
}

上面的代码表示:业务层调用持久层,并且此时业务层在依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种依赖关系就是我们可以通过优化代码解决的。

再比如下面的代码中,我们的类依赖了MySQL的具体驱动类,如果这时候更换了数据库品牌,我们需要改源码来修改数据库驱动。

public class JdbcDemo1 {
	/**
	 * JDBC操作数据库的基本入门中存在什么问题?
	 *   导致驱动注册两次是个问题,但不是严重的。
	 *   严重的问题:是当前类和mysql的驱动类有很强的依赖关系.当我们没有驱动类的时候,连编译都不让。
	 * 
	 *  我们在开发中,理想的状态应该是:编译时不依赖,运行时才依赖。
	 */
	public static void main(String[] args) throws Exception {
		//1.注册驱动
		DriverManager.registerDriver(new com.mysql.jdbc.Driver());
                ...
	}
}

「解决耦合的思路」

上面JDBC的代码,我们当时是通过反射来注册驱动的:

Class.forName("com.mysql.jdbc.Driver");

这时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译。但是因为没有驱动类,所以不能运行。不过,此处也有个问题,就是我们反射类对象的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。解决这个问题也很简单,使用配置文件配置。

「工厂模式解耦」

在实际开发中我们可以把所有的dao和service和action对象使用配置文件配置起来,当启动服务器应用加载的时候,通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。

「IOC控制反转」

解耦思路的问题:

1)存去哪里

在应用加载时,创建一个Map,用于存放action,Service和dao对象。我们把这个map称之为容器

2)什么是工厂

工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。 原来我们在获取对象时,都是采用new的方式,是主动的。现在我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象,是被动的。

这种被动接收的方式获取对象的思想就是控制反转,它是spring框架的核心之一。它的作用只有一个:削减计算机程序的耦合。

「IOC解决程序耦合」

1、创建持久层接口和实现类

// 持久层接口
public interface CustomerDao {
    void saveCustomer();
}

// 持久层实现类
public class CustomerDaoImpl implements CustomerDao {
    @Override
    public void saveCustomer() {
        System.out.println("ICustomerDaoImpl--保存客户方法");
    }
}

2、创建业务层接口和实现类

// 业务层接口
public interface CustomerService {
    void saveCustomer();
}

// 业务层实现类
public class CustomerServiceImpl implements CustomerService {
    // 此处有依赖关系
    private CustomerDao customerDao = new CustomerDaoImpl();
    @Override
    public void saveCustomer() {
        customerDao.saveCustomer();
    }
}

3、配置 spring-config.xml 。把资源交给spring来管理,在配置文件中 配置service和dao

<?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">

    <!--配置Customer相关资源-->
    <bean id="customerDao" class="com.spring.customer.dao.CustomerDaoImpl" />
    <bean id="customerService" class="com.spring.customer.service.CustomerServiceImpl" />
</beans>

4、测试配置。看下IOC是如何解决程序耦合问题的:

// 模拟表现层,调用
public class CustomerTest {
    public static void main(String[] args) {
        // 创建一个Spring的IOC容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 从IOC容器中获取Bean实例
        CustomerService customerService = (CustomerService) context.getBean("customerService");
        System.out.println(customerService);
        customerService.saveCustomer();

        // 从IOC容器中获取Bean实例
        CustomerDao customerDao = (CustomerDao) context.getBean("customerDao");
        System.out.println(customerDao);
        customerDao.saveCustomer();
    }
}

Spring基于XML的IOC细节

「工厂类结构图」

image.png

BeanFactory和ApplicationContext的区别:

  • BeanFactory才是Spring容器中的顶层接口。ApplicationContext是它的子接口。
  • BeanFactory和ApplicationContext还区别在创建对象的时间点不一样:
    • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。(立即加载思想)
    • BeanFactory:什么时候使用(也就是调用)什么时候创建对象。(延迟加载思想)

ApplicationContext接口实现类:

image.png

  • ClassPathXmlApplicationContext从类的根路径下加载配置文件(推荐使用这种)
  • FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
// 创建一个Spring的IOC容器对象
ApplicationContext context = new
    ClassPathXmlApplicationContext("spring-config.xml");

bean标签管理

bean管理指两部分:Spring创建对象、Spring注入属性

「bean标签」

bean标签的作用:用于配置对象让spring来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。(最佳实践:类无参构造一定要写上)

【bean标签的属性】

  • id:给对象在容器中提供一个唯一标识,用于获取对象
  • class:指定类的全限定类名用于反射创建对象。默认情况下调用无参构造函数
  • scope:指定对象的作用范围
    • singleton:默认值,单例
    • prototype:多例的。(当Spring接管struts2的action创建,action必须配置此值)
    • request:WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中。
    • session:WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中。
    • globalSession:WEB项目中,应用在Portlet环境。如果没有Portlet环境那么globalSession相当于session
  • init-method:指定类中的初始化方法名称
  • destroy-method:指定类中销毁方法名称

「Bean作用范围」

  • 单例对象:scope="singleton"(默认为单例)
    • 一个应用只有一个对象的实例。它的作用范围就是整个引用。
    • 生命周期:
      • 对象出生:当应用加载创建容器时(加载spring配置文件)对象就被创建了
      • 对象活着:只要容器在,对象一直活着。
      • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
  • 多例对象:scope="prototype"
    • 每次访问对象时(调用getBean方法),都会重新创建对象实例
    • 生命周期:
      • 对象出生:当使用对象时,创建新的对象实例。
      • 对象活着:只要对象在使用中,就一直活着。
      • 对象死亡:当对象长时间不用时,被java的垃圾回收器回收了。

「实例化bean三种方式」

第一种:使用默认无参构造函数(使用最多)

在默认情况下,它会根据默认无参构造函数来创建类对象。如果bean中没有默认无参构造函数,将会创建失败。

<bean id="customerService" class="com.spring.customer.service.CustomerServiceImpl" />

第二种:spring管理静态工厂-使用静态工厂的方法创建对象

此种方式是:使用StaticFactory类中的静态方法createCustomerService创建对象,并存入spring容器

id属性:指定bean的id,用于从容器中获取

class属性:指定静态工厂的全限定类名

factory-method属性:指定生产对象的静态方法

// 模拟一个静态工厂,创建业务层实现类
public class StaticFactory {
    public static CustomerService createCustomerService(){
        return new CustomerServiceImpl();
    }
}
<bean id="customerService" class="com.spring.customer.factory.StaticFactory" factory-method="createCustomerService" />

第三种:spring管理实例工厂-使用实例工厂的方法创建对象

此种方式是先把工厂的创建交给spring来管理。然后在使用工厂的bean来调用里面的方法。

factory-bean属性:用于指定实例工厂bean的id

factory-method属性:用于指定实例工厂中创建对象的方法

// 模拟一个实例工厂,创建业务层实现类
// 此工厂创建对象,必须现有工厂实例对象,再调用方法
public class InstanceFactory {
    public CustomerService createCustomerService(){
        return new CustomerServiceImpl();
    }
}
<!--第3种实例化bean方式-->
<bean id="instanceFactory" class="com.spring.customer.factory.InstanceFactory"/>
<bean id="customerService"
      factory-bean="instanceFactory"
      factory-method="createCustomerService"/>

FactoryBean可以让定义的bean类型和返回类型不一样

public class MyBean implements FactoryBean<Course> {

    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setName("abc");
        return course;
    }

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

    @Override
    public boolean isSingleton() {
        return false;
    }
}

「Bean生命周期」

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

Spring依赖注入

什么叫依赖注入:它是spring框架核心IOC的具体实现方式。简单的说,就是坐等框架把对象传入,而不用我们自己去获取(我们只管问框架要就可以了)

「构造函数注入」

顾名思义,就是使用类中的构造函数给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。

使用构造函数的方式,给service中的属性传值。要求:类中需要提供一个对应参数列表的构造函数

constructor-arg属性:

  • index:指定参数在构造函数参数列表的索引位置
  • type:指定参数在构造函数中的数据类型
  • name:指定参数在构造函数中的名称,用这个找给谁赋值
  • value:它能赋的值是基本数据类型和String类型
  • ref:它能赋的值是其他bean类型,也就是说,必须得是在配置文件中配置过的bean
public class CustomerServiceImpl2 implements CustomerService {
    
    private String name;
    private Integer age;
    private Date birthday;

    public CustomerServiceImpl2(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    @Override
    public void saveCustomer() {
        System.out.println("CustomerServiceImpl2{" + "name='" + name + ''' +
                ", age=" + age + ", birthday=" + birthday + '}');
    }
}
<bean id="customerService2" class="com.spring.customer.service.CustomerServiceImpl2">
    <constructor-arg name="name" value="测试依赖注入"/>
    <constructor-arg name="age" value="2022"/>
    <constructor-arg name="birthday" ref="now"/>
</bean>
<bean id="now" class="java.util.Date"/>

测试代码:

public class CustomerTest {
    public static void main(String[] args) {
        // 创建一个Spring的IOC容器对象
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

        // 从IOC容器中获取Bean实例
        CustomerService customerService = (CustomerService) context.getBean("customerService2");
        System.out.println(customerService);
        customerService.saveCustomer();
    }
}

「★set()方法注入★」

顾名思义,就是在类中提供需要注入成员的set方法。通过配置文件给bean中的属性传值。

涉及的标签:

property标签属性

  • name:找的是类中set方法后面的部分
  • ref:给属性赋值是其他bean类型的
  • value:给属性赋值是基本数据类型和string类型的

实际开发中,此种方式用的较多。

public class CustomerServiceImpl3 implements CustomerService{
    private String name;
    private Integer age;
    private Date birthday;

    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public void saveCustomer() {
        System.out.println("CustomerServiceImpl2{" + "name='" + name + ''' +
                ", age=" + age + ", birthday=" + birthday + '}');
    }
}
<bean id="customerService3" class="com.spring.customer.service.CustomerServiceImpl3">
    <property name="name" value="测试依赖注入方式3"/>
    <property name="age" value="2023"/>
    <property name="birthday" ref="now"/>
</bean>

测试代码:

// 从IOC容器中获取Bean实例
CustomerService customerService = (CustomerService) context.getBean("customerService3");
System.out.println(customerService);
customerService.saveCustomer();

「使用p名称空间注入」

本质还是调用set()方法。了解即可

public class CustomerServiceImpl4 implements CustomerService {
    private String name;
    private Integer age;
    private Date birthday;

    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public void saveCustomer() {
        System.out.println("CustomerServiceImpl2{" + "name='" + name + ''' +
                ", age=" + age + ", birthday=" + birthday + '}');
    }
}
xmlns:p="http://www.springframework.org/schema/p"

<bean id="customerService4" class="com.spring.customer.service.CustomerServiceImpl4"
    p:name="测试依赖注入方式4" p:age="2024" p:birthday-ref="now"/>

测试代码同上,bean获取customerService4

「注入其他类型」

null值

<property name="address">
    <null />
</property>

属性值包含特殊符号

<property name="address">
    <value><![CDATA[<<杭州>>]]></value>
</property>

「注入配置文件」

需要引入context名称空间

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.username=***
jdbc.password=***

在spring配置文件使用标签引入外部属性文件:

<!--引入外部属性文件--> 
<context:property-placeholder location="classpath:jdbc.properties"/> 

<!--配置连接池--> 
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
    <property name="driverClassName" value="${prop.driverClass}"></property>
    <property name="url" value="${prop.url}"></property>
    <property name="username" value="${prop.userName}"></property>
    <property name="password" value="${prop.password}"></property>
</bean> 

「注入集合属性」

给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。如下测试一下注入数组,List,Set,Map,Properties:

public class CustomerServiceImpl5 implements CustomerService {
    private String[] myStr;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String, String> myMap;
    private Properties myProps;

    public void setMyStr(String[] myStr) {
        this.myStr = myStr;
    }
    public void setMyList(List<String> myList) {
        this.myList = myList;
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }

    @Override
    public void saveCustomer() {
        System.out.println(Arrays.toString(myStr));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}
<bean id="customerService5" class="com.spring.customer.service.CustomerServiceImpl5">
    <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
    <!--给数组注入数据-->
    <property name="myStr">
        <array>
            <value>myStr-AA</value>
            <value>myStr-BB</value>
            <value>myStr-CC</value>
        </array>
    </property>
    <!--注入list集合数据-->
    <property name="myList">
        <list>
            <value>myList-11</value>
            <value>myList-22</value>
        </list>
    </property>
    <!--注入set集合数据-->
    <property name="mySet">
        <set>
            <value>mySet-aa</value>
            <value>mySet-bb</value>
            <value>mySet-cc</value>
        </set>
    </property>
    <!--注入Map数据-->
    <property name="myMap">
        <map>
            <entry key="map-A-Key" value="map-A-Value"/>
            <entry key="map-B-Key">
                <value>map-B-Value</value>
            </entry>
        </map>
    </property>
    <!--注入properties数据-->
    <property name="myProps">
        <props>
            <prop key="myProps-xxx">myProps-xxx-XXX</prop>
            <prop key="myProps-yyy">myProps-yyy-YYY</prop>
        </props>
    </property>
</bean>

测试代码同上,bean获取customerService5

测试结果:

com.spring.customer.service.CustomerServiceImpl5@7e2d773b
[myStr-AA, myStr-BB, myStr-CC]
[myList-11, myList-22]
[mySet-aa, mySet-bb, mySet-cc]
{map-A-Key=map-A-Value, map-B-Key=map-B-Value}
{myProps-yyy=myProps-yyy-YYY, myProps-xxx=myProps-xxx-XXX}

还可以使用util标签提取出来集合然后使用:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util.xsd">
    
    ...
    <property name="bookList" ref="bookList"></property>

    ...
    <util:list id="bookList">
        <value>西游记</value>
        <value>三国演义</value>
        <value>水浒传</value>
        <value>红楼梦</value>
    </util:list>

「注入内部bean」

Emp类中有Dept属性,并有setDept()方法:

<bean id="emp" class="com.spr.emp.Emp">
    <property name="ename" value="职员1"></property>
    <property name="gender" value="female"></property>
    <property name="dept">
        <bean id="dept" class="com.spr.emp.Dept">
            <property name="dname" value="技术中心"></property>
        </bean>
    </property>
</bean>

「级联赋值」

方法一:

<bean id="emp1" class="com.spr.emp.Emp">
    <property name="ename" value="职员2"></property>
    <property name="gender" value="male"></property>
    <property name="dept" ref="dept1"></property>
</bean>
<bean id="dept1" class="com.spr.emp.Dept">
    <property name="dname" value="测试部"></property>
</bean>

方法二:

private Dept dept;

public Dept getDept() { // 在Emp类中需要有Dept的get方法
    return dept;
}
<bean id="dept1" class="com.spr.emp.Dept">
    <property name="dname" value="财务部"></property>
</bean>
<bean id="emp2" class="com.spr.emp.Emp">
    <property name="ename" value="职员3"></property>
    <property name="gender" value="male"></property>
    <property name="dept" ref="dept1"></property>
    <property name="dept.dname" value="运营部"></property>
</bean>

最后输出的结果是运营部

「自动装配属性」

bean 标签属性autowire,配置自动装配

autowire 属性常用两个值:

  • byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
  • byType 根据属性类型注入

根据属性名称自动注入:

<bean id="emp" class="com.spring5.autowire.Emp" autowire="byName">
    <!--<property name="dept" ref="dept"></property>-->
</bean>

<bean id="dept" class="com.spring5.autowire.Dept">
</bean>

根据属性类型自动注入:

<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byType">
    <!--<property name="dept" ref="dept"></property>-->
</bean>

<bean id="dept" class="com.spring5.autowire.Dept">
</bean>

基于注解的IOC配置

第一步,导入约束(给配置文件导入约束)

<?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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

第二步,使用 @Component 配置管理的资源

// 业务层实现类
@Component(value = "customerService")
public class CustomerServiceImpl implements CustomerService {
    @Override
    public void saveCustomer() {
        System.out.println("CustomerServiceImpl.saveCustomer()");
    }
}

第三步,修改Spring配置文件,在spring的配置文件中开启spring对注解ioc的支持:(如果没有这一步,是无法执行的,显示“No bean named 'customerService'”)

多个包使用逗号隔开

<!--告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中-->
<context:component-scan base-package="com.spring.customer.service" />

「开启组件扫描细节配置」

只扫描带对应注解的类:


<!--示例1
    use-default-filters="false"  表示现在不使用默filter,自己配置
    filter context:include-filter  设置扫描哪些内容 
--> 

<context:component-scan base-package="com.code" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

不扫描带对应注解的类:

<!--示例2
    下面配置扫描包所有内容 
    context:exclude-filter: 设置哪些内容不进行扫描 
--> 
<context:component-scan base-package="com.atguigu">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

✍️常用注解✍️

「用于创建对象」

相当于:<bean id="" class="">

如: <bean id="customerService" class="com.spring.customer.service.CustomerServiceImpl"/>

|@Component|

作用:把资源让spring来管理。相当于在xml中配置一个bean。

属性:

value:指定bean的id。如果不指定value属性,默认bean的id是当前类的类名,首字母小写。

@Component(value = "customerService")
public class CustomerServiceImpl implements CustomerService {}

|@Controller|

作用:一般用于表现层的注解

|@Service|

作用:一般用于业务层的注解

// 业务层实现类
@Service(value = "customerService")
public class CustomerServiceImpl implements CustomerService {
}

|@Repository|

作用:一般用于持久层的注解

// 持久层实现类
@Repository(value = "customerDao")
public class CustomerDaoImpl implements CustomerDao {
}

他们三个注解都是针对一个的衍生注解,作用及属性都是一模一样的,只不过是提供了更加明确的语义化。

细节:如果注解中有且只有一个属性要赋值时,且名称是value,value在赋值时可以不写。

「用于注入数据」

相当于:<property name="" ref=""> 和 <property name="" value=""> 如:

<property name="age" value="24"></property>

<property name="birthday" ref="now"></property>

|@Autowired|

作用:自动按照类型注入当使用注解注入属性时,set方法可以省略它只能注入其他bean类型。当有多个类型匹配时,使用要注入的对象变量名称作为bean的id,在spring容器查找,找到了也可以注入成功。找不到就报错。

@Autowired
private CustomerDao customerDao;

|@Qualifie|

作用:在自动按照类型注入的基础之上,再按照Bean的id注入。它在给字段注入时不能独立使用,必须和@Autowire一起使用;但是给方法参数注入时,可以独立使用。

属性:value:指定bean的id。

@Autowired
@Qualifier(value = "customerDao")
private CustomerDao customerDao;

|@Resource|

作用:直接按照Bean的id注入,它也只能注入其他bean类型

可以根据类型注入,也可以根据名称注入

属性:name:指定bean的id。

@Resource // 根据类型进行注入
@Resource(name = "customerDao") // 根据名称进行注入
private CustomerDao customerDao;

|@Value|

作用:注入基本数据类型和String类型数据的。还可以读取文件配置

属性: value:用于指定值

@Value(value = "com.mysql.jdbc.Driver")
private String driver;
@Value(value = "19")
private int number;

「用于改变作用范围」

相当于:<bean id="" class="" scope="prototype|singleton|...">

|@Scope|

作用:指定bean的作用范围

属性:value:指定范围的值。取值:singleton、prototype、request、session、globalsession

@Scope(value = "singleton")
public class CustomerServiceImpl implements CustomerService {
}

「生命周期相关」

相当于:<bean id="" class="" init-method="" destroy-method=""/> (了解即可)

|@PostConstruct|

作用:用于指定初始化方法。

|@PreDestroy|

作用:用于指定销毁方法。

「注解&XML如何选择」

注解的优势:配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。

XML的优势:修改时,不用改源码。不涉及重新编译和部署。

场景基于XML配置基于注解配置
Bean定义<bean id="xxx" class="xxx" />@Component。衍生类:@Controller(表现层)@Service(业务层)@Repository(持久层)
Bean名称通过id或name指定@Component(value = "xxx")
Bean注入<property name="xxx" value="xxx"/>,或者p命名空间@Autowired(按照类型)、@Qualifie(按照名称),或者 @Resource(Bean类型)、 @Value(基本类型)
生命过程+Bean作用范围init-method、destory-method,范围scope属性@PostConstruct(初始化)、@PreDestroy(销毁)、@Scope(作用范围)
适合场景Bean来自第三方Bean的实现类由自己开发

「常用注解实例」

Dao层接口和实现类:

public interface CustomerDao {
    void saveCustomer();
}
@Repository(value = "customerDao")
public class CustomerDaoImpl implements CustomerDao{

    @Override
    public void saveCustomer() {
        System.out.println("Dao层保存用户方法");
    }
}

Service层接口和实现类:

public interface CustomerService {
    void saveCustomer();
}
@Component(value = "customerService")
@Scope(value = "singleton")
public class CustomerServiceImpl implements CustomerService {

    @Resource(name = "customerDao")
    private CustomerDao customerDao = null;
    @Value(value = "com.mysql.jdbc.Driver")
    private String driver;

    @Override
    public void saveCustomer() {
        System.out.println("这里是一些数据库信息:" + driver);
        System.out.println("service层保存用户:");
        customerDao.saveCustomer();
    }
}

spring-config.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.spdemo" />
</beans>

测试代码:

public class TestClient {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        CustomerService customerService = (CustomerService) context.getBean("customerService");
        customerService.saveCustomer();
    }
}

Spring纯注解配置

问题背景:

之所以我们现在离不开spring-config.xml配置文件,是因为我们有一句很关键的配置:

<context:component-scan base-package="com.spdemo" />

「注解配置扫描包」

@Configuration // 表明当前类是一个配置类
@ComponentScan(basePackages = "com.spdemo") //配置要扫描的包
public class SpringConfiguration {
}

测试类获取容器的方法也发生了变化:

public class TestClient {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        CustomerService customerService = (CustomerService) context.getBean("customerService");
        customerService.saveCustomer();
    }
}

「新注解说明」

|@Configuration|

作用:用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解。获取容器时需要使用AnnotationApplicationContext(有@Configuration注解的类.class)。

属性:value:用于指定配置类的字节码

|@ComponentScan|

作用:用于指定 spring在初始化容器时要扫描的包。作用和在spring的xml配置文件中的: <context:component-scan base-package="xxx"/>是一样的。

属性:basePackages:用于指定要扫描的包。和该注解中的value属性作用一样。

@Configuration // 表明当前类是一个配置类
@ComponentScan(basePackages = "com.spdemo") //配置要扫描的包
public class SpringConfiguration {
}

|@PropertySource|

作用:用于加载.properties文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到properties配置文件中,就可以使用此注解指定properties配置文件的位置。

属性:value[]:用于指定properties文件位置。如果是在类路径下,需要写上 classpath:xxx

jdbcConfig.properties配置:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/demo
jdbc.username=***
jdbc.password=***

配置加载文件:

@Configuration // 表明当前类是一个配置类
@ComponentScan(basePackages = "com.spdemo") //配置要扫描的包
@PropertySource(value = {"classpath:config/jdbcConfig.properties"})
public class SpringConfiguration {
}

备注:如果classpath路径错误,则会报错

image.png

|@Import|

作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration注解。当然,写上也没问题。

属性:value[]:用于指定其他配置类的字节码。

@Configuration
@ComponentScan(basePackages = "xxx")
@Import({ Configuration_B.class})
public class Configuration_A {
}


@Configuration
@PropertySource("classpath:info.properties")
public class Configuration_B {
}

|@Bean|

作用:该注解只能写在方法上,表明使用此方法创建一个对象,并且放入spring容器。它就相当于我们之前在xml配置中的factory-bean和factory-method

属性:name:给当前@Bean注解方法创建的对象指定一个名称(即bean的id)。

@Bean(name = "datasource2")
	public DataSource createDS() throws Exception {
		ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
		comboPooledDataSource.setUser("root");
		comboPooledDataSource.setPassword("1234");
		comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
		comboPooledDataSource.setJdbcUrl("jdbc:mysql:///spring_ioc");
		return comboPooledDataSource;
	}

Spring整合Junit

【准备测试内容】

持久层接口和实现类:

// 持久层接口
public interface CustomerDao {
    // 查询所有客户
    List<Customer> findAllCustomer();
    // 保存客户
    void saveCustomer(Customer customer);
}


// 持久层实现类
public class CustomerDaoImpl implements CustomerDao{
    @Override
    public List<Customer> findAllCustomer() {
        System.out.println("dao--查询所有用户成功");
        return null;
    }

    @Override
    public void saveCustomer(Customer customer) {
        System.out.println("dao--保存用户成功");
    }
}

业务层接口和实现类:

// 业务层接口
public interface CustomerService {
    // 查询所有客户
    List<Customer> findAllCustomer();
    // 保存客户
    void saveCustomer(Customer customer);
}


// 业务层实现类
public class CustomerServiceImpl implements CustomerService {

    private CustomerDao customerDao;

    public void setCustomerDao(CustomerDao customerDao) {
        this.customerDao = customerDao;
    }

    @Override
    public List<Customer> findAllCustomer() {
        return customerDao.findAllCustomer();
    }

    @Override
    public void saveCustomer(Customer customer) {
        customerDao.saveCustomer(customer);
    }
}

测试类:

public class CustomerTest {
    private CustomerService customerService;

    @Test
    public void testFindAllCustomer(){
        customerService.findAllCustomer();
    }

    @Test
    public void testSaveCustomer(){
        Customer customer = new Customer();
        customer.setName("Spring-name");
        customerService.saveCustomer(customer);
    }
}

「使用XML配置」

1)xml文件中的配置

<?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">

    <!-- 把资源交给spring来管理 -->
    <bean id="customerDao" class="com.code.dao.CustomerDaoImpl"></bean>
    <bean id="customerService" class="com.code.service.CustomerServiceImpl">
        <property name="customerDao" ref="customerDao"></property>
    </bean>
</beans>

2)使用@RunWith注解替换原有运行器

@RunWith(SpringJUnit4ClassRunner.class)
public class CustomerTest {
    ...
}

3)使用@ContextConfiguration指定spring配置文件的位置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-config.xml"})
public class CustomerTest {
    ...
}

4)使用@Autowired给测试类中的变量注入数据

@Autowired
private CustomerService customerService;

「使用纯注解配置」

1)把资源都用注解管理

@Service("customerService")
public class CustomerServiceImpl implements CustomerService {

    @Autowired
    private CustomerDao customerDao;
    
    ...
}
@Repository("CustomerDao")
public class CustomerDaoImpl implements CustomerDao{
    ...
}

2)使用注解配置方式创建spring容器

@Configuration
@ComponentScan(basePackages = {"com.code"})
public class CustomerTest2 {

    @Autowired
    private CustomerService customerService;
    
    ...
}

3)使用RunWith注解和ContextConfiguration注解配置

@Configuration
@ComponentScan(basePackages = {"com.code"})
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {CustomerTest2.class})
public class CustomerTest2 {
    ...
}