Spring之IOC容器的基础知识

学习目标

1、掌握ioc理论基础
2、熟练使用spring创建bean
3、掌握依赖注入的原理和使用
4、掌握AOP概念和应用使用

Spring之IOC容器的基础知识

历史背景

​ 要谈Spring的历史,就要先谈J2EE。J2EE应用程序的广泛实现是在1999年和2000年开始的,它的出现带来了诸如事务管理之类的核心中间层概念的标准化,但是在实践中并没有获得绝对的成功,因为开发效率,开发难度和实际的性能都令人失望。

当时程序员使用EJB开发JAVA EE应用复杂和难以使用,并且产生开发效率低,极高的资源消耗等缺点。EJB的学习要严格地实现各种不同类型的接口,类似的或者重复的代码大量存在。而配置也是复杂和单调,同样使用JNDI进行对象查找的代码也是单调而枯燥。虽然有一些开发工作随着xdoclet的出现,而有所缓解,但是学习EJB的高昂代价,和极低的开发效率,极高的资源消耗,都造成了EJB的使用困难。而Spring出现的初衷就是为了解决类似的这些问题

目的:

  • 1、使JAVA EE开发更简单容易。

  • 2、使用接口而不是使用类,是更好的编程习惯。Spring将使用接口的复杂度几乎降低到了零。

  • 3、为JavaBean提供了一个更好的应用配置框架。

  • 4、更多地强调面向对象的设计,而不是现行的技术如JAVA EE。

  • 5、尽量减少不必要的异常捕捉。

  • 6、使应用程序更加容易测试。

1 spring介绍

(特点,架构,核心模块)

Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。

官网文档:https://docs.spring.io/spring-framework/docs/current/reference/html/index.html

1.1 优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架
  • 支持事务的处理,对框架整合的支持!
  • 面向接口编程,而不是针对类编程。Spring将使用接口的复杂度降低到零。
  • JavaBean提供了应用程序配置的最好方法。

1.2 组成

在这里插入图片描述

Spring框架由七个定义明确的模块组成:

​ 就像你所看到的,所有的Spring模块都是在核心容器之上构建的。容器定义了Bean是如何创建、配置和管理的——更多的Spring细节。如果作为一个整体,这些模块为你提供了开发企业应用所需的一切。但你不必将应用完全基于Spring框架。你可以自由地挑选适合你的应用的模块而忽略其余的模块。

核心容器(Spring Core)

这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。这里最基本的概念是BeanFactory,它是任何Spring应用的核心。BeanFactory是工厂模式的一个实现,它使用IoC将应用配置和依赖说明从实际的应用代码中分离出来。

应用上下文(Spring Context)

核心模块的BeanFactory使Spring成为一个容器,而上下文模块使它成为一个框架。这个模块扩展了BeanFactory的概念,增加了对国际化(I18N)消息、事件传播以及验证的支持。

​ 另外,这个模块提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。也包括了对模版框架例如Velocity和FreeMarker集成的支持。

Spring的AOP模块

​ Spring在它的AOP模块中提供了对面向切面编程的丰富支持。这个模块是在Spring应用中实现切面编程的基础。为了确保Spring与其它AOP框架的互用性,Spring的AOP支持基于AOP联盟定义的API。AOP联盟是一个开源项目,它的目标是通过定义一组共同的接口和组件来促进AOP的使用以及不同的AOP实现之间的互用性。通过访问他们的站点,你可以找到关于AOP联盟的更多内容。

JDBC抽象和DAO模块

​ 使用JDBC经常导致大量的重复代码,取得连接、创建语句、处理结果集,然后关闭连接。Spring的JDBC和DAO模块抽取了这些重复代码,因此你可以保持你的数据库访问代码干净简洁,并且可以防止因关闭数据库资源失败而引起的问题

​ 这个模块还在几种数据库服务器给出的错误消息之上建立了一个有意义的异常层。使你不用再试图破译神秘的私有的SQL错误消息!

​ 另外,这个模块还使用了Spring的AOP模块为Spring应用中的对象提供了事务管理服务

对象/关系映射集成(Spring ORM)

​ 对那些更喜欢使用对象/关系映射工具而不是直接使用JDBC的人,Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,包括Hibernate、JDO和iBATIS SQL映射。Spring的事务管理支持这些ORM框架中的每一个也包括JDBC。

Spring的Web模块

​ Web上下文模块建立于应用上下文模块之上,提供了一个适合于Web应用的上下文。另外,这个模块还提供了一些面向服务支持。例如:实现文件上传的multipart请求,它也提供了Spring和其它Web框架的集成,比如Struts、WebWork。

Spring的MVC框架

​ Spring为构建Web应用提供了一个功能全面的MVC框架。虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离

​ 它也允许你声明性地将请求参数绑定到你的业务对象中,此外,Spring的MVC框架还可以利用Spring的任何其它服务,例如国际化信息与验证。

Spring框架Web页面乱码问题

​ 在做java Web 项目时,乱码问题时常都会出现,解决方法也不尽相同,有简单也有复杂的;如果加入了Spring框架之后就不一样了,可以采用Spring框架自带的过滤器CharacterEncodingFilter,这样可以大大减轻了我们的工作量,即简单方便又容易理解。

spring的核心模块:
- spring-core:依赖注入IOC与DI的最基本实现
- spring-beans:Bean工厂与bean的装配
- spring-context:spring的context上下文即IoC容器
- spring-context-support
- spring-expression:spring表达式语言

2 ioc理论指导

2.1 用例说明

​ 在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改源代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!

以前调用方式:

​ User实体类

public class User {
    private String name;
    private Integer id;
    private Integer age;
    private String password;

}

​ 1.UserDao 接口

public interface UserDao {
    public User selectUser(int id);
}

​ 2.UserDaoMysqlImpl实现类–Mysql查询方式

public class UserDaoMysqlImpl  implements UserDao {
    public User selectUser(int id) {
        System.out.println("dao------Mysql查询用户");
        return new User();
    }
}

