Spring——Spring学习教程(详细)(上篇)——IOC、AOP

本文是Spring的学习上篇,主要讲IOC和AOP。
Spring的JDBCTemplete以及事务的知识,请见下篇。

Spring——Spring学习教程(详细)(下篇)——JDBCTemplete、事务

文章目录

1、Spring概述

Spring,最核心的概念就两个:AOP(切面编程)和DI(依赖注入),而DI又依赖IoC。Spring其实就是对java众多功能的封装。

1.1 Spring概述

(1)Spring是一个开源框架;

(2)Spring框架为简化企业级开发而生,使用Spring和JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB重要通过复杂的配置和代码才能够实现,而在Spring中就十分简洁优雅;

(3)Spring是一个IOC(DI)和AOP容器框架。

(4)Spring的优良特性:
A.非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。
B.依赖注入:DI(Dependency Injection),是反转控制(IOC)最经典的实现。
C.面向切面编程:AOP(Aspect Oriented Programming)。
D.容器:Spring是一个容器,因为它包含并管理应用对象的生命周期。
E.组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以使用xml和java注解组合这些对象。Spring中的组件就是Spring管理的对象。
F.一站式:在AOP和IOC的基础上整合各种企业应用的开源框架和优秀的第三方类库(事实上上,Spring本身也提供了表现层的SpringMVC和持久层的SpringJDBC)。

(5)Spring模块
在这里插入图片描述
可以理解为有四大部分:核心容器、Spring数据访问、Spring Web以及其他。

A. 核心容器(IOC)
核心容器层是spring框架的基础,其他层都依赖于这一层,核心容器这一层包含以下4个模块:

Spring Core:这个模块是Spring框架的核心,提供控制反转/依赖注入功能

Spring Bean:这个模块实现Bean的工厂模式,Bean可以理解为组件,是JEE中基本的代码组织单位,Spring中Bean形式是普通Java类

Spring Context:此模块表示Spring应用的环境,通过此模块可访问任意Bean,ApplicationContext接口是模块的关键组成

Spring表达式语言(SpEL):这个模块提供对表达式语言(SpEL)支持

B. Spring数据访问/集成
数据访问相关,由以下5个模块组成:

JDBC:对Java JDBC接口再次包装,让Spring应用中使用JDBC更简单

ORM: ORM代表对象关系映射,该模块提供对ORM的支持

OXM: OXM代表对象XML映射器,该模块提供对OXM的支持

JMS: JMS代表Java消息传递服务,该模块提供对JMS的支持

事务: 该模块提供数据库事务的支持

C. Spring Web

Web层包括以下模块:

Web:提供基本的Web功能,如文件下载、rest接口支持等

web-servlet:实现MVC(Model-View-Controller)功能

web socket:提供对web socket的支持

web portlet:提供对web portlet的支持

D. 其他模块

AOP 提供对面向切面编程的支持。

Aspects 提供与AspectJ集成,AspectJ是另一个面向切面编程的框架。

Instrumentation 提供在某些应用服务器中使用的类加载实现。

Messaging 提供对STOMP(Simple (or Streaming) Text Oriented Message Protocol )的支持。

Test 支持JUnit或TestNG框架测试Spring组件

小插播:Spring中的IOC就是用来管理对象的。

1.2 Idea创建Spring项目

我们采用使用maven管理依赖。
(1)Idea界面左上角new一个project,选择maven,点next,命名,最终finish。
在这里插入图片描述
(2)右击项目名称,add framework Support,选择Spring,点击OK,即开始下载相关jar包到lib包内,下载完成后项目建立完成。
在这里插入图片描述

1.3 Spring体验小案例

Spring是一个容器,因为它包含并管理应用对象的生命周期。
(1)之前我们会new一个对象并进行使用
首先定义Person类。

package com.test02.test;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/22 16:34
 */
public class Person {
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

接着,创建Person对象并访问。

package com.test02.test;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/22 17:05
 */
public class TestPerson {

