目录
5.1 ApplicationContext 的三个实现类及简单的demo
5.2 ApplicationContext 和 BeanFactory 的区别
5.2.2 为什么推荐使用 ApplicationContext
5.6 依赖注入(Dependency Injection)
一、什么是 Spring
Spring 是一个分层的一站式轻量级开源框架,提供了多个模块给用户自由组合,能有效解决复杂的企业级应用
二、Spring 的特点
- 解耦,简化开发:使用 IOC 容器,将对象的创建和管理统一交给 IOC 来管理,降低各模块之间的依赖性,使开发者只需要着重逻辑业务的开发
- 统一管理:由 IOC 容器统一管理对象,维护对象之间的依赖关系
- 扩展性强:支持兼容主流框架,如 MyBatis、Hibernate等
- 面向切面编程:AOP使的开发者可以很方便地对程序进行权限拦截、运行监控等
- 高度开放性:开发者可以自由地选择使用部分 Spring 模块或是全部 Spring 模块
(1)spring属于低侵入式设计,代码的污染极低;
(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring对于主流的应用框架提供了集成支持。
三、Spring核心概念
3.1 IOC(控制反转)
3.1.1 概念
通俗地说,所谓的控制反转就是用工厂模式将模块包装起来,其他模块要使用时由工厂统一管理和处理。IOC 则是由一个个这样的工程组合而成的。
3.1.2 为什么叫控制反转
假设有 A、B 两个模块,若 A 模块需要使用到 B 模块的对象
没有IOC容器的时候:
没有 IOC 容器时,A模块需要在初始化或某个节点时 new 一个 B 对象或使用已有的 B 对象,而这个主动权在 A 模块中,A 模块能选择使用哪种 B 模块对象,是 new 一个新的,还是使用已有的
使用IOC容器后:
使用 IOC 容器后,所有的 B 对象都由 IOC 容器统一管理,在项目运行后,IOC容器发现A需要使用B,就从B工厂中生成一个B并发送给A
控制 -- 反转
控制:结合例子,可以发现对象的创建交给了 IOC 容器实现,即对象的创建和控制权交给了 IOC 容器,这就是Spring中的控制
反转:结合例子,未使用 IOC 容器时,对象的创建权和控制权是在他的调用对象手中的。
使用了 IOC 容器后,对象的创建权和控制权都交给了 IOC 容器,而调用他的对象模块只能使用 IOC 容器返回的对象。这
也就是依赖注入
3.1.3 依赖注入
DI—Dependency Injection,即“依赖注入”:是组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。
依赖 -- 注入
依赖:组件依赖于 IOC 容器,通过 IOC 容器来提供对象所需要的资源,实现组件间的解耦
注入:调用组件的模块,通过向 IOC 容器获取,得到所需对象的过程,叫做注入
3.1.4 IOC 和 DI 有什么好处
- 提高组件重用频率,保证代码简洁,提高代码质量
- 通过依赖注入机制,让开发者无需关系指定目标是如何创建、来自哪里、如何实现,而是只需要即拿即用,专注于自身的业务逻辑
3.2 AOP
3.2.1 AOP 概念
AOP 意为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护是一种技术。它允许我们在不改变实现类代码的情况下,实现功能的横向拓展。
AOP解决了什么:
例如日志功能,由于日志代码往往水平散布在所有实现类中,在OOP中需要在所有需要写日志的类中调用日志代码,这种重复性极高的日志代码会导致代码的冗余以及难以维护。
而AOP利用一种称为横切的技术,将影响多个类的公共行为封装到一个公共模块中(如日志模块),命名为"Aspect"。简单地说,就是将那些与业务无关,却被业务模块共同调用的逻辑封装起来,便于减少系统的重复代码,降低模块的耦合性
3.2.2 AOP 优势
- 减少重复代码
- 维护方便
四、Spring 模块
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器 :核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用 控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开 - Spring 上下文 :Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。其他资源可以通过 Context 访问 Spring 的 Bean 资源,相当于资源注入。
- Spring AOP :AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截器,供用户自定义和配置,主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等
- Spring DAO :JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM :Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块 :Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架 :WEB MVC模块为Spring提供了一套轻量级的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring自己的MVC框架更加简洁和方便。MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。
五、Spring 使用
5.1 ApplicationContext 的三个实现类及简单的demo
三种实现类:
- ClassPathXmlApplicationContext:加载类路径下的配置文件,即resources下的配置文件
- FileSystemXmlApplicationContext:加载磁盘任意路径下的配置文件(需要有访问权限)
- AnnotationConfigApplicationContext:根据注解创建容器
配置方式:
public static void main(String[] args) {
// 1. 获取核心容器对象
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new FileSystemXmlApplicationContext("E:\\study\\project\\spring\\spring-demo03\\src\\main\\resources\\bean.xml");
// 2. 根据ID获取Bean对象
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
AccountDao accountDao = ac.getBean("accountDao", AccountDao.class);
accountDao.saveAccount();
}
5.2 ApplicationContext 和 BeanFactory 的区别
5.2.1 比较
ApplicationContext | BeanFactory | |
区别 | 在构建核心容器时,创建对象采取的策略是立即加载的方式,即已读取完配置文件马上就会创建文件中配置的对象 | 在构建核心容器时,采用的是延迟加载的方式。即什么时候根据 id 获取对象了,什么时候才真正创建对象 |
使用难度 | 简单 | 复杂 |
功能 | 继承了 BeanFactory 的特性,还增加了大量拓展。如资源绑定、事件传播、资源加载等 | 实现了依赖注入、控制反转等基础的bean对象操作 |
推荐使用 | 单例模式下,更推荐使用 | 非单例模式,可以使用 |
5.2.2 为什么推荐使用 ApplicationContext
推荐原因:
- 由于 BeanFactory 采用的是延时加载的方式注入,如果 Bean 注入异常,则只有在你第一次调用 GetBean 方法时才会抛出异常,而 ApplicationContext 会在初始化时进行检查,及时检查历来是否完全注入
- 原始的 BeanFactory 无法支持 APO、Web 应用等许多插件
- ApplicationContext 是由 BeanFactory 派生出来的,故拥有 BeanFactory 的所有功能。且又扩展了其他的功能,如资源访问、事件传播、载入多个上下文等
5.2.3 使用
依赖:
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
ApplicationContext
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 2. 根据ID获取Bean对象
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
BeanFactory
public static void main(String[] args) {
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService accountService = factory.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
5.3 创建 Bean 的三种方式
5.3.1 使用默认构造函数创建对象
使用默认构造函数创建,若类中没有默认构造函数,则对象无法创建
<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="accountService" class="com.tom.www.service.impl.AccountServiceImpl"></bean>
</beans>
5.3.2 使用某个类中的方法创建对象
这种是使用某个类中的方法,并存入 spring 容器
/**
* 模拟一个工厂类(该类可能是存在于 jar 包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFactory{
public IAccountService getAccountService() {
return new IAccountServiceImpl();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器) -->
<bean id="instanceFactory" class="com.tom.www.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
</beans>
5.3.3 使用某个类中的静态方法创建对象
/**
* 模拟一个工厂类(该类可能是存在于 jar 包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class StaticFactory{
public static IAccountService getAccountService() {
return new IAccountServiceImpl();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用某个类的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器) -->
<bean id="accountService" class="com.tom.www.factory.StaticFactory" factory-method="getAccountService"></bean>
</beans>
5.4 Bean 的作用范围(Scop)
5.4.1 属性列举
属性 | 描述 |
singleton (默认) | 单例模式使用 |
prototype | 多例模式 |
request | 为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收 |
session | 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效 |
global-session | 全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同 |
5.4.2 设置
使用 scope 关键字进行设置,默认模式为 singleton
<?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="accountDao" class="com.tom.dao.AccountDao" scope="singleton"></bean>
</beans>
5.5 Bean 对象的生命周期
出生 | 活着 | 死亡 | 总结 | |
单例对象 | 当容器创建时对象出生 | 只要容器还在,对象一直活着 | 容器销毁,对象消亡 | 单例对象的生命周期与容器相同 |
多例对象 | 当我们使用对象时由spring框架创建 | 对象只要在使用,就一直活着 | 当对象长时间不用,且没有别的对象引用时,由java的垃圾回收器回收 |
5.6 依赖注入(Dependency Injection)
在IOC的作用中,当一个类需要使用到其他类的对象,由IOC容器为我们提供,我们只需要在配置文件中说明,这样使用类通过被使用类获取对象的方式,我们就称为依赖注入。
5.6.1 依赖注入的数据类型
- 基本类型和string
- 其他 bean 类型(在配置文件中或注解中配置过的 bean)
- 复杂类型/集合类型
5.6.2 依赖注入的方式
- 使用构造器注入
- 使用 setter 方式注入
- 使用注解注入(推荐)
5.6.3 使用构造器注入
<!-- 构造函数注入
使用标签:constructor-arg
标签出现的为止: bean标签内部
标签中的属性:
type: 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index: 用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引位置从0开始
name(c常用): 用于指定给构造函数中指定名称的参数赋值
======================= 以上三个用于指定给构造函数中哪个参数赋值 =====================
value: 用于提供基本类型和string类型的数据
ref: 用于指定其他的bean类型数据,即在spring的ioc容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了 Bean 对象的实例化方式,使我们在创建对象时,若用不到这些数据,也必须提供
-->
<bean id="accountService" class="xyz.tom.www.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="小明"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
5.6.4 使用 setter 方法注入
<!-- set方法注入
设计的标签: property
出现的为止: bean标签的内部
标签中的属性:
name(c常用): 用于指定注入时所调用的set方法名称
value: 用于提供基本类型和string类型的数据
ref: 用于指定其他的bean类型数据,即在spring的ioc容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行
-->
<bean id="accountService2" class="xyz.tom.www.service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签: list array set
用于给Map结构注入的标签: map props
同类型可以互换,即只要记: list map
-->
<bean id="accountService3" class="xyz.tom.www.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="testA">AAA</prop>
<prop key="testB">BBB</prop>
<prop key="testC">CCC</prop>
</props>
</property>
</bean>
5.6.5 使用注解方式注入
使用注解前,需要在 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"
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">
<context:component-scan base-package="xyz.tom"></context:component-scan>
</beans>
常用的注解:
属性名 | 作用 | 位置 | 限制 |
@Component | 创建 Bean 对象 | 类名头部 | |
@Autowired | 注入变量 | 变量上,或方法上 | 要求 bean 的 id 唯一 |
@Value | 注入基本类型或string类型数据 | 变量上 | ${“xxx.xxx”} |
@Scope | 改变 Bean 对象的作用范围 | 类名头部 | |
@PreDestory、@PostConstruct | 生命周期,指定销毁方法和初始方法 | 方法上 | |
@Qualifier | 存在多个对象类型时,可以指定使用哪一个对象类型 | 类名头部 |
根据mvc三层框架,spring分别提供了三个注解,这三个注解其实是一样的,只是使用地方有所区别:
- @Controller:一般用在表现层
- @Service:一般用在业务层
- @Repository:一般用在持久层
- @Component:其他
5.7 完全注解方式使用Spring
5.7.1 增加配置类,抛弃xml文件
新建 SpringConfiguration.java 类,作为 Spring 项目配置的根本配置类
@Configuration
@ComponentScan({"com.tom"})
public class SpringConfiguration {
}
修改启动类
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
accountService.saveAccount();
}
5.7.2 多配置文件组合
在实际开发中,Spring 允许使用 @Import 注解加载其他配置
新建数据库配置类 JdbcConfig.java
// 表明这是一个配置类,会被扫描器扫到
public class JdbcConfig {
....
}
在主配置类中引用
@ComponentScan({"xyz.tom"}) // 指定父类路径
@Import(JdbcConfig.class) // 导入子类,此时子类无需加 @Configuration
public class SpringConfiguration {
...
}
5.7.3 将配置变量存放到配置文件并加载
在 resources 文件夹下创建 jdbcConfig.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://www.xxxx.xyz:3306/mysql?useUnicode=true&characterEncoding=utf8&useSSL=false
jdbc.user=root
jdbc.password=root
在配置类中,使用 @PropertySource 引入
//@Configuration
@ComponentScan({"xyz.tom"})
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
在配置类中,用 @Value 使用
public class JdbcConfig {
@Value("${jdbc.driver}")
private String dirver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/*
* 用于创建一个 QueryRunner 对象
*/
@Bean(name = "runner")
// @Scope(value = "prototype")
public QueryRunner createQueryRunner(DataSource datasource) {
return new QueryRunner(datasource);
}
/*
* 用于创建一个数据源对象
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
ComboPooledDataSource ds = new ComboPooledDataSource();
try {
ds.setDriverClass(dirver);
// System.out.println(url);
// ds.setDriverClass("com.mysql.cj.jdbc.Driver");
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
} catch (PropertyVetoException e) {
throw new RuntimeException(e);
}
}
}
六、Spring 搭建单元测试
加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
创建测试类
/*
* 使用Junit单元测试,测试我们的配置
* Spring整合junit的配置
* 1. 导入spring整合junit的jar
* 2. 使用Junit提供的一个注解将原有的main方法替换,改成spring提供的
* @RunWith
* 3. 告知spring的运行期,spring和ioc创建是基于xml还是注解的,并说明位置
* @ContextConfiguration
* locations: 指定xml文件的位置,加上classpath关键字,表示在类路径下
* classes: 指定注解类所在位置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class MainTest{
@Autowired
private IAccountService as;
@Test
public void test() {
as.saveAccount();
}
}
七、动态代理
7.1 概述
动态代理的作用是在不改变原有相关代码的情况下,实现对类的功能拓展。使用者通过访问代理对象,实现功能。
如图:
- 用户要调用A服务,需要调用A服务的代理对象
- A服务的代理对象先对用户的请求进行处理,然后才调用到真正的对象
- 代理对象接收到真正的服务的响应,返回给用户
7.2 两种使用方式
7.2.1 介绍
动态代理有两种使用方式:
jdk | Cglib | |
区别 | 基于接口的动态代理 | 基于子类的动态代理 |
局限 | 被代理类最少实现一个接口,如果没有则不能使用 | 被代理类不能是最终类 |
提供者 | JDK官方 | 第三方cglib库 |
创建方式 | 使用Proxy类中的newProxyInstance方法 | 使用Enhancer类中的create方法 |
7.2.2 代码实现
建立实体类
public class Producer{
public void sale(float money) {
System.out.println("售出商品,金额:" + money);
}
public void doService(float money) {
System.out.println("维修商品,金额:" + money);
}
}
JDK 方式调用
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 用于加载对象字节码的,和被代理对象使用相同的类加载器,固定写法。
* Class[]:字节码数组
* 用于让代理对象和被代理对象,有相同方法
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理,一般都是些该接口的实现类,通常是匿名内部类,但不是必须
* 此接口的实现类都是谁用谁写
*/
IProducer proxyProduer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过盖房啊
* 方法参数的含义:
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 提供增强的代码
Object returnValue =null;
// 1. 获取方法执行的参数
Float money = (Float) args[0];
// 2. 判断当前方式是不是销售
if("sale".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProduer.sale(5000f);
}
}
Cglib 方式调用
增加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
示例代码
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* create方法的参数:
* Class:字节码
* 用于指定被代理对象的字节码。
* Callback:用于提供增强的代码
*/
Producer cglibProxyProduer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行对象的任何方法一定会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法中的参数是一样的
* @param methodProxy // 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 提供增强的代码
Object returnValue =null;
// 1. 获取方法执行的参数
Float money = (Float) args[0];
// 2. 判断当前方式是不是销售
if("sale".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProxyProduer.sale(12000f);
cglibProxyProduer.doService(12000f);
}
}
7.2.3 使用工厂模式封装代理
工厂类封装
@Component
public class ProxyFactory {
@Autowired
private ProducerService producer;
public ProducerService getProducer() {
ProducerService proxyProduer = (ProducerService) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过盖房啊
* 方法参数的含义:
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 提供增强的代码
Object returnValue =null;
// 1. 获取方法执行的参数
Float money = (Float) args[0];
// 2. 判断当前方式是不是销售
if("sale".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
return proxyProduer;
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class MainTest{
@Autowired
private ProxyFactory proxyFactory;
@Test
public void test(){
ProducerService producer = proxyFactory.getProducer();
producer.sale(5000f);
}
}
结果