​ 3.UserDaoOracleImpl实现类 —Oracle查询方式

public class UserDaoOracleImpl implements UserDao {
    public User selectUser(int id) {
        System.out.println("dao------Oracle查询用户");
        return new User();
    }
}	

3.UserService 业务接口

public interface UserService {
    public User selectUser(int id);
}

​ 4.UserServiceImpl 业务实现类

public class UserServiceImpl  implements UserService {
    private UserDao userDao = new UserDaoImpl();
    public User selectUser(int id) {
        System.out.println("service------查询用户");
        return userDao.selectUser(id);
    }
}

​ 5.测试

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        userService.selectUser(1);
    }
}

​ 结果:

service------查询用户
dao------Mysql查询用户

原先的使用方式需要在service实体类中通过new dao层实体类而达到调用的目的,但当需要修改数据库实体类的时候,就需要去修改源码,控制权在程序员手中

//------若调用mysql实体类,serviceImpl中则修改如下
private Userdao userDao = new UserDaoMysqlImpl();
//-----若调用oracle实体类,serviceImpl中则修改如下
private Userdao userDao = new UserDaoMysqlImpl();

下面使用set接口,不需要修改源码,通过注入bean达到调用接口的方式,将控制权给予到调用者手中(发生了革命性的变化!):

public class UserServiceImpl  implements UserService {
    private UserDao userDao ;
    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }
    public User selectUser(int id) {
        System.out.println("service------查询用户");
        return userDao.selectUser(id);
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        //------调用mysql实体类
        ((UserServiceImpl)userService).setUserDao(new UserDaoMysqlImpl());
        userService.selectUser(1);
        //-----调用oracle实体类
        ((UserServiceImpl)userService).setUserDao(new UserDaoOracleImpl());
        userService.selectUser(1);

    }
}

结果:

service------查询用户
dao------Mysql查询用户
service------查询用户
dao------Oracle查询用户
  • 之前,程序是主动创建对象,控制权在程序员手上

  • 使用set注入后,程序不再具有主动性,而是变成了被动的接受对象

这就是Ioc思想。

这种思想,从本质上解决了大量修改问题,我们程序员不需要再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上。这是IOC的原型。

2.2 IOC本质

控制反转IOC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IOC的一种方法,也有人认为DI只是IOC的另一种说法。没有ioc的程序中,我们使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。

​ 采用xml方式配置Bean的时候,Bean的定义信息和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到零配置的母的。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在spring中实现控制反转的是Ioc容器,其他实现方法是依赖驻日(Dependency Injection,DI)。

耦合度过程图解:
在这里插入图片描述

理解:早期为了耦合对象降低解耦性,使用了ioc容器作为中间件,通过ioc去调用某个对象,其后发展到不依赖中间件,自动注入对象。

3 Hello Spring

ioc理论指导的用例类基础上:

3.1 pom.xml导入spring依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>
    </dependencies>

3.2 spring配置文件

创建bean.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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

<!--bean标签:
    id: 标识单个bean的字符串
    class: 定义bean的类型,并使用全路径
	property:bean的属性设置
    ref: 引用spring容器中创建好的对象
    value: 具体的值,基本数据类型!
-->
    <bean id="mysqlImpl" class="com.innocence.dao.impl.UserDaoMysqlImpl"></bean>
    <bean id="oracleImpl" class="com.innocence.dao.impl.UserDaoOracleImpl"></bean>
    <!--service 需要调用dao层-->
    <bean id="userServiceImpl" class="com.innocence.service.impl.UserServiceImpl">
        <property name="userDao" ref="mysqlImpl"></property>
    </bean>
    <bean id="user" class="com.innocence.pojo.User"> </bean>
</beans>

3.3 运行

使用spring官网提供的获取bean方式

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.selectUser(1);

    }
}

结果:

service------查询用户
dao------Mysql查询用户

小结:

  • 配置文件怎么创建对象的(spring创建的,根据配置文件中的id-变量名,class-类,propetory-属性,propetory赋值依赖类中的set方法。)

  • springContext跟Servlet的ServletContext类似有资料共享的功能。通过配置文件创建对象后放入ioc容器,需要什么去get即可。

4 IOC容器