    public static void main(String args[]) {
        Person per = new Person();
        per.setId(16);
        per.setName("小二");
        System.out.println(per);
    }
}

(2)应用Spring后,我们由Spring管理对象
首先,继续定义Dog类。

package com.test02.test;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/22 17:22
 */
public class Dog {
    private Integer age;
    private String name;
    @Override
    public String toString() {
        return "Dog{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

接着,创建Spring配置文件applicationContext.xml,管理Person和Dog类,还可以设置类的属性值。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
    <bean>:定义Spring所管理的一个对象
    id:该对象的唯一标识,不可重复,在通过类型获取bean的过程中可以不设置id
    class:子对象所属类的全限定名
    -->
    <bean id="Person" class="com.test02.test.Person"></bean>
    <bean id="Person2" class="com.test02.test.Person"></bean>
    
    <bean id="Dog" class="com.test02.test.Dog">
        <!--
        <property>:为某个对象的属性赋值
        name:属性名
        value:属性值
        -->
        <property name="age" value="3"></property>
        <property name="name" value="小花"></property>
    </bean>


</beans>

第三,创建测试类直接访问Person和Dog对象。

package com.test02.test;
import javafx.application.Application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/22 17:25
 */
public class TestBySpring {
    public static void main(String args[]) {
        //初始化容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过getBean()获取对象
        Person per = (Person) ac.getBean("Person");//强转为Person,才能赋值
        per.setName("Lily");
        per.setId(199);
        System.out.println(per);
        
        //Dog对象因为在xml中,bean的子标签进行了赋值,可以直接获取值
        Dog dog = (Dog) ac.getBean("Dog");
        System.out.println(dog);
        
        //使用此方法获取对象时,要求Spring所管理的此类型的对象只能有一个,但是现在我们在配置文件中有两个Person类在被管理,所以此时报错。
        Person p2=ac.getBean(Person.class);
        System.out.println(p2);


        //最终:建议使用id+类名一起确定的方法,来确定具体的java对象
        Person pp = ac.getBean("Person2", Person.class);
        System.out.println(pp);

    }
}

以上具体报错见链接:报错——Exception in thread “main“ org.springframework.beans.factory.NoUniqueBeanDefinitionException:

我们可以看到,Spring可以对对象进行管理,无需我们自行创建对象了,这只是一个简单的小例子。

1.4 项目结构图

在这里插入图片描述

2、IOC容器和Bean的配置

2.1 IOC和DI

2.1.1 IOC(Inversion of Control),控制反转

IOC控制反转是一种思想:把原来程序员管理对象的权利反转给程序本身,让程序自己进行管理。类似于:我想吃饭,我要买菜自己做饭——转变为——我想吃饭,我点外卖。

  1. 控制反转是一种思想
  2. 依赖注入是一种设计模式
  3. IoC框架使用依赖注入作为实现控制反转的方式。

2.1.2 DI(Dependency Injection):依赖注入

DI是IOC的一种实现模式:组件以一些提前定义好的方法(比如说setter方法)接受来自容器的资源注入。

可以说:IOC是一种控制反转的思想,而DI是对IOC的一种具体实现。

比如说:汽车这个对象需要依赖于发动机对象、轮胎对象、方向盘对象。那么我们依赖谁就注入谁,注入就是赋值。如果按照传统方式,我们在汽车对象里new一个发动机对象、new一个轮胎对象、new一个方向盘对象,这就叫依赖。

2.1.3 IOC容器在Spring中的实现

前提:Spring中有IOC思想,IOC思想必须基于IOC容器来完成,而IOC容器最底层实际上就是一个对象工厂。

(1)在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。

(2)Spring提供了IOC容器的两种实现方式:

A. BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。

B.ApplicationContext:BeanFsactory的子接口,提供了更多的高级性能。面向Spring的使用者,几乎所有的场合都是用ApplicationContext而不是底层的BeanFactory。

2.1.4 ApplicationContext的主要实现类

(1)ClassPathXmlApplicationContext:
从类路径下的一个或多个xml配置文件中加载上下文定义,适用于xml配置的方式。

(2)FileSystemXmlApplicationContext:
从文件系统下的一个或多个xml配置文件中加载上下文定义,也就是说系统盘符中加载xml配置文件。

(3)在初始化时就创建单例的bean,也可以通过配置的方式指定创建的bean是多实例的。

2.1.5 ConfigurableApplicationContext

(1)是ApplicationContext的一个子接口,包含了一些扩展方法;

(2)refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。

2.1.6 WebApplicationContext

专门为Web应用而准备的,允许从相对于WEB根目录的路径中完成初始化工作。

可参考文章:理解Spring容器、BeanFactory和ApplicationContext

2.2 通过类型获取Bean

(1)从IOC容器中获取bean时,除了通过id获取,还可以通过bean的类型获取。但如果同一个类型的bean在xml文件中配置了多个,则在获取时会抛出异常,所以同一个类型的bean在容器中必须是唯一的。

Person p2=ac.getBean(Person.class);

(2)或者也可以通过另外一个重载的方法,同时指定bena的id和类型。

Person pp = ac.getBean("Person2", Person.class);

2.3 给bean的属性赋值

给bean的属性赋值其实就是依赖注入的过程。

2.3.1 依赖注入的方式

1、通过bean的setXxx()方法注入

2、通过构造方法注入

首先创建Student实体类。

package com.test02.di;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:12
 */
public class Student {
    private Integer id;
    private String name;
    private Integer age ;
    private  String sex;
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Student() {
    }
    public Student(Integer id, String name, Integer age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

通过配置文件,我们就可以看到通过set方法和通过构造方法进行依赖注入。

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

    <bean id="s1" class="com.test02.di.Student">
        <property name="id" value="10001"></property>
        <property name="name" value="王小妞"></property>
        <property name="age" value="16"></property>
        <property name="sex" value="女"></property>
    </bean>
    <bean id="s2" class="com.test02.di.Student">
        <constructor-arg index="0" value="1002"></constructor-arg>
        <constructor-arg index="1" value="宫小九"></constructor-arg>
        <constructor-arg index="2"  value="19"></constructor-arg>
        <constructor-arg index="3"  value="男"></constructor-arg>
    </bean>

</beans>

编写测试类。

package com.test02.di;

import com.test02.test.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:24
 */
public class StudentTest {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans-di.xml");

        Student s1 = ac.getBean("s1", Student.class);
        System.out.println(s1);

        Student s2 = ac.getBean("s2", Student.class);
        System.out.println(s2);

    }
}

2.3.2 p名称空间简化xml

为了简化xml文件的配置,可以通过元素属性的方式配置Bean的属性,我们使用p命名空间。这样基于xml的配置文件将进一步简化。见下面xml配置文件中的id为p3的bean的定义,简化程度一眼可以看出。

另外需要在命名空间引入:
xmlns:p=“http://www.springframework.org/schema/p”
这一句。

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="s1" class="com.test02.di.Student">
        <property name="id" value="10001"></property>
        <property name="name" value="王小妞"></property>
        <property name="age" value="16"></property>
        <property name="sex" value="女"></property>
    </bean>
    <bean id="s2" class="com.test02.di.Student">
        <constructor-arg index="0" value="1002"></constructor-arg>
        <constructor-arg index="1" value="宫小九"></constructor-arg>
        <constructor-arg index="2" value="19"></constructor-arg>
        <constructor-arg index="3" value="男"></constructor-arg>
    </bean>
    <!--使用p命名空间-->
    <bean id="s3" class="com.test02.di.Student" p:id="1003" p:name="欧阳三" p:age="12" p:sex="女"></bean>
</beans>

2.3.3 为属性赋值时可以使用的值

1、字面量
(1)可以使用字符串表示的值,可以通过value属性或者value子节点的方式指定;
(2)基本数据类型及其封装类,String等类型可以采用字面值注入的方式;
(3)如果字面值中包括特殊字符,可以使用<![CDATA[]]>把字面值包裹起来。

2、Null值

3、给bean的级联属性赋值
属性中包含属性,可以通过属性.属性进行赋值。

4、外部已声明的,引用其他的bean
通过ref来引用

    <!--定义一个teacher对象-->
    <bean id="teacher" class="com.test02.di.Teacher" p:id="1008" p:name="牛老师"></bean>
    <!--注入teacher对象-->
    <bean id="s4" class="com.test02.di.Student"  p:id="1003" p:name="欧阳三" p:age="12" p:sex="女" p:teacher-ref="teacher"></bean>

5、内部bean
当bean实例仅仅给一个特定的属性使用的时候,可以将其声明为内部bean。内部bean声明直接包含在property 或者constructor-arg 元素里,不需要设置任何id和属性。

内部bean不可以使用在任何其他地方。

    <bean id="s6" class="com.test02.di.Student">
        <property name="id" value="10066"></property>
        <property name="name" value="王大光"></property>
        <property name="age" value="16"></property>
        <property name="sex" value="女"></property>
        <property name="teacher">
            <!--定义在某个bean内部的bean,只能在该bean内部使用-->
            <bean id="tt" class="com.test02.di.Teacher">
                <property name="id" value="2988"></property>
                <property name="name" value="陈老师"></property>
            </bean>
        </property>
    </bean>

2.4集合属性

在Spring中可以通过一组内置的XML标签来配置集合属性,例如、、

2.4.1 数组和List

举例使用XML配置老师和学生的一对多关系,采用List。
1、Student类代码

package com.test02.di;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:12
 */
public class Student {

    private Integer id;
    private String name;
    private Integer age ;
    private  String sex;
    //private Teacher teacher;
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Student() {
    }
    public Student(Integer id, String name, Integer age, String sex) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
}

2、Teacher类代码

package com.test02.di;

import java.util.List;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 16:31
 */
public class Teacher {
    private Integer id;
    private String name;
    private List<String> classes;
    private List<Student> students;

    public Integer getId() {
        return id;
    }

    public List<Student> getStudents() {
        return students;
    }

    public List<String> getClasses() {
        return classes;
    }

    public void setClasses(List<String> classes) {
        this.classes = classes;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher() {
    }

    public Teacher(Integer id, String name, List<String> classes, List<Student> students) {
        this.id = id;
        this.name = name;
        this.classes = classes;
        this.students = students;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", classes=" + classes +
                ", students=" + students +
                '}';
    }

    public Teacher(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

3、配置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">
    <bean id="s1" class="com.test02.di.Student">
        <property name="id" value="451"></property>
        <property name="name" value="小王"></property>
        <property name="sex" value="男"></property>
        <property name="age" value="11"></property>
    </bean>

    <bean id="s2" class="com.test02.di.Student">
        <property name="id" value="452"></property>
        <property name="name" value="小刘"></property>
        <property name="sex" value="女"></property>
        <property name="age" value="16"></property>
    </bean>

    <bean id="t1" class="com.test02.di.Teacher">
        <property name="id" value="10001"></property>
        <property name="name" value="郝老师"></property>
        <property name="classes">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>
        <property name="students">
            <list>
                <ref bean="s1"></ref>
                <ref bean="s2"></ref>
            </list>
        </property>
    </bean>
</beans>

4、编写测试类

package com.test02.di;

import com.test02.test.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:24
 */
public class StudentTest {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("application_t.xml");
        Teacher s6 = ac.getBean("t1", Teacher.class);
        System.out.println(s6);
    }
}

5、结果输出

在这里插入图片描述

2.4.2 Map

1、Teacher类代码

package com.test02.di;

import java.util.List;
import java.util.Map;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 16:31
 */
public class Teacher {
    private Integer id;
    private String name;
    private List<String> classes;
    private List<Student> students;
    private Map<String,String> bossmap;

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", classes=" + classes +
                ", students=" + students +
                ", bossmap=" + bossmap +
                '}';
    }

    public Map<String, String> getBossmap() {
        return bossmap;
    }

    public void setBossmap(Map<String, String> bossmap) {
        this.bossmap = bossmap;
    }

    public Teacher(Integer id, String name, List<String> classes, List<Student> students, Map<String, String> bossmap) {
        this.id = id;
        this.name = name;
        this.classes = classes;
        this.students = students;
        this.bossmap = bossmap;
    }

    public Integer getId() {
        return id;
    }

    public List<Student> getStudents() {
        return students;
    }

    public List<String> getClasses() {
        return classes;
    }

    public void setClasses(List<String> classes) {
        this.classes = classes;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Teacher() {
    }

    public Teacher(Integer id, String name, List<String> classes, List<Student> students) {
        this.id = id;
        this.name = name;
        this.classes = classes;
        this.students = students;
    }

    public Teacher(Integer id, String name) {
        this.id = id;
        this.name = name;
    }
}

2、配置文件代码

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="s1" class="com.test02.di.Student">
        <property name="id" value="451"></property>
        <property name="name" value="小王"></property>
        <property name="sex" value="男"></property>
        <property name="age" value="11"></property>
    </bean>

    <bean id="s2" class="com.test02.di.Student">
        <property name="id" value="452"></property>
        <property name="name" value="小刘"></property>
        <property name="sex" value="女"></property>
        <property name="age" value="16"></property>
    </bean>

    <bean id="t1" class="com.test02.di.Teacher">
        <property name="id" value="10001"></property>
        <property name="name" value="郝老师"></property>
        <property name="classes">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>
        <property name="students">
            <list>
                <ref bean="s1"></ref>
                <ref bean="s2"></ref>
            </list>
        </property>
        <property name="bossmap">
            <map>
                <entry key="演员1" value="封腾"></entry>
                <entry key="演员2" value="薛杉杉"></entry>
                <entry key="演员3" value="翟一"></entry>
            </map>
        </property>
    </bean>
</beans>

3、测试类

package com.test02.di;

import com.test02.test.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:24
 */
public class StudentTest {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("application_t.xml");

        Teacher s6 = ac.getBean("t1", Teacher.class);
        System.out.println(s6);
    }
}

4、结果
在这里插入图片描述

2.4.3 集合类型的bean

当然我们也可以定义集合类型的bean,比如我们可以在xml定义一个list类型的bean。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 https://www.springframework.org/schema/util/spring-util.xsd">
    <bean id="s1" class="com.test02.di.Student">
        <property name="id" value="451"></property>
        <property name="name" value="小王"></property>
        <property name="sex" value="男"></property>
        <property name="age" value="11"></property>
    </bean>

    <bean id="s2" class="com.test02.di.Student">
        <property name="id" value="452"></property>
        <property name="name" value="小刘"></property>
        <property name="sex" value="女"></property>
        <property name="age" value="16"></property>
    </bean>
    
    <!--引用外部bean的方式给list赋值-->
    <bean id="t2" class="com.test02.di.Teacher">
        <property name="id" value="10002"></property>
        <property name="name" value="黄老师"></property>
        <property name="classes">
            <list>
                <value>1</value>
                <value>2</value>
                <value>3</value>
            </list>
        </property>
        <property name="students" ref="students"></property>
    </bean>

    <util:list id="students">
            <ref bean="s1"></ref>
            <ref bean="s2"></ref>
    </util:list>
</beans>

测试类如下:

package com.test02.di;

import com.test02.test.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/23 15:24
 */
public class StudentTest {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("application_t.xml");

        Teacher s6 = ac.getBean("t2", Teacher.class);
        System.out.println(s6);
    }
}

测试结果:
在这里插入图片描述

2.5 FactoryBean

Spring IOC实际上是工厂模式,我们不必关心bean是怎样创建的,需要的话你就直接去拿就好了。(AOP是代理模式实现的。)

Spring中有两种类型的bean,一种是普通bean,一种是工厂bean,即factorybean。

工厂bean和普通bean不同,返回的对象不是指定类的一个实例,返回的是该工厂bean的getObject方法所返回的对象。

工厂bean必须实现FactoryBean接口

1、创建car对象

package com.test02.factorybean;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:27
 */
public class Car {

    private String brand;

    private double price;

    public Car(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }

    public Car() {
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

2、创建工厂,实现FactoryBean接口

package com.test02.factorybean;

import org.springframework.beans.factory.FactoryBean;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:37
 */
public class MyFactory implements FactoryBean<Car> {

    public Car getObject() throws Exception {
        Car car=new Car();
        car.setBrand("奔驰");
        car.setPrice(999999.99);
        return car;
    }

    public Class<?> getObjectType() {
        return Car.class;
    }

    public boolean isSingleton() {
        return false;
    }
}

3、编写配置文件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">
    <!--factory_bean-->
<bean id="factory" class="com.test02.factorybean.MyFactory"></bean>

</beans>

4、测试类

package com.test02.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:54
 */
public class TestFactory {
    public static void main(String args[]) {
        ApplicationContext ac= new ClassPathXmlApplicationContext("factory_bean.xml");

       Object obj= ac.getBean("factory");
       System.out.println(obj);
    }

}

5、结果
在这里插入图片描述

根据结果可以看出:

工厂bean返回的对象不是指定类的一个实例,返回的是该工厂bean的getObject方法所返回的对象。

2.6 Bean的作用域

Spring中bean的作用域有四种。

作用域名称解释
singleton单例(默认)在整个应用中,只创建bean的一个实例
Prototype多例(原型)每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例
Request请求在web应用中,为每个请求创建一个bean实例
session会话在web应用中,为每个会话创建一个bean实例

Spring中默认bean的作用域是singleton,当然也可以通过scope属性进行设置。

首先看例子:

1、创建Car类。

package com.test02.factorybean;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:27
 */
public class Car {

    private String brand;

    private double price;

    public Car(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

//    @Override
//    public String toString() {
//        return "Car{" +
//                "brand='" + brand + '\'' +
//                ", price=" + price +
//                '}';
//    }

    public Car() {
        System.out.println("执行了一次Car的构造方法!");
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

2、配置类xml

没有设置scope,则默认为单例,可以通过输出结果看出。

<?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">
    <!--factory_bean-->
    <bean id="factory" class="com.test02.factorybean.MyFactory"></bean>

    <bean id="car1" class="com.test02.factorybean.Car" >
        <property name="brand" value="AAA"></property>
        <property name="price" value="999999.0"></property>
    </bean>

</beans>

3、测试类

package com.test02.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:54
 */
public class TestFactory {
    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("factory_bean.xml");

        Car obj1 = ac.getBean("car1", Car.class);
        Car obj2 = ac.getBean("car1", Car.class);
        System.out.println(obj1);
        System.out.println(obj2);
    }

}

4、结果
在这里插入图片描述
发现之创建了一个对象,获取的就是这一个对象。

接下来我们将bean的作用域改为原型模式(多例模式)。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--factory_bean-->
    <bean id="factory" class="com.test02.factorybean.MyFactory"></bean>

    <bean id="car1" class="com.test02.factorybean.Car" scope="prototype">
        <property name="brand" value="AAA"></property>
        <property name="price" value="999999.0"></property>
    </bean>

</beans>

再次启动测试类,结果如下:

在这里插入图片描述
就是多例模式了,创建了两次对象。

另外补充:

当Spring中有单例的bean时,在初始化的时候就会创建bean对象,多例即原型的bean在用到的时候才会被创建。

2.7 Bean的生命周期

1、Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。

2、Spring IOC容器对bean的生命周期进行管理的过程:

(1)创建对象:通过构造器或者工厂方法创建bean实例
(2)依赖注入:为bean的属性设置值和对其他bean的引用
(3)初始化:调用bean的初始化方法
(4)使用:bean可以使用了
(5)销毁:当容器关闭时,调用bean的销毁方法

下面将模拟Spring中bean的生命周期,分别放

Car类的代码(注意打印的内容以及位置)、

package com.test02.factorybean;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:27
 */
public class Car {

    private String brand;

    private double price;

    public Car(String brand, double price) {
        this.brand = brand;
        this.price = price;
    }

    @Override
    public String toString() {
        System.out.println("4:使用");
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }

    public Car() {
        System.out.println("1:创建对象");
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        System.out.println("2:依赖注入");
        this.brand = brand;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public  void init(){
        System.out.println("3:初始化");
    }

    public  void destory(){
        System.out.println("5:销毁");
    }
}

配置文件的代码(注意init-method和destory-method属性)、

<?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">
    <!--factory_bean-->
    <bean id="factory" class="com.test02.factorybean.MyFactory"></bean>

    <bean id="car1" class="com.test02.factorybean.Car" init-method="init" destroy-method="destory">
        <property name="brand" value="AAA"></property>
        <property name="price" value="999999.0"></property>
    </bean>

</beans>

测试类的代码

package com.test02.factorybean;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/24 14:54
 */
public class TestFactory {
    public static void main(String args[]) {
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("factory_bean.xml");
        Car obj1 = ac.getBean("car1", Car.class);
        System.out.println(obj1);;
        ac.close();//关闭容器
    }

}

以及结果
在这里插入图片描述

3、在配置bean时,通过init-method和destory-method属性为bean指定初始化和销毁方法。

4、bean的后置处理器

ben的生命周期:5步变成7步。

(1)bean的后置处理器调用初始化方法前后对bean进行额外的处理;

(2)Bean后置处理器对IOC容器里的所有bean实例逐一处理而不是单一实例;其典型应用是:检查bean属性的正确性或根据特定的标准更改bean的属性;

(3)Bean的后置处理器需要实现接口:BeanPostProcessor
实现该接口中的两个方法:postProcessBeforeInitialization(初始化之前执行)、postProcessAfterInitialization(初始化之后执行)

5、添加bean的后置处理器后的bean 的生命周期

(1)创建对象:通过构造器或者工厂方法创建bean实例;
(2)依赖注入:为bean的属性设置值和对其他bean的引用;
(3)初始化之前:将bean实例传递给后置处理器的postProcessBeforeInitialization方法;
(4)初始化:调用bean的初始化方法;
(5)初始化之后:将bean实例传递给后置处理器的postProcessAfterInitialization方法;

(6)使用:bean可以使用了;
(7)销毁:当容器关闭时执行bean的销毁方法。

对上面的代码使用后置处理器,需要手写后置处理器代码,并将后置处理器的bean交给Spring管理即可。

首先,手写后置处理器代码。

package com.test02.factorybean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 10:23
 */
public class AfterHandler implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之前");
        Car car = (Car) bean;
        if (car.getBrand().equals("AAA")) {
            car.setPrice(99.99);
        } else {
            car.setPrice(66.66);
        }
        return car;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("初始化之后");
        return bean;
    }
}

接着交给Spring容器管理该后置管理器,

<?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">
    <!--&lt;!&ndash;factory_bean&ndash;&gt;-->
    <!--<bean id="factory" class="com.test02.factorybean.MyFactory"></bean>-->

    <bean id="car1" class="com.test02.factorybean.Car" init-method="init" destroy-method="destory">
        <property name="brand" value="AAA"></property>
        <!--<property name="price" value="999999.0"></property>-->
    </bean>
    <bean class="com.test02.factorybean.AfterHandler"></bean>
</beans>

执行结果如下图所示:
在这里插入图片描述
后置处理器起作用,在初始化之前将Car对象的price设置为99.99了。

2.8 引用外部属性文件

当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。

这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引入properties属性文件中的内容,从而实现一部分属性值在发生变化的时候只需要修改properties属性文件即可。

这种技术多用于连接数据库的基本信息的配置。

2.8.1 直接配置

直接编写配置文件,配置项目数据库连接属性。创建配置文件代码如下:

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

    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="oracle.jdbc.OracleDriver"></property>
        <property name="url" value="你的url"></property>
        <property name="username" value="你的username"></property>
        <property name="password" value="你的password"></property>
    </bean>

</beans>

编写测试类:

package com.test02.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.test02.di.Teacher;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 13:34
 */
public class Test {
    public static void main(String args[]) throws  Exception {
        ApplicationContext ac = new ClassPathXmlApplicationContext("datasource.xml");
        DruidDataSource bean = ac.getBean("datasource", DruidDataSource.class);
        System.out.println(bean.getConnection());
    }
}

可见结果如下,表示已建立连接。

在这里插入图片描述

2.8.2使用外部的属性文件

创建db.properties属性文件。

# k=v
# 数据库配置
jdbc.driver=oracle.jdbc.OracleDriver
jdbc.url=***
jdbc.username=***
jdbc.password=***

然后在配置文件中引入properties属性文件,如下:

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

    <!--  加载资源文件方法一  -->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="db.properties"></property>
    </bean>
    <!--  加载资源文件方法二(方法一和方法二均可,方法二需要引入context命名空间)  -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>

    <bean id="datasource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

</beans>

结果与上面结果相同,只是采用外部属性文件的引入,从而实现一部分属性值在发生变化的时候只需要修改properties属性文件即可。

2.9 自动装配(自动注入)

2.9.1 自动装配的概念

(1)手动装配:以value或者ref的方式明确指定属性值都是手动装配。

(2)自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中。

注:autowire进行自动装配的属性必须是非字面量的属性(需要用到ref的属性),进行自动赋值。

Autowire:根据某种策略自动为非字面量属性赋值(byName:通过属性名和Spring中bean的id进行比较,若一致则可以直接赋值;byType:通过Spring中bean的类型,为兼容性的属性赋值,在使用byType的过程中,要求Spring容器中只能有一个能为属性赋值的bean)。

2.9.2 装配模式

首先看以下配置文件,emp中有car和dept类,进行了手动装配的配置。

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

    <bean id="emp" class="com.test02.factory.emp">
        <property name="eid" value="1001"></property>
        <property name="ename" value="小刘"></property>
        <property name="car" ref="car"></property>
        <property name="dept" ref="dept"></property>
    </bean>

    <bean id="car" class="com.test02.factory.Car">
        <property name="cid" value="101"></property>
        <property name="cname" value="大奔"></property>
    </bean>

    <bean id="dept" class="com.test02.factory.Dept">
        <property name="did" value="210001"></property>
        <property name="dname" value="办公室"></property>
    </bean>

</beans>

测试类代码:

package com.test02.factory;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 15:11
 */
public class test {
    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("factory_auto.xml");
        emp em = ac.getBean("emp", emp.class);
        System.out.println(em);
    }
}

输出结果为:

在这里插入图片描述

以下三种自动装配方式(根据类型自动装配、根据名称自动装配等),测试类的执行结果都和手动装配的结果相同。

(1)根据类型自动装配

设置autowire="byType”,则会根据属性的类型进行自动匹配。通过类型进行自动装配的时候,要考虑兼容性。而且配置文件中只能出现一个类型对应的bean。

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

    <bean id="emp" class="com.test02.factory.emp" autowire="byType">
        <property name="eid" value="1001"></property>
        <property name="ename" value="小刘"></property>
        <!--<property name="car" ref="car"></property>-->
        <!--<property name="dept" ref="dept"></property>-->
    </bean>

    <bean id="car" class="com.test02.factory.Car">
        <property name="cid" value="101"></property>
        <property name="cname" value="大奔"></property>
    </bean>

    <bean id="dept" class="com.test02.factory.Dept">
        <property name="did" value="210001"></property>
        <property name="dname" value="办公室"></property>
    </bean>

</beans>

(2)根据名称自动装配

设置autowire="byName”,则会根据属性的名称进行自动匹配。

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

    <bean id="emp" class="com.test02.factory.emp" autowire="byName">
        <property name="eid" value="1001"></property>
        <property name="ename" value="小刘"></property>
        <!--<property name="car" ref="car"></property>-->
        <!--<property name="dept" ref="dept"></property>-->
    </bean>

    <bean id="car" class="com.test02.factory.Car">
        <property name="cid" value="101"></property>
        <property name="cname" value="大奔"></property>
    </bean>

    <bean id="dept" class="com.test02.factory.Dept">
        <property name="did" value="210001"></property>
        <property name="dname" value="办公室"></property>
    </bean>

</beans>

(3)通过构造器装配:当bean中出现多个构造器时,此种自动装配方式会很复杂,不推荐使用。

2.9.3 兼容性

在根据类型进行自动装配的时候,要考虑兼容性。上面的代码中emp类的属性有:

private Integer eid;

private String ename;

private Car car;

private Dept dept;

那么当我们在配置文件中按照类型进行自动装配的时候,配置文件中类型是Car,那么emp中的对应类型也可以是Car的父类也可以是接口。使car继承carextends类,或者实现carextends接口。

private Integer eid;

private String ename;

private CarExtends car;

private Dept dept;

运行结果不变。

2.9.4 选用建议

使用xml文档进行自动装配的问题:设置autowire属性,会作用于bean中所有的非字面量属性。
相对于使用注解的方式实现的自动装配,在xml文档中进行的自动装配略显笨拙,在项目中更多地使用注解的方式实现。

2.10 通过注解配置bean

2.10.1 概述

相对于使用xml方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化的开发理念十分契合,是开发中常用的方式。

2.10.2 使用注解标识组件

加上注解的类就叫做组件。什么是Spring的组件?其实就是组成Spring的bean。

(1)普通组件:@Component
标识一个受Spring IOC容器管理的组件。

(2)持久化层组件:@Repository
标识一个受Spring IOC容器管理的持久化层组件。

(3)业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件。

(4)表示层控制器组件:@Controller
标识一个受Spring IOC容器管理的表示层控制器组件。

(5)组件命名规则:

A.默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id

B.使用组件注解的value属性指定bean的id
(事实上Spring并没有能力识别一个组件到底是不是他所标志的类型,及时将@Repository注解用在一个表述层控制器组件上面也不会产生任何错误,所以@Repository、@Service、@Controller这几个注解只是为了让开发人员自己明确当前的组件扮演的角色。)

以下为注解标识bean案例:

在这里插入图片描述
如上图所示项目结构,新建一个usermodule模块,分为controller、service、dao三个包,分别建立文件。代码如下:
1、Usercontroller,使用@Controller标识

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:54
 */
@Controller
public class UserController {
    /**
     * 构造函数
     */
    public  UserController(){
        System.out.println("UserController");
    }

}

2、UserService 接口

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:55
 */
public interface UserService {
}

3、UserServiceImpl 实现类,使用@Service标识

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:55
 */
@Service
public class UserServiceImpl implements UserService {
    public UserServiceImpl() {
        System.out.println("UserServiceImpl");
    }
}

4、UserDao 接口

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:56
 */
public interface UserDao {
}

5、UserDaoImpl 实现类,使用@Repository标识

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:57
 */
@Repository
public class UserDaoImpl implements UserDao {
    public UserDaoImpl() {
        System.out.println("UserDaoImpl");
    }
}

以上,我们已经使用注解标识了各个需要交给Spring管理的bean,但是Spring还不知道,所以我们需要有配置文件去告诉Spring。使用context:component-scan扫描组件。

如下,就告诉了Spring去扫描com.test02.usermodule包,将有注解的类作为组件去加载。

<?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-4.2.xsd">

    <!--context:component-scan :扫描组件,即在设置的包下面,将设置的包下面的类进行扫描,会将加上注解的类作为Spring的组件进行加载
    组件指:Spring中管理的bean
    作为Spring的组件进行加载:会自动在spring的配置文件中生成对应的bean,这些bean的id会以类的首字母小写为值
    -->
    <context:component-scan base-package="com.test02.usermodule"></context:component-scan>

</beans>

编写测试类:

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 17:20
 */
public class Test {
    
    public static void main(String args[]) {
        //加载Spring配置文件user.xml,此时已经扫描所有加了注解的类,并初始化
        ApplicationContext ac = new ClassPathXmlApplicationContext("user.xml");
        //以下是为了验证,会自动在spring的配置文件中生成对应的bean,这些bean的id会以类的首字母小写为值
        UserController uc = ac.getBean("userController", UserController.class);
        System.out.println(uc);
    }
}

执行结果:(打印了这三个加了注解的类的构造器内容)
在这里插入图片描述

2.10.3 扫描组件之排除和包含

之前我们扫描包是在xml文件中使用:

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

如果想多扫描可以加逗号再加上其他的包名,但是当我们需要扫描的包非常多的时候,要写的内容就很多了,当然你可以直接扫描com包?

但是扫描的文件越多会耗费很长的时间。所以我们需要对扫描组件的范围进行一个包含和排除的筛选限制。

怎么实现呢?

<?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-4.2.xsd">

    <!--context:component-scan :扫描组件,即在设置的包下面,将设置的包下面的类进行扫描,会将加上注解的类作为Spring的组件进行加载
    组件指:Spring中管理的bean
    作为Spring的组件进行加载:会自动在spring的配置文件中生成对应的bean,这些bean的id会以类的首字母小写为值
    -->
    <!--use-default-filters:默认的过滤条件(默认为true全扫描),
    使用包含时,一定设置use-default-filters="false"将默认过滤(即扫描包下所有的类)关闭;
    使用排除时,一定设置use-default-filters="true",将默认过滤(即扫描包下所有的类)打开-->
    <context:component-scan base-package="com.test02.usermodule" use-default-filters="false">
        <!--include-filter:扫描包含;exclude-filter:扫描排除-->
        <!--type="annotation"根据注解扫描,此处表示扫描包含所有的@Controller注解的类-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:include-filter>
        <!--type="assignable"根据类扫描,此处表示扫描UserDaoImpl类-->
        <context:exclude-filter type="assignable" expression="com.test02.usermodule.dao.daoimpl.UserDaoImpl"></context:exclude-filter>
    </context:component-scan>
</beans>

实际使用时。一个标签中可以同时出现多个包含include、可以同时出现多个排除exclude,但是不要包含和排除同时出现,逻辑混乱。

2.10.4 基于注解的自动装配

我们呢还是让Spring的配置文件扫描usermoule下所有的文件,创建一个addUser的方法。

一般我们会这样做,相关的五个文件如下。

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:54
 */
@Controller
public class UserController {

    private UserService userService = new UserServiceImpl();

    public void addUser() {
        userService.addUser();
    }

    /**
     * 构造函数
     */
    public UserController() {
        System.out.println("UserController");
    }

}

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:55
 */
public interface UserService {
  public void addUser();
}
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:55
 */
@Service
public class UserServiceImpl implements UserService {
private UserDao userDao=new UserDaoImpl();

    public void addUser() {
        userDao.addUser();
    }
    public UserServiceImpl() {
        System.out.println("UserServiceImpl");
    }
}
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:56
 */
public interface UserDao {
    void addUser();
}
/**
 * @author zll
 * @version 1.0
 * @date 2020/7/29 16:57
 */
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void addUser() {
        System.out.println("增加用户信息成功!!!");
    }
    public UserDaoImpl() {
        System.out.println("UserDaoImpl");
    }
}

在测试类调用addUser方法。

public class Test {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("user.xml");
        UserController uc = ac.getBean("userController", UserController.class);
        System.out.println(uc);
        uc.addUser();
    }
}

执行结果:

在这里插入图片描述
我们可以看出,在Spring容器初始化的时候,执行了一遍三层的构造方法,在调用的时候,又执行了一遍,说明创建了两遍对象。

下面我们使用注解进行自动装配:

使用@Autowired注解进行自动装配。

    @Autowired
    private UserService userService ;

取代下面这句创建对象进行应用。

  private UserService userService = new UserServiceImpl();

另外几个类同样使用@Autowired注解交由Spring容器进行管理,进行自动装配。

从以下执行结果可以看出,三个类是交给Spring自动装配了,构造方法也是只执行了一次,完全由Spring进行管理,无需我们自己创建对象。
在这里插入图片描述
那么思考一下,使用@Autowired注解进行自动装配是我们在配置文件中设置自动装配的byName(通过属性名)还是byType(通过属性类型)呢?

byType!

如果是byName的话,要求Spring的容器内有一个名字为userService的bean,然而并没有。此时Spring中的bean只有userController、userServiceImpl、userDaoImpl。(Spring的组件进行加载:会自动在spring的配置文件中生成对应的bean,这些bean的id会以类的首字母小写为值)。所以是byType。

但是,Spring的容器中如果有多个相同类型的bean,byType失效,Spring会自动寻求byName。

(如果没有给UserServiceImpl加@Service注解的话,那么Spring容器中就找不到这个bean,就会报错:NoSuchBeanDefinitionException)

另外@Controller(value = “user”),我们可以对controller进行value的设置,此时在Spring中管理的bean的名字就叫user了,否则为默认的userController。

2.10.5 小结——基于注解的组件化管理

@Component、@Controller、@Service、@Repository,以上四个注解功能完全相同,但是在实际开发中,要在实现不同功能的类上加上相应的注解。

完成组件化管理的过程:

1、在需要Spring管理的类上加上相应注解

2、在配置文件中通过context:component-scan 对所在的包结构进行扫描,就会将加上注解的类,作为spring的组件进行加载。

组件:指的是Spring中管理的bean。

作为Spring的组件进行加载:会自动在spring的配置文件中生成对应的bean,这些bean的id会以类的首字母小写为值,也可以通过@Controller(value=“beanId”)这种注解+value的形式指定bean的id。

**自动装配:**在需要赋值的非字面量属性上,加上@Autowired,就可以在Spring容器中,通过不同的方式匹配到相对应的bean(默认优先使用byType的方式,此时要求Sspring中只有一个能够为其赋值的bean;当byType不能满足装配时,会自动切换到byName方式,此时要求Spring容器中必须有唯一一个bean的id和属性名一致)。

若自动装配时,匹配到多个能够赋值的bean,可使用@Qualifier(value=”bean的名字”)指定使用的bean。

@Autowired和@Qualifier(value=”bean的名字”)可以一起作用于一个带形参的方法上,此时,@Qualifier(value=”bean的名字”)所指定的bean作用于形参。

(另外:java自己提供了@Resource注解,他的作用和@Autowired是一样的,不同点就是@Autowired是先默认byType而@Resource是先默认byName方式装配。)

3、AOP前奏

IOC是工厂模式、AOP是代理模式。

案例题目

设计一个小计算器,实现加减乘除的功能,并实现日志输出。

3.1 计算器小案例

1、定义计算器接口

public interface MathInter {
    int add(int i, int j);
    int sub(int i, int j);
    int mul(int i, int j);
    int div(int i, int j);
}

2、定义计算方法实现类(包含日志输出功能)

public class MathImpl implements MathInter {
    @Override
    public int add(int i, int j) {
        System.out.println("method:add,arguments:" + i + "," + j);
        int result = i + j;
        System.out.println("method:add,result:" + result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("method:sub,arguments:" + i + "," + j);
        int result = i - j;
        System.out.println("method:sub,result:" + result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("method:mul,arguments:" + i + "," + j);
        int result = i * j;
        System.out.println("method:mul,result:" + result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("method:div,arguments:" + i + "," + j);
        int result = i / j;
        System.out.println("method:div,result:" + result);
        return result;
    }
}

3、定义测试类

public class Test {
    public static void main(String args[]) {
        MathInter math = new MathImpl();
        int result1 = math.add(3, 7);
        int result2 = math.sub(9, 3);
        int result3 = math.mul(4, 3);
        int result4 = math.div(12, 3);
    }
}

执行结果:

在这里插入图片描述

这样子,代码中日志和逻辑团在一起,代码十分混乱。那么我们使用动态代理对象来实现。

3.2 计算器小案例动态代理改进

1、首先赶紧删掉计算器实现类中的一堆System.out吧。

2、定义我的日志类。

public class MyLogger {
    public static void before(String methodName, String args) {
        System.out.println("methodName: " + methodName + ", arguments: " + args);
    }
    public static void after(String methodName, Object result) {
        System.out.println("methodName: " + methodName + ", result: " + result);
    }
}

3、编写创建动态代理对象的类。

public class ProxyUtil {
    //定义目标对象,一般是要写Object,这里为了更好理解使用MathImpl
    private MathImpl mathImpl;

    /**
     * 有参构造
     * @param mathImpl
     */
    public ProxyUtil(MathImpl mathImpl) {
        this.mathImpl = mathImpl;
    }

    public Object getProxy() {
        //获取当前类的类加载器,用来加载代理对象所属类
        ClassLoader loader = this.getClass().getClassLoader();
        //得到目标对象实现的所有的接口的class,代理对象和目标类实现相同的接口,代理对象就获得了目标对象要实现的所有的方法
        Class[] interfaces = mathImpl.getClass().getInterfaces();
        //创建代理对象!!!InvocationHandler 是用来控制代理对象实现功能的方法,是一个控制器
        return Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
            //代理对象实现功能的方式。
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                MyLogger.before(method.getName(), Arrays.toString(args));
                Object result = method.invoke(mathImpl, args);//动态代理对象实现功能
                System.out.println(result);
                MyLogger.after(method.getName(),result);
                return result;
            }
        });
    }
    public ProxyUtil() {
    }
}

4、测试类中调用,使用动态代理执行,并输出日志。

public class Test {
    public static void main(String args[]) {
        ProxyUtil proxy = new ProxyUtil(new MathImpl());
        MathInter proxy2=(MathInter)proxy.getProxy();
        proxy2.add(3,9);
        proxy2.div(15,3);
    }
}

输出结果如下:
在这里插入图片描述
可见,应用了动态代理之后,可以动态控制日志的输出了。

4、AOP概述

4.1 AOP概述

1、AOP(面向切面编程):是一种新的方法论,是对于传统OOP(面向对象编程)的补充。

面向对象:纵向继承机制;
面向切面:横向抽取机制。

在这里插入图片描述

2、AOP的主要操作对象是切面,而切面应用于模块化横切关注点(公共功能)。

3、在应用AOP编程时,仍然需要定义公共功能,但是可以明确定义这个功能应该应用在哪里,以什么方式应用,并且不必修改影响的类。这样一来,横切关注点就被模块化到特殊的类里——这样的类我们称之为切面。(什么叫切面?来存储公共功能的类就叫做切面,比如说上面案例的MyLogger类就是一个切面。)

4、AOP的好处:
(1)每个事务逻辑位于一个位置,代码不分散,便于维护和升级;
(2)业务模块更简洁,只包含核心业务代码;
(3)AOP图解:


如下图所示:本来我们在计算器的每个方法之前都需要进行验证参数、前置日志、方法执行、后置日志的操作。

但是我们抽取横切关注点,通过AOP面向切面编程的思想,将上述过程形成一个切面(类),可以实现验证和日志的功能,从而也使得业务逻辑相对独立简洁。这就是AOP的思想。

在这里插入图片描述

4.2 AOP术语

4.2.1 横切关注点

从每个方法中抽取出来的同一类非核心业务。

4.2.2 切面(Aspect)

封装横切关注点信息的类,每个关注点体现为一个通知方法。

4.2.3 通知(Advice)

切面必须完成的各个具体工作。

4.2.4 目标(Target)

被通知的对象。

4.2.5 代理(Proxy)

向目标对象应用通知之后创建的代理对象。

4.2.6 连接点(Joinpoint)

横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:在某个方法调用前、调用后、方法捕捉到异常后等。

4.2.7 切入点(pointcut)

定位连接点的方式。每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物。

如果把连接点看作数据库中的记录,那么切入点就是查询条件——AOP可以通过切入点定位到特定的连接点。切点通过aop.PointCut接口进行描述,他通过类和方法作为连接点的查询条件。

4.3 AspectJ框架(基于注解的AOP)

4.3.1 简介

AspectJ:Java社区中最完整最流行的AOP框架。
在Spring2.0以上的版本中,可以使用基于AspectJ注解或者基于xml配置的AOP。

4.3.2 在Spring中启用AspectJ注解支持

Spring会自动生成动态代理类,实现AOP。

5、AOP细节

5.1 切入点表达式

5.1.1 作用
5.1.2 语法细节
5.1.3 应用

5.2 当前连接点细节(JoinPoint)

5.2.1 概述
5.2.2 JoinPoint

5.3 通知(5类通知)

5.3.1概述
5.3.2 前置通知
5.3.3 后置通知
5.3.4 返回通知
5.3.5 异常通知
5.3.6 环绕通知

5.4 重用切入点定义(@PointCut)

5.5 指定切面的优先级 (@Order)

5.6 以上知识点的案例应用

我们要做什么?

就是抽取与业务无关的公共方法,并应用AOP实现切面插入业务逻辑中。我们需要应用AspectJ框架通过注解来实现。

1、首先pom文件的一些依赖。

    <dependencies>
        <!-- spring aop支持 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <!--AspectJ-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.bundles</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8_2</version>
        </dependency>
        <!--oracle驱动-->
        <dependency>
            <groupId>com.oracle.ojdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>19.3.0.0</version>
        </dependency>
        <!--druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>
        <!--log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2、MathInter接口和MathImpl实现类的代码和之前的一样。

MathImpl实现类需要加上注解:
@Component
//交由Spring进行管理,切面才知道应该作用于谁

我们要实现的就是在MathImpl实现类的方法执行中插入日志。所以我们定义一个切面类,实现了上述功能,体现了以上的知识点,好好看代码。

/**
 * @author zll
 * @version 1.0
 * 切面(五种通知)
 * @date 2020/7/31 10:58
 */
@Component
@Aspect
@Order(1)
//@Aspect注解作用:标注当前类为切面
//必须加上@Component注解,给Spring的IOC容器去管理,才可以让Spring识别这是一个切面
//@Order注解:定义切面的优先级(正整数,默认值为int的最大值)。如果有多个切面,数字越小的优先级越高。
public class MyLoggerAspect {

    /**
     * 切入点重用
     */
    @Pointcut(value = "execution(*  com.test02.aop.*.*(..))")
    public  void test(){
    }

    /**
     * @Before: 将方法指定为前置通知,必须设置value,值为*切入点表达式*
     * 作用于方法执行之前
     */
    //@Before(value = "execution(public int com.test02.aop.MathImpl.add(int,int))")
    @Before(value = "test()")//使用test方法的切入点表达式
    public void beforeMethod(JoinPoint joinpoint) {
        Object[] args = joinpoint.getArgs();//获取方法的参数
        String methodName = joinpoint.getSignature().getName();//获取方法名
        System.out.println("method:  " + methodName + " , arguments:   " + Arrays.toString(args));
    }


    /**
     * 后置通知,作用于方法执行后,是在finally语句块里的,不管有无异常一定会被执行
     *
     * @param joinPoint
     */
    @After(value = "execution(* com.test02.aop.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();//获取方法名
        System.out.println("method:  " + methodName + " , result:   这是后置通知!!!");
    }


    /**
     * 返回通知:有异常的话不执行,作用于方法返回之后。
     * 可以通过returning设置接收方法返回值的变量名
     * 要想在方法中使用,必须在方法的形参中设置和变量名相同的参数名的参数
     *
     * @param joinPoint
     */
    @AfterReturning(value = "execution(* com.test02.aop.*.*(..))", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();//获取方法名
        System.out.println("method:  " + methodName + " , result:   这是返回通知!!!  result: " + result);
    }

    /**
     * @param ex
     * @AfterThrowing异常通知: 将方法标注为异常通知,作用于当方法抛出异常时
     * 可以通过throwing设置接收方法返回的异常信息
     * 在参数列表中,可以通过具体的异常类型,对指定的异常进行操作。
     */
    @AfterThrowing(value = "execution(* com.test02.aop.*.*(..))", throwing = "ex")
    public void afterthrowing(Exception ex) {
        System.out.println("有异常了。messages:  " + ex);
    }

    /**
     * @Around环绕通知: 在环绕通知,可以控制方法的执行。可以和其他通知共同进行。
     */
    @Around(value = "execution(* com.test02.aop.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            //前置通知
            System.out.println("前置通知");
            result = joinPoint.proceed();//执行方法
            //后置返回通知
            System.out.println("后置返回通知");
            return result;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            //异常通知
            System.out.println("异常通知");
        } finally {
            //后置通知
            System.out.println("后置通知");
        }
        return -1;
    }
}

3、Spring的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:aop="http://www.springframework.org/schema/aop"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启AspectJ的自动代理功能-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--扫描组件-->
    <context:component-scan base-package="com.test02.aop"></context:component-scan>

</beans>

4、测试类代码

public class test {
    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop.xml");
        MathInter math = ac.getBean("mathImpl", MathInter.class);
        int result = math.add(3, 0);
        System.out.println("执行结果: "+result);
    }
}

这样我们就把日志的功能横向抽取出来为一个切面,并横向插入到业务中去,实现了AOP的思想。原理还是动态代理,Spring会自动生成代理类完成AOP的功能。

6、以XML的方式配置切面

当然MathImpl实现类依旧需要加@Compenont注解,交给Spring容器管理。现在我们不使用任何AspectJ的注解,就要在Spring的xml配置文件中及逆行AOP切面的配置了。
1、我们写一个简单的切面

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/31 15:52
 */
@Component
public class MyLogger {

    public void before() {
        System.out.println("这是前置通知!!!");
    }
}

2、Spring的xml配置文件,通过 < aop:config >标签来配置AOP切面,设置切面的方法的切入点表达式等

<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--扫描组件-->
    <context:component-scan base-package="com.test02.aopxml"></context:component-scan>

    <!--配置AOP切面-->
    <aop:config>
        <aop:aspect ref="myLogger">
            <aop:pointcut id="cut" expression="execution(* com.test02.aopxml.*.* (..))"></aop:pointcut>
            <aop:before method="before" pointcut-ref="cut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>

3、测试类代码

/**
 * @author zll
 * @version 1.0
 * @date 2020/7/31 16:01
 */
public class test {

    public static void main(String args[]) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("aop_xml.xml");
        MathInter math = ac.getBean("mathImpl", MathInter.class);
        math.add(1, 8);
    }
}

内容已经太长了,本篇主要讲Spring的IOC和AOP。

Spring的JDBCTemplete以及事务的知识,请见下篇。
Spring——Spring学习教程(详细)(下篇)——JDBCTemplete、事务

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值