​ 本章介绍控制反转 (IoC) 原则的 Spring 框架实现。IoC 也称为依赖注入 (DI)。在此过程中,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象实例后在对象实例上设置的属性来定义其依赖项(即它们使用的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。这个过程基本上是Bean本身的反转(因此名称为Inversion of Control),通过使用类的直接构造或服务定位器模式等机制来控制其依赖关系的实例化或位置

​ 包是Spring Framework的IoC容器的基础。[ BeanFactory]接口提供了一种能够管理任何类型的对象的高级配置机制。

他的优点:

  • 更容易与Spring的AOP功能集成

  • 消息资源处理(用于国际化)

  • 事件发布

  • 特定于应用程序层的上下文,例如在 Web 应用程序中使用的 上下文。WebApplicationContext

在 Spring 中,构成应用程序主干并由 Spring IoC 容器管理的对象称为 bean。Bean 是由 Spring IoC 容器实例化、组装和管理的对象。否则,Bean 只是应用程序中的众多对象之一。Bean 及其之间的依赖关系反映在容器使用的配置元数据中

4.1 容器概述

Spring IoC 容器负责实例化、配置和组装 Bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的说明。配置元数据以 XML、Java 注释或 Java 代码表示。它允许您表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。

该接口的几种实现随Spring一起提供。在独立应用程序中,通常创建[ClassPathXmlApplicationContext]或[FileSystemXmlApplicationContext]的实例。虽然 XML 一直是定义配置元数据的传统格式,但您可以通过提供少量 XML 配置来声明性地启用对这些附加元数据格式的支持,从而指示容器使用 Java 注释或代码作为元数据格式。

下图显示了Spring工作原理的高级视图。应用程序类与配置元数据相结合,以便在创建和初始化 后,您拥有一个完全配置且可执行的系统或应用程序。
在这里插入图片描述

4.1.1 配置元数据

如上图所示,Spring IoC 容器使用某种形式的配置元数据。此配置元数据表示作为应用程序开发人员,您如何告诉 Spring 容器在应用程序中实例化、配置和组装对象。

配置元数据的三种方式

  • xml格式:传统上以简单直观的 XML 格式提供

  • 基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。

  • 基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能成为Spring框架核心的一部分。因此,您可以使用 Java 而不是 XML 文件来定义应用程序类外部的 Bean。要使用这些新功能,请参阅@Configuration 、@Bean、@Import@DependsOn注释。

4.1.2 基于 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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="唯一标识" class="bean类的全路径"></bean>
</beans>

当Bean 定义跨越多个 XML 文件如何使用呢?通常,每个单独的 XML 配置文件都表示体系结构中的一个逻辑层或模块。这时可以使用Import 导入所需的 XML 片段加载配置定义。

<beans>
    <import resource="others.xml"/>
</beans>

注意:所有位置路径对于执行import导入的定义文件都是相对的,因此必须与执行导入的文件位于同一目录或类路径位置,而 并且必须位于导入文件位置下方的位置。

4.2.3 实例化容器

提供给构造函数的位置路径是资源字符串,允许容器从各种外部资源(如本地文件系统、Java 等)加载配置元数据

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

注:有需要的话可以加载多个配置文件

ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml", "daos.xml");
4.2.4 使用容器

读取bean的配置并获取他们:

// 创建和配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");

// 检索配置实例
UserService userService = context.getBean("userServiceImpl", UserService.class);

// 使用
userService.selectUser(1);

4.2 bean概述

Spring IoC 容器管理一个或多个 Bean。这些 Bean 是使用您提供给容器的配置元数据(例如,以 XML 定义的形式)创建的。在容器本身中,这些 Bean 定义表示为对象。

4.2.1 bean的别名

在 Bean 定义本身中,可以通过使用属性指定的最多一个名称和属性中任意数量的其他名称的组合,为 Bean 提供多个名称。这些名称可以是同一 Bean 的等效别名,并且在某些情况下很有用。在基于 XML 的配置元数据中,可以使用alias来实现。

<alias name="userService" alias="userServiceAlias"/>

bean命名约定:约定是在命名 bean 时使用标准 Java 约定来命名字段名称。也就是说,bean名称以小写字母开头,采用驼峰式命名。

4.2.2 bean标签属性

当我们使用配置文件xxx.xml定义bean的时候,会看到如下的属性,对属性的了解更方便我们进行使用。
在这里插入图片描述

属性说明
class指定bean对应类的全路径
namename是bean对应对象的一个标识
scope执行bean对象创建模式和生命周期,scope="singleton"和scope=“prototype”
idid是bean对象的唯一标识,不能添加特殊字符
lazy-init是否延时加载 默认值:false。true 延迟加载对象,当对象被调用的时候才会加载,测试 的时候,通过getbean()方法获得对象。lazy-init=“false” 默认值,不延迟,无论对象 是否被使用,都会立即创建对象,测试时只需要加载配置文件即可。注意:测试的时候 只留下id,class属性
init-method只需要加载配置文件即可对象初始化方法
destroy-method对象销毁方法
autowire自动装配bean。可选择通过byName或者byType来装配bean对象。
factory-method通过工厂类方法创建Bean。
用于调用静态方法以在工厂类中创建bean对象。
factory-bean实例化工厂类。若非静态方法必须实例化工厂类之后才能创建bean。
4.2.3 实例化bean

创建对象的方式:无参构造,有参构造,静态工厂方法factory-method,实例工厂方法factory-bean)

1)构造函数

当通过构造函数方法创建 Bean 时,一般有两种方式,无参构造(默认)和有参构造。

使用基于 XML 的配置元数据,无参构造按如下方式指定 Bean :

<bean id="exampleBean" class="examples.ExampleBean"/>

有关向构造函数提供参数(如果需要)和在构造对象后设置对象实例属性的机制的详细信息,请参阅4.3.1 章节 依赖注入

2)使用静态工厂方法进行实例化

当使用静态工厂方法创建的 Bean 时,使用属性指定工厂方法的类,并指定工厂方法名称。通过调用此方法返回一个活动对象。

以下 是通过调用工厂方法创建 Bean的示例。xml配置中不指定返回对象的类型(类),仅指定包含工厂方法的类。在此静态工厂实例化中,该方法必须是静态方法

public class PersonStaticFactory {
	public static Person createPerson(){
		System.out.println("静态工厂创建Person");
		return new Person();
	}
}
<bean name="personStaticFactory" class="com.innocence.PersonStaticFactory" factory-method="createPerson" />

注:factory-method与静态方法名称一致。

3)使用非静态工厂方法进行实例化

使用实例工厂方法的实例化从容器中调用现有bean的非静态方法来创建新的Bean。若要使用此机制,请先将该工厂类实例化后,调用创建对象的实例方法。属性设置工厂方法的名称。

以下 是非静态工厂方法创建 Bean的示例:

public class PersonFactory {
	public Person createPerson(){
		System.out.println("非静态工厂创建Person");
		return new Person();
	}
}

<bean id="personFactory" class="com.bean.PersonFactory"></bean>
<bean id="u3" factory-method="createPerson" factory-bean="personFactory"></bean>

注:需要先创建工程类后使用factory-bean属性传入指定;factory-method与非静态工厂方法名称一致。

4.2.5 bean的作用域

不仅可以控制要插入到特定 Bean 定义创建的对象中的各种依赖项和配置值,还可以控制特定 Bean 定义创建的对象的作用域。Spring 框架支持六个作用域,其中四个作用域仅在使用 Web 感知 .还可以创建自定义范围,下表描述了支持的作用域:

掌握常用的前两个。
在这里插入图片描述

1.单例模式(Spring默认模式)

    <bean id="userServiceImpl" class="com.innocence.service.impl.UserServiceImpl" scope="singleton">
    </bean>

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        UserService userServiceImpl = context.getBean("userServiceImpl", UserServiceImpl.class);
        System.out.println(userServiceImpl.hashCode());
        System.out.println("========================");
        UserService userServiceImpl2 = context.getBean("userServiceImpl", UserServiceImpl.class);
        System.out.println(userServiceImpl2.hashCode());
    }
}

结果:

有参构造方法!
1995616381
========================
1995616381

2.原型模式(每次从容器中get的时候,都会 产生一个新的对象)

    <bean id="userServiceImpl" class="com.innocence.service.impl.UserServiceImpl" scope="prototype">
    </bean>

结果:

有参构造方法!
1399499405
========================
238157928
4.2.6 spring bean的生命周期

在这里插入图片描述

Bean 生命周期的整个执行过程描述如下 :

1)根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean。
2)利用依赖注入完成 Bean 中所有属性值的配置注入。
3)如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
4)如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂 实例的引用。
5)如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法 传入当前 ApplicationContext 实例的引用。
6)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它 实现的。 7)如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。初始化 bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean 接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。
8)如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
9)如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
10)如果在 中指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓 存池中,将触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用范围为 scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
11)如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean 销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方 法对 Bean 进行销毁。

4.3 依赖关系

典型的企业应用程序不包含单个对象(或 Spring 用语中的 bean)。即使是最简单的应用程序也有一些对象,这些对象协同工作以呈现最终用户视为一致的应用程序。下一节将介绍如何从定义许多独立的 Bean 定义转变为对象协作以实现目标的完全实现的应用程序。

4.3.1 依赖注入

理解:

  • 依赖:bean对象的创建依赖于容器

  • 注入:bean对象在的所有属性,由容器来注入

测试环境设置

1、创建Student对象:

package com.innocence.pojo;

import java.lang.reflect.Array;
import java.util.*;
public class Student {
    private String name;
    private String address;
    private String[] books;
    private List<String>  hobbys;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

	//创建----get/set,ToString()方法
}

2、bean.xml配置文件新增

   <bean id="student" class="com.innocence.pojo.Student">
        <property name="name" value="Innocence"></property>
        <property name="address" value="北京市"></property>
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>
        <property name="hobbys">
            <list>
                <value>唱歌</value>
                <value>跳舞</value>
                <value>绘画</value>
            </list>
        </property>
        <property name="card">
            <map>
                <entry key="身份证" value="121212121212121222"></entry>
                <entry key="银行卡" value="121212121212121234"></entry>
            </map>
        </property>
        <property name="games">
            <set>
                <value>LOL</value>
                <value>COC</value>
            </set>
        </property>
        <property name="wife">
            <null></null>
        </property>
        <property name="info">
            <props>
                <prop key="学号">20211122</prop>
                <prop key="性别"></prop>
                <prop key="小名"></prop>
            </props>
        </property>
    </bean>

3、测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student.toString());
    }
}

结果:

无参构造方法!
Student{name='Innocence', address='北京市', books=[红楼梦, 水浒传, 三国演义], hobbys=[唱歌, 跳舞, 绘画], card={身份证=121212121212121222, 银行卡=121212121212121234}, games=[LOL, COC], wife='null', info={学号=20211122, 性别=女, 小名=猫}}

1)基于Setter

基于Setter的依赖注入方式在第3章节 使用过。但也分为两种:

基于属性类型值注入:

<property name="name" value="jeck" />

引用属性类型值注入:

<property name="name" ref="jeck" />
2)基于构造函数

无参构造(默认)第3章节 注册bean的时候,所以一般保留对象的无参构造。

xml配置文件:

      <bean id="user" class="com.innocence.pojo.User"> </bean>

有参构造(name属性、Index属性、type注入)

示例:创建user对象

public class User {

    private String name;
    private Integer id;
    private Integer age;
    private String password;

    public User() {
        System.out.println("无参构造方法!");
    }

    public User(String name, Integer id, Integer age, String password) {
        System.out.println("有参构造方法!");
        this.name = name;
        this.id = id;
        this.age = age;
        this.password = password;
    }

    //创建----get/set,ToString()方法
}

下面使用有参构造创建的方式,bean.xml配置:

在这里插入图片描述

1、可以通过name属性,按照参数名赋值

    <bean id="user" class="com.innocence.pojo.User">
        <constructor-arg name="name" value="java学习"></constructor-arg>
        <constructor-arg name="id" value="1"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="password" value="123456"></constructor-arg>
    </bean>

2、可以通过index属性,按照参数索引注入

    <bean id="user" class="com.innocence.pojo.User">
        <constructor-arg index="0" value="java学习"></constructor-arg>
        <constructor-arg index="1" value="1"></constructor-arg>
        <constructor-arg index="2" value="18"></constructor-arg>
        <constructor-arg index="3" value="123456"></constructor-arg>
    </bean>

注意:Index的个数由有参构造决定,不然会报红线提示

3、使用type类型注入

    <bean id="user" class="com.innocence.pojo.User">
        <constructor-arg type="java.lang.String" value="java学习"></constructor-arg>
        <constructor-arg type="java.lang.Integer" value="1"></constructor-arg>
        <constructor-arg type="java.lang.Integer" value="18"></constructor-arg>
        <constructor-arg type="java.lang.String" value="123456"></constructor-arg>
    </bean>

注意:对象定义的类型若是Int,使用Integer会报错,严格对照

以上三种方式选择一个进行测试即可。

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user.toString());;
    }
}

结果:

有参构造方法!
User{id=1, name='java学习', age=18, password='123456'}

注:在配置文件加载的时候,spring容器中管理的对象就已经初始化了。

3)spel spring表达式
<bean name="car" class="com.xzk.spring.bean.Car" >
<property name="name" value="mime" />
<property name="color" value="白色"/>
</bean>
<!--利用spel引入car的属性 -->
<bean name="person1" class="com.xzk.spring.bean.Person" p:car-ref="car">
<property name="name" value="#{car.name}"/>
<property name="age" value="#{person.age}"/>
</bean>
4)p命名空间和c命名注入

使用命名空间的方式注入,需要给配置文件头文件添加p命名空间和c命名空间配置,两者使用set方法机制注入。

     xmlns:p="http://www.springframework.org/schema/p"
      xmlns:c="http://www.springframework.org/schema/c"

p 命名空间使用元素的属性来协作 bean 的属性值,或两者兼而有之,相当于property标签

p命名空间XML配置文件示例

    <!--p命名空间注入,可以直接注入属性的值,替代<property/>标签-->
    <bean id="user" class="com.innocence.pojo.User" p:age="18" p:name="java学习"></bean>

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user.toString());
    }
}

结果:

无参构造方法!
User{id=null, name='java学习', age=18, password='null'}

与p 命名空间的 XML 格式类似,Spring 3.1 中引入的 c 命名空间允许内联属性来配置构造函数参数,相当于constructor-arg标签

c命名空间XML文件示例

 <!--c命名空间注入,通过构造器注入,替代<constructor-arg标签-->
    <bean id="user2" class="com.innocence.pojo.User" c:age="18" c:name="java学习" c:id="1" c:password="1234"></bean>

main方法改为user2获取bean对象,测试结果:

有参构造方法!
User{id=1, name='java学习', age=18, password='1234'}

注:赋值属性的时候,必须对照有参构造方法传递相同个数,不然结果会报Error creating bean with name ‘user2’ defined in class path resource [bean.xml]错误。

5)简单和复杂类型注入

注入的属性类型:String类型,引用类型,数组,List,Map,Set,Null空字符串,prop配置

创建Student对象:

package com.innocence.pojo;

import java.lang.reflect.Array;
import java.util.*;
public class Student {
    private String name;
    private String address;
    private String[] books;
    private List<String>  hobbys;
    private Map<String,String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

	//创建----get/set,ToString()方法
}

bean.xml配置文件新增

   <bean id="student" class="com.innocence.pojo.Student">
        <property name="name" value="Innocence"></property>
        <property name="address" value="北京市"></property>
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>
        <property name="hobbys">
            <list>
                <value>唱歌</value>
                <value>跳舞</value>
                <value>绘画</value>
            </list>
        </property>
        <property name="card">
            <map>
                <entry key="身份证" value="121212121212121222"></entry>
                <entry key="银行卡" value="121212121212121234"></entry>
            </map>
        </property>
        <property name="games">
            <set>
                <value>LOL</value>
                <value>COC</value>
            </set>
        </property>
        <property name="wife">
            <null></null>
        </property>
        <property name="info">
            <props>
                <prop key="学号">20211122</prop>
                <prop key="性别"></prop>
                <prop key="小名"></prop>
            </props>
        </property>
    </bean>

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        Student student = context.getBean("student", Student.class);
        System.out.println(student.toString());
    }
}

结果:

无参构造方法!
Student{name='Innocence', address='北京市', books=[红楼梦, 水浒传, 三国演义], hobbys=[唱歌, 跳舞, 绘画], card={身份证=121212121212121222, 银行卡=121212121212121234}, games=[LOL, COC], wife='null', info={学号=20211122, 性别=女, 小名=猫}}

6)autowire自动注入

autowire属性使用:

  • no 不自动装配(默认值)
  • byName 属性名=id名 ,调取set方法赋值
  • byType 属性的类型和id对象的类型相同,当找到多个同类型的对象时报错,调取set方法赋值
  • constructor 构造方法的参数类型和id对象的类型相同,当没有找到时,报错。调取构造方法赋值

示例:一个人有两个宠物

Dog对象:

public class Dog {

    public void shout(){
        System.out.println("狗--叫");
    }
}

Cat对象:

public class Cat {
    public void shout(){
        System.out.println("猫--叫");
    }
}

People对象:

public class People {
    private String name;
    private Cat cat;
    private Dog dog;
    //get/set方法
}

bean.xml配置文件

    <bean id="cat" class="com.innocence.pojo.Cat" ></bean>
    <bean id="dog" class="com.innocence.pojo.Dog"></bean>

    <bean id="people" class="com.innocence.pojo.People">
        <property name="name" value="java学习"></property>
        <property name="cat" ref="cat"></property>
        <property name="dog" ref="dog"></property>
    </bean>

根据属性名称自动注入,使用byName

    <bean id="people" class="com.innocence.pojo.People" autowire="byName"></bean>

根据属性类型自动注入,使用byType

    <bean id="people" class="com.innocence.pojo.People" autowire="byType"></bean>

注:配置全局自动装配方式选择

<beans default-autowire="constructor/byName/byType/no">

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        People people = context.getBean("people", People.class);
        people.getCat().shout();
        people.getDog().shout();
    }
}

结果:

猫--叫
狗--叫

小结:

  • byName的时候,需要保证所有bean的id唯一,并且这个属性bean需要和自动注入的属性的set方法的值一致!例如:属性cat和beanid cat一致
  • byType的时候,需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

4.4 注解实现ioc

大多数示例都使用 XML 来指定在 Spring 容器中生成每个配置的元数据。上一节演示了如何通过源代码级注释提供大量的配置元数据。但是,即使在这些示例中,"基"Bean 定义也在 XML 文件中显式定义,而注释仅驱动依赖关系注入。本节介绍一个选项,用于通过扫描类路径隐式检测候选组件。候选组件是与筛选条件匹配的类,并且具有向容器注册的相应 Bean 定义。这样就无需使用 XML 来执行 Bean 注册

在spring4之后,要使用注解开发必须导入aop的包,创建新的配置文件,名字随意,bean2.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
	<!--bean 注解使用的开启,无需之前的bean标签,package需要扫描包的路径-->
    <context:component-scan base-package="com.innocence"/>

</beans>
注解使用(类名,属性,方法)

添加在类名上(@Compoent。。。。)

@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象

添加在属性上(@Value,@Autowired…)

@Value("属性值")
private String name;
@Autowired //如果一个接口类型,同时有两个实现类,则报错,此时可以借助@Qualifier("bean
name")
@Qualifier("bean name")
private Car car;
//说明:@Resource 是java的注释,但是Spring框架支持,@Resource指定注入哪个名称的对象
//@Resource(name="对象名") == @Autowired + @Qualifier("name")
@Resource(name="baoma")
private Car car;

jdk1.5支持注解,spring2.5就支持注解了

bean.xml开启属性注释的支持,注意命名空间context

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
   <!--前面的bean标签配置省略--> 
<!--bean属性注释的开启,仅在定义 Bean 的同一应用程序上下文中查找对 Bean 的注释-->
    <context:annotation-config/>

</beans>

注释使用-----属性注释------配合xml bean的注入

1、@Autowired

直接在属性上使用即可!也可以在set方式上使用

使用Autowired 我们可以不用编写set方法,前提是你这个自动装配的属性在Ioc容器中存在,且符合名字byName!

科普:

@Nullable 字段标记了这个注解,说明这个字段可以未Null;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

用例:

public class People {
    //如果显示定义了Autowired的required属性未false,说明这个对象可以为null,否则不允许为空
    @Autowired(required = false)
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    //省略get方法
}

bean.xml配置文件

    <bean id="cat" class="com.innocence.pojo.Cat" ></bean>
    <bean id="dog" class="com.innocence.pojo.Dog"></bean>

    <bean id="people" class="com.innocence.pojo.People" ></bean>

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        People people = context.getBean("people", People.class);
        people.getCat().shout();
        people.getDog().shout();
    }
}

结果:

猫--叫
狗--叫

2、@Qualifier

如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置一起使用,指定一个唯一的bean的对象注入

public class People {
    @Autowired(required = false)
    private String name;
    @Autowired
    @Qualifier(value = "cat")
    private Cat cat;
    private Dog dog;
    //省略get方法
}

3、@Resource = @Autowired + @Qualifier(value = “cat”)

public class People {
    @Autowired(required = false)
    private String name;
    @Resource(name = "cat")  //name默认为“”可不省略不写,当bean对象注入冲突时,可用来指定bean
    private Cat cat;
    private Dog dog;
    //省略get方法
}

@Resource和@Autowired区别:

共同:都是用来自动装配,都可以放在属性字段上

不同:

@Autowired 通过byType的方式实现,而且必须要求这个对象存在---------------------------类型和名字实现方式???有疑问

@Resource 默认通过byName的方式实现,如果找不到名字再通过byType实现。

注解汇总:

@Autowired 自动装配通过类型/名字。如果Autowired不能自动装配上唯一属性,则需要@Qualifiler(value = "xxx")
@Nullable 字段标记了这个注解,说明这个字段可以为null;
@Resource 自动装配通过名字/类型

添加在方法上(@Postconstruct)

@PostConstruct //等价于init-method属性
public void init(){
System.out.println("初始化方法");
}
@PreDestroy //等价于destroy-method属性
public void destroy(){
System.out.println("销毁方法");
}
@Compoent注解

用例说明:

@Component
public class Cat {
    public void shout(){
        System.out.println("猫--叫");
    }
}
@Component
public class Dog {

    public void shout(){
        System.out.println("狗--叫");
    }
}
@Component
public class People {
    @Autowired(required = false)
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    
    public String getName() {
        return name;
    }

    public Cat getCat() {
        return cat;
    }

    public Dog getDog() {
        return dog;
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        People people = context.getBean("people", People.class);
        people.getCat().shout();
        people.getDog().shout();
    }
}

结果:

猫--叫
狗--叫
@Component的衍生注解

在web开发中,bean的注入会按照mvc三层架构分层!这些注解的功能和@Component是一样的,都是代表将某个类注册到spring容器中,注入bean

dao层: @Repository
service层:@Service
controller层: @Controller
@Value注解

属性值或者set方法使用

@Component
public class People {
    @Value("java学习")
    private String name;
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
	//省略get方法
}

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
        People people = context.getBean("people", People.class);
        System.out.println(people.getName());;
    }
}

结果:

java学习
@Scope
@Component
@Scope("singleton")   //作用域
public class Cat {
    public void shout(){
        System.out.println("猫--叫");
    }
}

小结:

xml与注解:

  • xml更万能,适用于任何场合!维护简单方便
  • 注解不是自己类使用不了,维护相对复杂!

xml与注解最佳实践:

  • xml用来管理bean
  • 注解值负责完成属性的注入
  • 我们在使用过程中,只需注意一个问题,必须开启注解支持

一般情况下这两者开启是连接一起使用

    <context:annotation-config></context:annotation-config>
    <context:component-scan base-package="com.innocence"/>

4.5 使用Java的方式配置spring

现在要完全不使用spring的xml配置了,全权交给java来做。

注入bean的另一种方式。javaConfig是spring的一个子项目,在在spring4之后,他成为了一个核心的功能。

@Bean和@Configuration

基本概念:

该注释用于指示方法实例化、配置和初始化要由 Spring IoC 容器管理的新对象。对于那些熟悉Spring的XML配置的人来说,注释与元素起着相同的作用。您可以将 注释用于任何 Spring。

​ 用 注释类表示其主要用途是作为 bean 定义的源。此外,类允许通过调用同一类中的其他方法来定义 Bean 间依赖项。

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

前面的类等效于下面的 Spring XML:AppConfig``<beans/>

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

@Bean: 是方法级批注。若要声明 Bean,使用该注释。此方法的返回值类型是定义的注册 Bean 。默认情况下,Bean 名称与方法名称相同

用例说明

1、创建person对象

public class Person {
    private String name;

    public String getName() {
        return name;
    }
}

2、创建Appconfig配置类并启动组件扫描@ComponentScan

@ComponentScan:扫描包以查找任何带批注的类,并将这些类注册为容器中的 Spring Bean 定义。 该注释与xml文件中<context:component-scan base-package=“com.acme”/> 同等效应

@Configuration     
@ComponentScan("com.innocence.pojo")    //扫描包
public class Appconfig {
    @Bean                 
    public Person getPerson(){
        return new Person();
    }
}

3、测试

注意测试获取配置的方式是AnnotationConfigApplicationContext,获取的bean方法是代码中定义的名称

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class);
        Person getPerson = (Person) context.getBean("getPerson");
        System.out.println(getPerson.getName());;
    }
}

结果:

null

这就是纯java的方式,通过 Java 的配置创建 spring 容器完全撇开spring的xml配置文件。

5 AOP

5.1 代理模式

5.1.1静态代理

在这里插入图片描述

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
  • 客户:访问代理对象的人!

用例说明:客户租房----中介-----房东出租

代码结构
    1.抽象角色:
        租房接口----Rent,创建抽象租房方法
    2.真实角色:
        房东实体类----Host,实现租房接口
    3.代理角色:
        中介实现租房----Proxy,实现租房接口
    4.客户角色
        客户实体类----Client,通过main方法模拟客户租房
public interface Rent {
	//租房方法
    public void getRent();
}
public class Host extends Properties implements Rent{
    public void getRent() {
        System.out.println("房东:出租");
    }
}
public class Proxy implements Rent {

    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }
    //代理出租
    public void getRent() {
        seeHouse();
        host.getRent();        //房东--真实对象出租
        hetong();
        fare();
    }
    //代理自身的其他方法---带看房,签合同,收中介费
    public void seeHouse(){
        System.out.println("中介:带你去看房");
    }
    public void hetong(){
        System.out.println("中介:签合同");
    }
    public void fare(){
        System.out.println("中介:收取中介费");
    }

}
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy(host);
        proxy.getRent();

    }
}

运行结果:

中介:带你去看房
房东:出租
中介:签合同
中介:收取中介费

理解:通过main方法的运行过程,可以看出代理类(中介)的产生,通过有参构造传入被代理对象(房东)进行创建,因为代理类中的出租方法由房东的真实方法执行,所以代理类执行的出租真实代理了房东,实现了静态代理的结果。与此同时,代理类的其他功能正常执行。

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍—开发效率降低
5.1.2 动态代理

​ 为了解决静态代理的缺点,出现了动态代理,其机制是使用反射的原理解决代理类的代码量。动态代理的底层基本上都是反射实现的

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生成的,不是直接编写
  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
    • 基于接口----jdk动态代理
    • 基于类:cglib
    • java字节码实现:javasist

与上述例子的静态代理角色变化:

代码结构
    1.抽象角色:
        租房接口----Rent,创建抽象租房方法 ----有
    2.真实角色:
        房东实体类----Host,实现租房接口 ----有
    3.代理角色:
        中介实现租房----Proxy,实现租房接口 ----变成InvocationHandle处理类,自动生成代理类
    4.客户角色
        客户实体类----Client,通过main方法模拟客户租房
jdk
public interface Rent {
    public void getRent();
}
public class Host extends Properties implements Rent {
    public void getRent() {
        System.out.println("房东:出租");
    }
}
package com.innocence.jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
    private Rent rent;
    public void setRent(Rent rent){
        this.rent = rent;
    }

    //处理真实角色实例,并返回动态创建的对象----反射(动态代理的本质)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //返回rent接口的实现类对象
        Object invoke = method.invoke(rent, args);
        return invoke;
    }

    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }

}
public class Client {
    public static void main(String[] args) {
        //真实角色--房东
        Host host = new Host();
        //动态代理-代理真实角色
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setRent(host);
        Rent proxy = (Rent)proxyInvocationHandler.getProxy();
        //执行
        proxy.getRent();
    }
}

运行结果:

房东:出租

以上的代理类内部只是对Host类进行了代理,下面升级为公共类,对所有需要代理的对象进行动态代理。

public class ProxyInvocationHandler implements InvocationHandler {
    //修改处:通过Object类接收所有需要代理的对象
    private Object target;
    public void setRent(Object target){
        this.target = target;
    }

    //处理真实角色实例,并返回动态创建的对象----反射(动态代理的本质)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //返回rent接口的实现类对象
        Object invoke = method.invoke(target, args);
        return invoke;
    }

    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

}

使用之前的client测试,结果一致。创建新的真实角色(Teacher)再次测试

public class Teacher implements Rent{
    public void getRent() {
        System.out.println("老师:出租房子");
    }
}
public class Client {
    public static void main(String[] args) {
        //真实角色--老师
        Teacher teacher = new Teacher();
        //动态代理-代理真实角色
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setRent(teacher);
        Rent proxy = (Rent)proxyInvocationHandler.getProxy();
        //执行
        proxy.getRent();
    }
}

运行结果:

老师:出租房子

动态代理的使用—添加日志

对代理类增加日志

public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;
    public void setRent(Object target){
        this.target = target;
    }

    //处理真实角色实例,并返回动态创建的对象----反射(动态代理的本质)
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        getMsg(method.getName());       //日志
        Object invoke = method.invoke(target, args);
        return invoke;
    }

    //生成代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    //新增日志方法
    public void getMsg(String msg){
        System.out.println("日志:执行了"+msg+"方法");
    }

}

同样client测试结果

日志:执行了getRent方法
老师:出租房子
cglib

使用JDK创建代理有一个限制,它只能为接口创建代理实例。这一点可以从Proxy的接口方法 newProxyInstance(ClassLoader loader,Class[] interfaces,InvocarionHandler h)中看的很清楚

第二个入参interfaces就是需要代理实例实现的接口列表

**对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?**JDK动态dialing技术显然已经黔驴技穷,CGLib作为一个代替者,填补了这一空缺。

CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

测试示例:

pom.xml依赖包:

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.5</version>
        </dependency>

创建实体类:

public class Teacher {
    public void login(){
        System.out.println("老师:登陆");
    }
}

创建代理类:

public class CgProxy implements MethodInterceptor {
    /**
     *
     * @param o
     * @param method  上下文中实体类所调用的被代理的方法
     * @param objects   CGLib动态生成的代理类实例,object[] 为参数值列表
     * @param methodProxy  生成的代理类对方法的代理引用。
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("打印1:===============");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println("打印2:===============");
        return obj;
    }

}

测试:

public class JTest {
    public static void main(String[] args) {
        //1.创建真实对象
        Teacher teacher = new Teacher();
        //2.创建代理对象
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(teacher.getClass());
        enhancer.setCallback(new CgProxy());
        Teacher o = (Teacher)enhancer.create();//代理对象
        o.login();
    }
}

结果:

打印1:===============
老师:登陆
打印2:===============

两种代理方式的区别:

1、jdk动态代理生成的代理类和委托类实现了相同的接口;

2、cglib动态代理中生成的字节码更加复杂,生成的代理类是委托类的子类,且不能处理被final关键字 修饰的方法;

3、jdk采用反射机制调用委托类的方法,cglib采用类似索引的方式直接调用委托类方法;

5.2 Aop概念

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生模型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同事提高了开发的效率。

​ Spring通过使用基于架构的方法@AspectJ注释样式,提供了编写自定义方面的简单而强大的方法。

AOP在Spring框架中用于:

  • 提供声明性企业服务。最重要的此类服务是声明式事务管理
  • 让用户实现自定义切面,通过 AOP 补充他们对 OOP 的使用。

AOP术语:

  • 横切关注点:跨越应用程序多个模块的方法或功能。如:日志,安全,缓存,事务等等
  • 切面(Aspect):横切关注点被模块化的特殊对象,即,他是一个类。
  • 通知(Advice):切面必须要完成的工作。即,他是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知执行的”地点“的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-stRpsn52-1638769622547)(.assets/image-20211125103147680.png)]

SpringAop中,通过Advice定义横切逻辑,Spring中支持5中类型的Adive:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SGy5RcKD-1638769622549)(.assets/image-20211125103422150.png)]

即AOP在不改变原有代码的情况下,去增加新的功能。

5.3 AOP的实现机制

  • JDK的动态代理:针对实现了接口的类产生代理。InvocationHandler接口
  • CGlib的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象,MethodInterceptor接口。

实例可以回看5.1.2 节 < 动态代理 >
在这里插入图片描述

5.4 spring中使用Aop

导入依赖包:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
5.4.1 使用spring的api的接口

1、创建service 增删改查的方法及实现类impl

public interface PeopleService {

    public void add();
}
public class PeopleServiceImpl implements PeopleService {
    public void add() {
        System.out.println("调用了add方法");
    }
}

2、增强类

import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
    /**
     * 后置增强
     * @param returnValue   返回值
     * @param method     要执行的目标对象的方法
     * @param args      参数
     * @param target    目标对象
     * @throws Throwable
     */
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了,返回结果:"+returnValue);
    }
}
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;
public class beforeLog implements MethodBeforeAdvice {
    /**
     * 前置增强
     * @param method   要执行的目标对象的方法
     * @param args    参数
     * @param target   目标对象
     * @throws Throwable
     */
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}

3、创建新的aop.xml配置文件,名字随意取

注意:需要导入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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--注册bean-->
    <bean id="afterLog" class="com.innocence.utils.AfterLog"></bean>
    <bean id="beforeLog" class="com.innocence.utils.beforeLog"></bean>
    <bean id="peopleServiceImpl" class="com.innocence.service.impl.PeopleServiceImpl"></bean>

    <!--方式一:使用原生的spring api的接口-->
    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点:expression表达式: execution(要执行的位置! * * * * *)-->
        <aop:pointcut id="pointcut" expression="execution(* com.innocence.service.*.*(..))"/>
        <!--执行增强方法-->
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"></aop:advisor>
    </aop:config>

</beans>

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        PeopleService peopleService = context.getBean("peopleServiceImpl", PeopleService.class);
        peopleService.add();
    }
}

运行结果:

com.innocence.service.impl.PeopleServiceImpl的add被执行了
调用了add方法
com.innocence.service.impl.PeopleServiceImpl的add被执行了,返回结果:null
5.4.2 使用自定义类来实现aop

后补充

5.4.3 注解实现

1、创建切面类

@Aspect            //标注这个类是一个切面
public class AnnotationPointCut {

    @Before("execution(* com.innocence.service.*.*(..))")
    public void before(){
        System.out.println("=====前置:方法执行====");
    }
}

2、aop.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--注册bean-->
    <bean id="peopleServiceImpl" class="com.innocence.service.impl.PeopleServiceImpl"></bean>

   
    <!--方式三:注解-->
    <bean id="annotationPointCut" class="com.innocence.aop3.AnnotationPointCut"></bean>
    <!--开启注解支持,扩展:jdk(默认 proxy-target-class="false")  cglib( proxy-target-class="true")-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

测试:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
        PeopleService peopleService = context.getBean("peopleServiceImpl", PeopleService.class);
        peopleService.add();
    }
}

运行结果:

=====前置:方法执行====
调用了add方法

注解方式中注解的顺序问题

1.没有异常情况下 :
----环绕开始 ----
----前置增强开始执行----
----方法()-------
----环绕结束 ----
----最终增强 ----
----后置增强开始执行----
相对顺序固定,注解换位置时不影响结果顺序
在这里插入图片描述

aop的应用场景:事务底层实现,日志,权限控制,mybatis中sql绑定,性能检测

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值