spring是什么?我们为什么要学习它?
为什么需要学习spring?
1.最大程度的简化了开发
spring是一个非常优秀的java框架,其目标是为了简化java企业级开发,spring出来已经十几年了,这期间也一直围绕着这个目标在进行,像后面需要学习的springmvc、springboot、springcloud,这些技术也都是围绕着简化开发的目标在努力
2.大量公司使用
目前99%的公司使用了spring,可以去各大招聘网站看一下,spring算是必备技能,所以一定要掌握。
3.顶级的源码设计
spring框架源码设计非常优秀,在java开源项目中可以说是顶级的
什么是spring?
spring是一个简化java企业级开发的一个框架,内部包含了很多技术,比如:控制反转&依赖注入、面向切面编程、spring事务管理、通过spring集成其他框架、springmvc、springboot、springcloud等等,这些都是围绕简化开发展开的技术,后面会对每个知识点详细介绍。
控制反转(IoC)与依赖注入(DI)
Spring中有3个核心的概念:控制反转(Ioc)、依赖注入(DI)、面向切面编程(AOP),spring中其他的技术都是依靠3个核心的技术建立起来的,所以玩spring需要先对这3个概念有个深入的理解。
什么是IOC(控制反转)
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想.对象的创建交给外部容器完成,这个就做控制反转。
●**谁控制谁,控制什么:**传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●**为何是反转,哪些方面反转了:**有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
传统程序设计如图,都是主动去创建相关对象然后再组合起来:
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了
UserService类中的execute()方法调用UserDao类中的add()方法
**原始方式代码:**这样写耦合度太高了。
class UserDao{
add(){
.....
}
}
class UserService{
execute(){
UserDao dao=new UserDao();
dao.add();
}
}
IOC过程:进一步降低耦合度
我们写程序的目标就是** 高内聚 低耦合!**这样修改起来 就不会有太多的联系 不用 改一个地方 其他的都要修改。
高内聚
内聚:就是一个模块内各个元素彼此结合的紧密程度, 高内聚就是一个模块内各个元素彼此结合的紧密程度高. 所谓高内聚实质一个软件模块是由相关性很强的代码组成, 只负责一项任务, 也就是常说的单一责任原则.
低耦合
**耦合: 一个软件结构内不同模块之间互连程度的度量(耦合性也叫块间联系,指软件系统模块中各模块间相互联系紧密程度的一种度量. **模块之间联系越紧密, 其耦合性就越强, 模块的独立性则越差, 模块间耦合的高低取决于模块间接口的复杂性, 调用的方式以及传递的信息.)对于低耦合, 粗浅的理解是: 一个完整的系统, 模块与模块之间, 尽可能的使其独立存在, 也就是说, 让每个模块, 尽可能的独立完成某个特定的子功能. 模块与模块之间的接口, 尽量的少而简单. 如果某两个模块间的关系比较复杂的话, 最好首先考虑进一步的模块划分. 这样有利于修改和组合.
- Spring使⽤控制反转来实现对象不⽤在程序中写死、
- 控制反转解决对象处理问题【把对象交给别⼈创建】
那么对象的对象之间的依赖关系,Spring是怎么做的呢??依赖注⼊:dependency injection.
- Spring使⽤依赖注⼊来实现对象之间的依赖关系
- 在创建完对象之后,对象的关系处理就是依赖注⼊
上⾯已经说了,控制反转是通过外部容器完成的,⽽Spring⼜为我们提供了这么⼀个容器,我们⼀般将这个容器叫做:IOC容器. 这里所说的IOC容器也叫spring容器。
⽆论是创建对象、处理对象之间的依赖关系、对象创建的时间还是对象的数量,我们都是在Spring为我们提供的IOC容器上配置对象的信息就好了。
那么使⽤IOC控制反转这⼀思想有什么作⽤呢???
ioc的思想最核⼼的地⽅在于,资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。第⼀,资源集中管理,实现资源的可配置和易管理。第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合度。
也就是说,甲⽅要达成某种⽬的不需要直接依赖⼄⽅,它只需要达到的⽬的告诉第三⽅机构就可以了,⽐如甲⽅需要⼀双袜⼦,⽽⼄⽅它卖⼀双袜⼦,它要把袜⼦卖出去,并不需要⾃⼰去直接找到⼀个卖家来完成袜⼦的卖出。它也只需要找第三⽅,告诉别⼈我要卖⼀双袜⼦。这下好了,甲⼄双⽅进⾏交易活动,都不需要⾃⼰直接去找卖家,相当于程序内部开放接⼝,卖家由第三⽅作为参数传⼊。甲⼄互相不依赖,⽽且只有在进⾏交易活动的时候,甲才和⼄产⽣联系。反之亦然。这样做什么好处么呢,甲⼄可以在对⽅不真实存在的情况下独⽴存在,⽽且保证不交易时候⽆联系,想交易的时候可以很容易的产⽣联系。甲⼄交易活动不需要双⽅⻅⾯,避免了双⽅的互不信任造成交易失败的问题。因为交易由第三⽅来负责联系,⽽且甲⼄都认为第三⽅可靠。那么交易就能很可靠很灵活的产⽣和进⾏了。这就是ioc的核⼼思想。⽣活中这种例⼦⽐⽐皆是,⽀付宝在整个淘宝体系⾥就是庞⼤的ioc容器,交易双⽅之外的第三⽅,提供可靠性可依赖可灵活变更交易⽅的资源管理中⼼。另外⼈事代理也是,雇佣机构和个⼈之外的第三⽅。
update=
在以上的描述中,诞⽣了两个专业词汇,依赖注⼊和控制反转所谓的依赖注⼊,则是,甲⽅开放接⼝,在它需要的时候,能够讲⼄⽅传递进来(注⼊)所谓的控制反转,甲⼄双⽅不相互依赖,交易活动的进⾏不依赖于甲⼄任何⼀⽅,整个活动的进⾏由第三⽅负责管理。
什么是Spring的依赖注入
依赖注入:是指程序运行过程中,如果需要创建一个对象,无须再代码中new创建,而是依赖外部的注入。spring的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对pojo之间依赖关系的管理
注入方式后面讲解
Spring 的整体架构
1.数据访问/集成( Data Access/ ntegration)
JDBC模块:JDBC 模块提供了一个 JDBC 抽象层,它可以消除冗长的 JDBC 编码和解析数据库厂
商特有的错误代码。这个模块包含了 Spring 对 JDBC 数据访问进行封装的所有类。
**ORM 模块:**ORM 模块为流行的对象-关系映射 API,如 JPA、JDO、Hibernate、iBatis 等,提供了
一个交互层。利用 ORM 封装包,可以混合使用所有 Spring 提供的特性进行 O/R 映射,如前边提到的简单声明性事务管理。
**OXM 模块:**OXM 模块提供了一个对 Object/XML 映射实现的抽象层,Object/XML 映射实现包括
JAXB、Castor、XMLBeans、JiBX 和 XStream。
**JMS模块:**JMS(Java Messaging Service)模块主要包含了一些制造和消费消息的特性。
**Transaction 模块:**Transaction 模块支持编程和声明性的事务管理,这些事务类必须实现特定的接口,并且对所有的 POJO 都适用。
2.Web
Web 层包含了 Web、Servlet、Socket和 Porlet 模块
**Web 模块:**提供了基础的面向 Web 的集成特性。例如,多文件上传、使用 servlet listeners
初始化 IoC 容器以及一个面向 Web 的应用上下文。它还包含 Spring 远程支持中 Web
的相关部分。
**Web-Servlet 模块:**Web-Servlet 模块 web.servlet.jar:该模块包含 Spring 的 model-view-controller(MVC)实现。Spring 的 MVC 框架使得模型范围内的代码和 web forms 之间能够清楚地分离开来,并与 Spring 框架的其他特性集成在一起。
**Websocket模块:**提供 Websocket:功能
Web-Porlet 模块:提供了用于 Portlet 环境和 Web-Servlet 模块的 MVC 的实现。
3.AOP、 Aspects
**AOP:**提供了符合 AOP Alliance规范的面向切面的编程( aspect- oriented programming)
实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,
并且能动态地把这些功能添加到需要的代码中。这样各司其职,降低业务逻辑和通用
功能的耦合。
**Aspects:**提供了对 AspectJ的集成, AspectJ提供了比 Spring ASP更强大的功能。
4.Core Container (核心容器)
**Spring-beans:**提供了框架的基础部分,包括控制反转和依赖注入。其中 Bean Factoy是容器核心,本质是“エ厂设计模式”的实现,而且无需编程实现“单例设计模式”,单例完全由容器控制,而且提倡面向接口编程,而非面向实现编程。所有应用程序对象及对象间关系由框架管理,从而真正从程序逻辑中,把维护对象之间的依赖关系提取出来,所有这些依赖关系都由 Bean Factory来维护
**Spring-core:**核心工具类,封装了框架依赖的最底层部分,包括资源访问、类型转换及一些常用工具类
**Spring- Context:**以Core和 Beans为基础,集成 Beans模块功能并添加资源绑定、数据验证、国际化、 Java EE支持、容器生命周期、事件传播等。核心接口是Appllcationc ontext
**Spring-SpEL:**提供强大的表达式语言支持,支持访问和修改属性值、方法调用;支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring容器获取Bean,还支持列表投影、选择和一般的列表聚合等。
5.Test
Test模块: Spring支持Junit和 TestNG测试框架,而且还额外提供了一些基于 Spring的测式功能,比如在测试Web框架时,模拟HTTP请求的功能
Spring模块
Spring可以分为6⼤模块:
- Spring Core spring的核⼼功能:** IOC容器**, 解决对象创建及依赖关系
- Spring Web Spring对web模块的⽀持。
- 可以与struts整合,让struts的action创建交给spring
- spring mvc模式
- Spring DAO Spring 对jdbc操作的⽀持 【JdbcTemplate模板⼯具类】
- Spring ORM spring对orm的⽀持:
- 既可以与hibernate整合,【session】
- 也可以使⽤spring的对hibernate操作的封装
- Spring AOP 切⾯编程
- SpringEE spring 对javaEE其他模块的⽀持
下载地址:https://repo.spring.io/release/org/springframework/spring//
.dist后缀表示该文件夹下存放的是jar包,文档和项目;
.docs后缀表示该文件夹下存放相关文档,开发指南和API;
.schema里存放了spring4所用的xsd文件。
注:xsd文件即XML结构定义(XML schemas definition),它描述了XML文档的结构。
Spring容器基本使用及原理
IOC容器
IOC容器是具有依赖注入功能的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。我们需要使用的对象都由ioc容器进行管理,不需要我们再去手动通过new的方式去创建对象,由ioc容器直接帮我们组装好,当我们需要使用的时候直接从ioc容器中直接获取就可以了。
那么spring ioc容器是如何知道需要管理哪些对象呢?
需要我们给ioc容器提供一个配置清单,这个配置支持xml格式和java注解的方式,在配置文件中列出需要让ioc容器管理的对象,以及可以指定让ioc容器如何构建这些对象,当spring容器启动的时候,就会去加载这个配置文件,然后将这些对象给组装好以供外部访问者使用。这里所说的IOC容器也叫spring容器。
Bean概念
由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的,我们需要在配置文件中告诉spring容器需要创建哪些bean对象,所以需要先在配置文件中定义好需要创建的bean对象,这些配置统称为bean定义配置元数据信息,spring容器通过读取这些bean配置元数据信息来构建和组装我们需要的对象。
Spring容器使用步骤
- 引入spring相关的maven配置
- 创建bean配置文件,比如bean xml配置文件
- 在bean xml文件中定义好需要spring容器管理的bean对象
- 创建spring容器,并给容器指定需要装载的bean配置文件,当spring容器启动之后,会加载这些配置文件,然后创建好配置文件中定义好的bean对象,将这些对象放在容器中以供使用
- 通过容器提供的方法获取容器中的对象,然后使用
Spring容器对象
在Spring中总体来看可以通过四种⽅式来配置对象:
1.使⽤XML⽂件配置
2.使⽤注解来配置
3.使⽤JavaConfig来配置
4.groovy脚本 DSL
spring内部提供了很多表示spring容器的接口和对象,我们来看看比较常见的几个容器接口和具体的实现类。
BeanFactory接口【功能简单】
spring容器中具有代表性的容器就是BeanFactory接口,这个是spring容器的顶层接口,提供了容器最基本的功能。
HierarchicalBeanFactory:提供父容器的访问功能
ListableBeanFactory:提供了批量获取Bean的方法
AutowireCapableBeanFactory:在BeanFactory基础上实现对已存在实例的管理
常用的几个方法:
方法 | 说明 |
---|---|
getBean(String name) | 按bean的id或者别名查找容器中的bean |
getBean(String name, Class requiredType) | 这个是一个泛型方法,按照bean的id或者别名查找指定类型的bean,返回指定类型的bean对象 |
getBean(String name, Object… args) | 获取Bean |
getBean(Class requiredType) | 返回容器中指定类型的bean对象 |
ApplicationContext接口【功能强大,一般我们使用这个】
这个接口继承了BeanFactory接口,所以内部包含了BeanFactory所有的功能,并且在其上进行了扩展,增加了很多企业级功能,比如AOP、国际化、事件支持等等。。
ClassPathXmlApplicationContext
这个类实现了ApplicationContext接口,注意一下这个类名称包含了ClassPath Xml,说明这个容器类可以从classpath中加载bean xml配置文件,然后创建xml中配置的bean对象
简单的用 ApplicationContext 做测试的话 , 获得 Spring 中定义的 Bean 实例(对象) 可以用:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
如果是两个以上 , 可以使用字符串数组 :
ApplicationContext context = new ClassPathXmlApplicationContext
(new String[]{"applicationContext.xml","SpringTest.xml"});
或者可以使用通配符:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/*.xml");
Ioc创建对象方式
(1)IOC通过无参构造函数来创建对象, 通过set方法注入属性
idea2020.3.3版本创建一个简单的spring项目,xml格式定义bean
1.先创建一个普通的java项目
2.给这个普通的java项目添加框架
3.在添加依赖之前,先对maven进行配置,点击File—>Settings—>Build,Execution,Deployment—>Build Tools—>Maven
这是我的仓库配置
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>ui</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://uk.maven.org/maven2/</url>
</mirror>
<mirror>
<id>ibiblio</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://mirrors.ibiblio.org/pub/mirrors/maven2/</url>
</mirror>
<mirror>
<id>jboss-public-repository-group</id>
<mirrorOf>central</mirrorOf>
<name>JBoss Public Repository Group</name>
<url>http://repository.jboss.org/nexus/content/groups/public</url>
</mirror>
<!--访问慢的网址放入到后面-->
<mirror>
<id>CN</id>
<name>OSChina Central</name>
<url>http://maven.oschina.net/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
<mirror>
<id>net-cn</id>
<mirrorOf>central</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://maven.net.cn/content/groups/public/</url>
</mirror>
<mirror>
<id>JBossJBPM</id>
<mirrorOf>central</mirrorOf>
<name>JBossJBPM Repository</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases/</url>
</mirror>
</mirrors>
4.添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
5.在java目录下建立一个包,在包里建立一个主类Main和一个User类,在resources目录下建立一个applicationContext.xml文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPYuSQhG-1621778369394)(D:\桌面\书籍\自己的笔记\笔记一锅端\Spring\Spring\69bdab7749af659124067fff8bcc6fc8.png)]
建立后若出现Application context not configured for this file以下错误,说明没有配置该文件到项目中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDrPXbSy-1621778369394)(D:\桌面\书籍\自己的笔记\笔记一锅端\Spring\Spring\09cc25f3023387766ef255ae4227a671.png)]
解决方案:
点击File—>Project Structrue—>Modules—>Spring—>点击加号(+)—>把创建的文件勾上—>点击ok—>再点击AppLY—>再点击ok
applicationContext.xml文件一定要放在resources目录下,不放的话可能会出现
6.添加内容
(1)User类
package com.soft;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
@Getter
@Setter
@ToString
public class User {
private String name;
private int age;
//无参构造函数可省略,会默认帮你创建
public User() {
}
}
(2)Main类
reeewewewpackage com.soft;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
//创建Spring上下文(加载bean.xml)
ApplicationContext acx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取User实例
User user = acx.getBean("user",User.class);
//调用方法
System.out.println(user.toString());
}
}
也就是通过getBean(String name, Class requiredType)
(3)applicationContext.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理解为我们的javaBean,其中两个property标签即表示给User类中的name和age属性赋值!-->
<bean id="user" class="com.soft.User">
<property name="name" value="张三"/>
<property name="age" value="18"/>
</bean>
</beans>
运行结果:
(2)通过 IOC 创建 有参构造 的对象
Spring创建对象的方式:
通过下标 index
通过参数名 【推荐】name
通过参数类型 type
1.applicationContext.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理解为我们的javaBean,其中两个property标签即表示给User类中的name和age属性赋值!-->
<bean id="user" class="com.zsq.User">
<!--constructor-arg用于指定有参构造的值:
name:参数名称
value:参数值
index:参数索引,0表示第一个参数,1表示第二个参数
type:参数的类型
ref:引用外部的对象数据,即当插入的值是容器内其它bean的时候,这个值
为容器中对应bean的名称
-->
<constructor-arg value="22" name="id" index="0" type="int"/>
<constructor-arg value="张三" name="name" index="1" type="java.lang.String"/>
</bean>
</beans>
2.User类
package com.zsq;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
@Setter
@Getter
@ToString
public class User {
private String name;
private int id;
public User(int id, String name) {
System.out.println("User 对象创建:有参构造");
this.id = id;
this.name = name;
}
}
3.Main类
package com.zsq;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
//创建Spring上下文(加载bean.xml)
ApplicationContext acx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取User实例
User user = acx.getBean("user",User.class);
//调用方法
System.out.println(user.toString());
}
}
运行结果:
配置constructor-arg 节点的时候有一个属性 ref,表示引用外部的对象,
修改 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--我们可以将这个bean理解为我们的javaBean,其中两个property标签即表示给User类中的name和age属性赋值!-->
<bean id="user" class="com.zsq.User">
<!--constructor-arg用于指定有参构造的值:
name:参数名称
value:参数值
index:参数索引,0表示第一个参数,1表示第二个参数
type:参数的类型
ref:引用外部的对象数据
-->
<constructor-arg value="22" name="id" index="0" type="int"/>
<constructor-arg ref="str"/><!--ref:引用下面定义的字符串对象-->
</bean>
<!--定义一个字符串对象:用java 方法实现为 String str=new String("李四");-->
<bean id="str" class="java.lang.String">
<constructor-arg value="李四"/>
</bean>
</beans>
由于是有参构造,此时不需要set,我们把它们注释掉,其它不变
package com.zsq;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
//@Setter
//@Getter
@ToString
public class User {
private String name;
private int id;
public User(int id, String name) {
System.out.println("User 对象创建:有参构造");
this.id = id;
this.name = name;
}
}
运行结果:
Spring中xml的配置说明
bean标签
用该标记定义一个Bean的实例化信息,用以指导Bean工厂正确地进行Bean的生产与装配。由class属性指定类全名,由id(推荐使用)或name属性指定生成的Bean实例名称。init-method属性指定初始化时要调用的方法,destroy-method属性实例销毁时要调用的方法。**Bean实例的依赖关系可通过property子标记(set方式注入)或constructor-arg子标记(构造方式注入)来定义。**bean标记中的scope属性用以设定Bean实例的生成方式,当scope设置为singleton或默认时以单例模式生成,当scope设置为prototype时则以原型(多例)模式生成。
bean标签常用属性
属性 | 说明 |
---|---|
id | 是bean的唯一标识,IoC容器中bean的id标签不能重复,否则报错。用于指定Bean的名称,在Bean被依赖时使用,在获取Bean时使用等。将相当于要new对象的名字。 |
name | 用于指定Bean的别名,bean标签的name属性也是不能重复,且id和name属性也不能重复,name标签应该等同于id属性。 |
**class ** | 用来定义类的全限定名(包名+类名)。只有子类Bean不用定义该属性。 |
singleton(默认为“true”) | 定义Bean是否是Singleton(单例)。如果设为“true”, 表示为单例模式、则在BeanFactory作用范围内,只维护此Bean的一个实例。如果设为“flase”,表示原型模式 、Bean将是Prototype(原型)状态,BeanFactory将为每次Bean请求创建一个新的Bean实例。 |
depends-on | 用于指定当前Bean的依赖Bean,强制指定的Bean在当前Bean初始化之前先完成初始化。 |
init-method | 用于指定当前Bean的初始化方法,在Bean实例创建好后,首先会调用其指定名称的方法。 |
destory-method | 用于指定当前Bean的销毁方法,在Bean即将被销毁之前会自动调用该属性指定的方法。 |
lazy-init | 用于指定当前Bean的初始化时间,若值为true表示在初次调用时才会自动创建实例并初始化,false表示在IoC容器创建的时候就会完成创建和初始化。 |
autowire | 用于指定当前Bean的依赖关系的自动注入方式 , autowire(自动装配,默认为“default”)、 |
scope | Bean的作用范围 |
dependency-check | 用于指定Bean的依赖检查模式,检查依赖关系是否完整,与自动装配合用。 |
factory-method | 工厂方法属性,通过该属性,我们可以调用一个指定的静态工厂方法,创建bean实例 |
factory-bean | 就是生成bean的工厂对象,factory-bean属性和factory-method属性一起使用,首先要创建生成bean的工厂类和方法。 |
autowire值
no : 默认值,不进行自动装配
byName : 根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配
byType值:表示通过class指定的类型来自动装配、如果存在多个该类型bean,那么抛出异常,并指出不能使用byType方式进行自动装配;
如果没有找到相匹配的bean,则什么事都不发生,也可以通过设置dependency-check="objects" 让Spring抛出异常。
constructor值:表示使用构造函数的参数进行自动装配(参数的类型匹配)。如果容器中没有找到与构造器参数类型一致的bean, 那么抛出异常
autodetect值:表示自动进行选择匹配方式,首先进行constructor自动装配,若不存在构造方法则使用byType方式进行自动装配;
如果发现默认的构造器,那么将使用byType方式,否则采用 constructor。
default:由上级标签的default-autowire属性确定。
**scope值 : **
singleton值: 默认值是单例的。只有一个实例对象,就断创建多个对象,这些对象也是同一个对象,地址相同。此外,singleton类型的
bean定义,从容器启动,到他第一次被请求而实例化开始,只要容器不销毁或退出,该类型的bean的单一实例就会一直存活。
表示整个IOC容器共享一个Bean,也就是说每次说每次通过getBean获取的bean都是同一个。
prototype值: 多例的 。每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的
bean实例。
request值:web项目中spring创建一个bean对象,将对象存到request域中。Spring容器,XmlWebApplicationContext 会为每个HTTP请求创建
一个全新的RequestPrecessor对象,当请求结束后,该对象的生命周期即告结束,如同Java web中request的生命周期。当同时有100个HTTP请求
进来的时候,容器会分别针对这10个请求创建10个全新的RequestPrecessor实例,且他们相互之间互不干扰。
session值:每次会话请求对应一个bean实例。比如存放登录信息。
globalSession:web项目中,应用在prolet环境,如果没有prolet环境那么globalSession相当于session。
dependency-check值:
simple值:表示针对基本类型、字符串、集合进行依赖检查
object值:表示对引用对象进行依赖检查
all值:表示对基本类型、字符串、集合、引用对象全部进行依赖检查
none值:表示不进行任何依赖检查,默认情况。
bean的子标签property标签
property:它是bean标记的子标记用以调用Bean实例中的相关Set方法完成属性值的赋值,从而完成依赖关系的注入
属性 | 说明 |
---|---|
name | 用于指定属性的名称,与类中的set方法后方的名称一致 |
value | 用于指定该属性的值,用于指定的值是基本类型、字符串类型 |
ref | 用于指定该属性的值,用于指定的值是引用对象类型(即其他的Bean),ref后面的值为另一个Bean的id |
bean的子标签constructor-arg标签:
它是bean标记的子标记,用以传入有参构造参数进行实例化,这也是注入依赖关系的一种常见方式
constructor-arg标签属性:
name | 通过参数名找到参数列表中对应参数 |
---|---|
index | 通过参数在参数列表中的索引找到参数列表中对应参数,index从0开始: |
type | 通过参数数据类型找到参数列表中对应参数 |
value | 设置参数列表参数对应的值,用于设定基本数据类型和String类型的数据 |
ref | 如果参数值为非基本数据类型,则可通过ref为参数注入值,其值为另一个bean标签id或name属性的属性值 |
constructor-arg的子标签:
ref子标签 | 对应ref属性,该标签name属性的属性值为另一个bean标签id或name属性的属性值; |
---|---|
value子标签 | 对应value属性,用于设置基本数据类型或String类型的参数值; |
list子标签 | 为数组或List类型的参数赋值 |
set子标签 | 为Set集合类型参数赋值 |
map子标签 | 为Map集合类型参数赋值 |
props子标签 | 为Properties类型的参数赋值 |
alias标签
alias元素也可以用来给某个bean定义别名,语法:
<alias name="需要定义别名的bean" alias="别名" />
如:
<bean id="user6" class="com.javacode2018.lesson001.demo2.UserModel" />
<alias name="user6" alias="user6_1" />
import标签
当我们的系统比较大的时候,会分成很多模块,每个模块会对应一个bean xml文件,我们可以在一个总的bean xml中对其他bean xml进行汇总,相当于把多个bean xml的内容合并到一个里面了,可以通过import元素引入其他bean配置文件
语法:
<import resource="其他配置文件的位置" />
如:
<?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-4.3.xsd">
<import resource="user.xml" />
<import resource="order.xml" />
</beans>
AnnotationConfigApplicationContext类
这个类也实现了ApplicationContext接口,注意其类名包含了Annotation和config两个单词,上面我们有说过,bean的定义支持xml的方式和注解的方式,**当我们使用注解的方式定义bean的时候,就需要用到这个容器来装载了,这个容器内部会解析注解来构建构建和管理需要的bean。**注解的方式相对于xml方式更方便一些,也是我们比较推荐的方式,通过注解来配置信息就是为了简化IOC容器的配置,注解可以把对象添加到IOC容器中、处理对象依赖关系
什么是注解
(1)注解是代码特殊标记,格式:@注解名称(属性名称=属世值,属性名称=属性值…)
java内置了三种标准注解及四种元注解。
三种标准注解,定义在java.lang中:
四种元注解
注解的优点
(1)可以提供完整的描述程序所需要的信息(当创建描述性质的类或接口时,一旦包含重复性的工作,就可考虑使用注解来简化);
(2)使用注解,代码变的更加干净易读;
(3)可以编译期类型检查。
注解语法
@注解名称(参数1=值1,参数2=值2,参数n=参数n)
目标对象
Spring原始注解
Component 表示该组件用于Spring生成一个bean ->Component:组件
@Component("user")
Controller 表示该控制器用于Spring生成一个bean
@Controller("user")
Service 表示该业务逻辑类用于Spring生成一个bean
@Service("user")
Repository 表示该Dao类用于Spring生成一个bean ->repository:贮藏室 数据封装基地
@Resipotory("user")
Autowired 表示自动封装该属性,或者字段(可以省略set方法,根据字段Field即可,如果该字段是一个Bean,将通过Spring容器中查找该类型的Bean对
象7,用于封装该注解字段),
@Autowired 根据该注解指定的字段,查询Spring容器中对应的bean对象,分配给当前类作为成员.
Autowired默认是根据类型依赖注入.ByType,如果Autowired注解的是一个字段,并不能完整的体现面向对象的思想,最好是注解在set方法上.
Qualifier 表示根据提供的修饰语 @Qualifier('xxx') -> 匹配该xxx属性,若要使用该注解,必须配合AutoWire一起使用,而Autowire可以单独使用
@Autowire @Qualifier("user") 封装user注解的bean为本类对象的成员,根据名称注入 ByName,设置在字段上. @Qualifier常常使用在参数的
赋值上
如: String getUsername(@Qualifier("user")User user){}
Resource 是Aotuwire注解和Qualifier注解的结合体.@Resource("user"),标注为user的bean
@Resource("user") 封装user注解的bean为奔雷对象的成员 == @Autowire @Qualifier
默认根据名称依赖注入,默认Resource() = Resource like Resource(name = "XXX") ->体现面向对象思想应该设置在字段上
可以手动设置为根据类型注入Resource(type = User.class) ->体现面向对象思想应该设置在set方法上
Value @Value("xxx");为修饰的字段赋值;通常配合SpEL(Spring Expression Language)使用
@Value("${jdbc.url}") private String url; 为url赋值 -> (Spring中引入的properties后缀文件中的jdbc.url对应的值)
->或者{PropertySrouce注解中的jdbc.url值}
Scope 常用 : @Scope("singleton") 声明从spring容器获得的该类的bean有且只有一个(单例模式)
@Scope("prototype") 声明从spring容器获得的该类的bean有多个(标准Bean模式)
PostConstruct 注解使用在用户定义的初始化方法前,声明该方法是该类的初始化方法@PostConstruct :意为执行在构造方法后,构造方法创建对象,
对象调用该初始化方法
PreDestroy 注解使用在用户自定义的结束方法前,声明该方法是该类的结束方法,当前对象被销毁前调用该方法@PreDestroy ;Spring容器被销毁的时
候,执行.
Order 注入对象引用的集合的时候使用:@Order(1) @Order(2) 表示该类生成的bean将会存储在某个List<该类型>中,并按照顺序插入List
如果是Map<String,T> 那么order的顺序没有任何关系,但是该Map必须定义为<String,T> String接收对应对象的id值(id唯一 -> key唯一);
此处的List和Map可以通过@Autowired或@Resource注入;
for example -> UserDao -> UserDaoImpl1(@Repository("userDao1") @Order(2)) and UserDaoImpl2(@Repository("userDao2")
@Order(1))
(@Resource)private List<UserDao> list; -> list.get(0) ->userDao2 list.get(1) -> userDao1
(@Autowired)private Map<String,UserDao> map; -> map{第一个("userDao1" = userDao1), 第二(个"userDao2" = userDao2)}
使⽤注解来配置
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
2.开启注解扫描器
(1)还使用一点xml。
<context:component-scan base-package=”XX.XX”/>
<!--如果想要指定多个包,可以用逗号隔开包名-->
<context:component-scan base-package=”a,b,c”/>
(2)完全不用xml,通过使用**@Configuration**
/*@Configuration标注在类上,相当于把该类作为spring的xml配置文件中的<beans>,
作用为:配置spring容器(应用上下文)*/
@Configuration
/*@ComponentScan("包名")不使用也能用*/
@ComponentScan("Pojo")
public class ConfigDemo {
/**@Bean注册一个bean,就相当于我们之前写的一个bean标签.这个方法的名字,就相当丁bean
* 标签中的id属性。这个方法的返回值,就相当bean标签中的class属性*/
@Bean
public User getUser(){
return new User();
}
}
实列1:使用xml文件加注解
这是需要建立的包和类以及配置文件
Application.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="Dao,Controller,Service,Main"/>
</beans>
UserController类
package Controller;
import Service.UserService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* @author 25959
*/
@Controller
public class UserController {
@Resource(name = "userService")
private UserService userService;
public void execute() {
userService.save();
}
}
User类
package Dao;
import org.springframework.stereotype.Repository;
/**
* @author 25959
*/
@Repository
public class User {
public void save() {
System.out.println("保存⽤户");
}
}
App类
package Main;
import Controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class App {
public static void main(String[] args) {
// 创建容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("Application.xml");
UserController userController =ac.getBean("userController", UserController.class);
userController.execute();
}
}
UserService类
package Service;
import Dao.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author 25959
*/
@Service
public class UserService {
@Resource(name = "user")
private User user;
public void save() {
user.save();
}
}
pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled7</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</project>
运行结果
实列2:完全使用注解
这是需要建立的包和类以及配置文件
ConfigDemo类
package Config;
import Pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @author 25959
*/
//这个也会Spring谷器托管,注册到谷器中,因为他本米就是一个@Component
//@configuration代表这是一个虎置类,就和我们之前看的beans.xml
@Configuration
/*@ComponentScan("Pojo")不使用也能用*/
@ComponentScan("Pojo")
public class ConfigDemo {
/**注册一个bean,就相当于我们之前写的一个bean标签.这个方法的名字,就相当丁bean标签中的id属性
这个方法的返回值,就相当bean标签中的class属性*/
@Bean
public User getUser(){
return new User();
}
}
User类
package Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
/**
* @author 25959
*/
@Setter
@Getter
@ToString
public class User {
@Value("小阿giao")
private String name;
}
Main类
package Main;
import Config.ConfigDemo;
import Pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的cLass对象加载
ApplicationContext applicationContext=new AnnotationConfigApplicationContext(ConfigDemo.class);
User getUser=(User) applicationContext.getBean("getUser");
System.out.println(getUser.getName());
}
}
运行结果:
xml与注解:
- xml 更加万能,适用于任何场合!维护简单方便
- 注解不是自己类使用不了,维护相对复杂!
xml与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
依赖注入方式
(1)构造方法注入(Construct注入)
- 根据构造器参数索引注入(index)
参数位置的注入对参数顺序有很强的依赖性,若构造函数参数位置被人调整过,会导致注入出错。
- 根据构造器参数类型注入(type)
实际上按照参数位置或者按照参数的类型注入,都有一个问题,很难通过bean的配置文件,知道这个参数是对应类中的那个属性的,代码的可读性不好
- 根据构造器参数名称注入(name)
如果使用构造方法注入推荐使用构造器名称注入
前面讲过了。可参考前面的例子
(2)setter注入(无参构造property注入)
通常情况下,我们的类都是标准的javabean,javabean类的特点:
-
属性都是private访问级别的
-
属性通常情况下通过一组setter(修改器)和getter(访问器)方法来访问
-
setter方法,以 set 开头,后跟首字母大写的属性名,如:setUserName,简单属性一般只有一个方法参数,方法返回值通常为void;
-
getter方法,一般属性以 get 开头,对于boolean类型一般以 is 开头,后跟首字母大写的属性名,如:getUserName,isOk;
1.建立以下的类和配置文件
applicationContext文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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理解为我们的javaBean,其中两个property标签即表示给User类中的name和age属性赋值!-->
<bean id="address" class="Pojo.Address">
<property name="address" value="南京"/>
</bean>
<bean id="student" class="Pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="奥力给"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--数组-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--List-->
<property name="hobbies">
<list>
<value>听歌</value>
<value>代码</value>
<value>看电影</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="12345678967655"/>
<entry key="银行卡" value="56782645335356"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!-- properties-->
<property name="info">
<props>
<prop key="学号">66666</prop>
<prop key="性别">男</prop>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
</beans>
Address类
package Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
@Getter
@Setter
@ToString
public class Address {
private String address;
}
Student类
package Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* @author 25959
*/
@Getter
@Setter
@ToString
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String>card;
private Set<String>games;
private String wife;
private Properties info;
}
Main类
package com.soft;
import Pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
//创建Spring上下文(加载bean.xml)
ApplicationContext acx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取User实例
Student student = acx.getBean("student", Student.class);
//调用方法
System.out.println(student.toString());
}
}
运行结果:
上面介绍的都是注入普通类型的对象,都是通过value属性来设置需要注入的对象的值的,value属性的值是String类型的,spring容器内部自动会将value的值转换为对象的实际类型。
优缺点:
setter注入相对于构造函数注入要灵活一些,构造函数需要指定对应构造函数中所有参数的值,而setter注入的方式没有这种限制,不需要对所有属性都进行注入,可以按需进行注入。
(3)p命名空间和c命名空间
在通过构造方法或set方法给bean注入关联项时通常是通过constructor-arg元素和property元素来定义的。在有了p(property)命名空间和c(constructor-arg)命名空间时我们可以简单的把它们当做bean的一个属性来进行定义。
- p命名空间(就是简化的无参构造setter注入)
使用p命名空间时需要先声明使用对应的命名空间,即在beans元素上加入xmlns:p=“http://www.springframework.org/schema/p”。
建立如图的文件
application.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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="student" class="Pojo.Student" p:name="张三" p:age="19"/>
<!-- <bean id="student" class="Pojo.Student">-->
<!-- <property name="name" value="张三"/>-->
<!-- <property name="age" value="19"/>-->
<!-- </bean>-->
<!--以上两种效果是等价的-->
</beans>
Student类
package Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
@Setter
@Getter
@ToString
public class Student {
private String name;
private int age;
}
Main类
package com.zsq;
import Pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
Student student=applicationContext.getBean("student",Student.class);
System.out.println(student);
}
}
运行结果;
- c命名空间(就是有参构造的constructor-arg注入)
使用c命名空间之前也需要通过**xmlns:c=”http://www.springframework.org/schema/c”**进行声明。
修改配置文件application.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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="student" class="Pojo.Student" c:age="16" c:name="小阿giao"/>
<!--<bean id="student" class="Pojo.Student">-->
<!-- <constructor-arg name="name" value="小阿giao"/>-->
<!-- <constructor-arg name="age" value="16"/>-->
<!--</bean>-->
<!--这两中是等价的-->
</beans>
修改Student类:
package Pojo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @author 25959
*/
@Setter
@Getter
@ToString
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
运行结果:
(4)xml形式的自动装配
Spring 容器可以在不使用< constructor-arg >和< property > 元素的情况下自动装配相互协作的 bean 之间的关系,助于减少编写一个大的基于 Spring 的应用程序的 XML 配置的数量
使用< bean >元素的 autowire 属性为一个 bean 定义指定自动装配模式。
- byName:由属性名自动装配
当一个bean节点带有 autowire byName的属性时,将查找其类中所有的set方法名,获得将set去掉并且首字母小写的字符串,然后去spring容器中寻找是否有此字符串名称id的对象。如果有,就取出注入;如果没有,就报空指针异常。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2uwKDkJZ-1621778369421)(9047a093d40a5df49b12128083fc5f44.png)]
建立以下文件
application.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:c="http://www.springframework.org/schema/c"
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="cat" class="Pojo.Cat"/>
<bean id="dog" class="Pojo.Dog"/>
<bean id="people" class="Pojo.People" autowire="byName"/>
<property name="name" value="李保国"/>
</bean>
<!-- <bean id="people" class="Pojo.People">-->
<!-- <property name="name" value="李保国"/>-->
<!-- <property name="cat" ref="cat"/>-->
<!-- <property name="dog" ref="dog"/>-->
<!-- </bean>-->
<!--以上都是等价的-->
</beans>
Cat类:
package Pojo;
/**
* @author 25959
*/
public class Cat {
public void shout(){
System.out.println("miao~");
}
}
Dog类:
package Pojo;
/**
* @author 25959
*/
public class Dog {
public void shout(){
System.out.println("wang~");
}
}
People类:
package Pojo;
/**
* @author 25959
*/
public class People {
private String name;
private Dog dog;
private Cat cat;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
运行结果:
- byType :由类型自动装配
该模式表示根据Property的数据类型(Type)自动装配,Spring会总动寻找与属性类型相同的bean,若一个bean的数据类型,兼容另一个bean中Property的数据类型,则自动装配。
注意:使用byType首先需要保证同一类型的对象,在spring容器中唯一,若不唯一会报不唯一的异常。
若使用自动装配推荐使用byname
(5)[重点]基于注解的注入(接口注入)
前面讲解AnnotationConfigApplicationContext类时已经讲解了一点。
1.@Configuration注解
@Configuration这个注解可以加在类上,让这个类的功能等同于一个bean xml配置文件,如下:
@Configuration
public class ConfigBean {
}
上面代码类似于下面的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-4.3.xsd">
</beans>
要通过 AnnotationConfigApplicationContext 来加载 @Configuration 修饰的类,如下:
AnnotationConfigApplicationContext context = new
AnnotationConfigApplicationContext(ConfigBean.class);
//(new 后面要接@Configuration下的类名)
此时ConfigBean类中没有任何内容,相当于一个空的xml配置文件,此时我们要在ConfigBean类中注册bean,那么我们就要用到@Bean注解了
2.@Bean注解
这个注解类似于bean xml配置文件中的bean元素,用来在spring容器中注册一个bean。@Bean注解用在方法上,表示通过方法来定义一个bean,默认将方法名称作为bean名称,类名作为bean定义类的全限定名,将方法返回值作为bean对象,注册到spring容器中。主要用在@Configuration注解的类里,也可以用在@Component注解的类里
如:
@Bean
public User user1() {
return new User();
}
//此时user1相当于<bean id="user1"/>
//此时User相当于<bean class="包名.User"/>
@Bean的属性
value | 字符串数组,第一个值作为bean的名称,其他值作为bean的别名 |
---|---|
name | bean名称,如果不写会默认为注解的方法名称 |
autowire | 自定装配默认是不开启的,建议尽量不要开启,因为自动装配不能装配基本数据类型、字符串、数组等,这是自动装配设计的局限性,并且自动装配不如依赖注入精确,已经过期了,不建议使用了 |
initMethod | bean的初始化之前的执行方法,该参数一般不怎么用,因为完全可以在代码中实现 |
destroyMethod | 默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果你不想执行该方法,则添加@Bean(destroyMethod="")来防止出发销毁方法; |
如:
@Configuration
public class ConfigBean {
//bean名称为方法默认值:user1
@Bean
public User user1() {
return new User();
}
//bean名称通过value指定了:user2Bean
@Bean("user2Bean")
public User user2() {
return new User();
}
//bean名称为:user3Bean,2个别名:[user3BeanAlias1,user3BeanAlias2]
@Bean({"user3Bean", "user3BeanAlias1", "user3BeanAlias2"})
public User user3() {
return new User();
}
}
@Bean 注解还可以与其他注解一起使用
- @Scope 注解
在Spring中对于bean的默认处理都是单例的,我们通过上下文容器.getBean方法拿到bean容器,并对其进行实例化,这个实例化的过程其实只进行一次,即多次getBean 获取的对象都是同一个对象,也就相当于这个bean的实例在IOC容器中是public的,对于所有的bean请求来讲都可以共享此bean。
如果我们不想把这个bean被所有的请求共享或者说每次调用我都想让它生成一个新的bean实例,该怎么实现呢?
如:新建一个ConfigScope配置类,用来定义多例的bean
@Configuration
public class ConfigScope {
/**
* 为myBean起两个名字,b1 和 b2
* @Scope 默认为 singleton,但是可以指定其作用域
* prototype 是多例的,即每一次调用都会生成一个新的实例。
*/
@Bean({"b1","b2"})
@Scope("prototype")
public MyBean myBean(){
return new MyBean();
}
}
- @Scope属性
singleton | 默认单例的bean定义信息,对于每个IOC容器来说都是单例对象 |
---|---|
prototype | bean对象的定义为任意数量的对象实例 |
request | bean对象的定义为一次HTTP请求的生命周期,也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅仅在web-aware的上下文中有效 |
session | bean对象的定义为一次HTTP会话的生命周期。仅仅在web-aware的上下文中有效 |
application | bean对象的定义范围在ServletContext生命周期内。仅仅在web-aware的上下文中有效 |
websocket | bean对象的定义为WebSocket的生命周期内。仅仅在web-aware的上下文中有效 |
singleton和prototype 一般都用在普通的Java项目中,而request、session、application、websocket都用于web应用中。
- @Primary 注解
指示当多个候选者有资格自动装配依赖项时,应优先考虑bean。此注解在语义上就等同于在Spring XML中定义的bean 元素的primary属性。注意: 除非使用component-scanning进行组件扫描,否则在类级别上使用@Primary不会有作用。如果@Primary 注解定义在XML中,那么@Primary 的注解元注解就会忽略,相反的话就可以优先使用。
@Primary 的两种使用方式:
-
与@Bean 一起使用,定义在方法上,方法级别的注解
-
与@Component 一起使用,定义在类上,类级别的注解
@Configuration
public class AppConfigWithPrimary {
@Bean
public MyBean myBeanOne(){
return new MyBean();
}
@Bean
@Primary
public MyBean myBeanTwo(){
return new MyBean();
}
}
上面代码定义了两个bean ,其中myBeanTwo 由@Primary 进行标注,表示它首先会进行注册
spring中,类上加不加@Configuration注解,有什么区别?
-
有没有@Configuration注解,@Bean都会起效,都会将@Bean修饰的方法作为bean注册到容器中
-
两个内容的第一行有点不一样,被@Configuration修饰的bean最后输出的时候带有
EnhancerBySpringCGLIB 的字样,而没有@Configuration注解的bean没有Cglib的字样;有
EnhancerBySpringCGLIB 字样的说明这个bean被cglib处理过的,变成了一个代理对象。
总结:
1.@Configuration注解修饰的类,会被spring通过cglib做增强处理,通过cglib会生成一个代理对象,代理会拦截所有被@Bean注解修饰的方法,可以确保一些bean是单例的
不管@Bean所在的类上是否有@Configuration注解,都可以将@Bean修饰的方法作为一个
bean注册到spring容器中
3.@Autowired
Autowired默认是根据类型依赖注入.ByType,如果Autowired注解的是一个字段(字段(field),通常叫做“类成员”,或 “类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。),并不能完整的体现面向对象的思想,最好是注解在set方法上.使用Autowired 我们可以不用编写Set方法了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8XXo7UQy-1621778369425)(838b142c282f000e898fc2f18ea997d4.png)]
建立以下文件:
application.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="Pojo"/>
<bean id="cat" class="Pojo.Cat"/>
<bean id="dog" class="Pojo.Dog"/>
<bean id="people" class="Pojo.People" autowire="byName">
<property name="name" value="哈哈"/>
</bean>
</beans>
Dog类
package Pojo;
/**
* @author 25959
*/
public class Dog {
public void shout(){
System.out.println("wang~");
}
}
Cat类
package Pojo;
/**
* @author 25959
*/
public class Cat {
public void shout(){
System.out.println("miao~");
}
}
People类
package Pojo;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @author 25959
*/
public class People {
private String name;
@Autowired
private Dog dog;
@Autowired
private Cat cat;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
Main类
package com.zsq;
import Pojo.People;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 25959
*/
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("application.xml");
People people =applicationContext.getBean("people", People.class);
people.getDog().shout();
people.getCat().shout();
System.out.println(people);
}
}
运行结果:
深入理解Java中的字段与属性的区别
1、Java中的属性和字段有什么区别?
答:Java中的属性(property),通常可以理解为get和set方法。
而字段(field),通常叫做“类成员”,或 "类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。
2、属性和字段详解
类成员(字段),通常是在类中定义的类成员变量,例如:
public class A{
private String s = “123”;
}
我们可以说A类中有一个成员变量叫做s,A类有一个字段s 。字段一般用来承载数据,所以为了安全性,一般定义为私有的。
字段和常量描述了类的数据(域),当这些数据的某些部分不允许外界访问时,
根据 “对象封装” 的原则,应尽量避免将一个类型的字段以公有方式提供给外部。除了final修饰的常量。
一般将其设置为private类型。既然是私有,那外界怎么访问呢? 当然是通过Java的属性方法!
4.@Qualifier
@Qualifier限定哪个bean应该被自动注入。当Spring无法判断出哪个bean应该被注入时,@Qualifier注解有助于消除歧义bean的自动注入
5.@Inject
由JSR-330提供
@Inject支持构造函数、方法和字段注解,也可能使用于静态实例成员。可注解成员可以是任意修饰符(private,package-private,protected,public)。
注入顺序:构造函数、字段,然后是方法。父类的字段和方法注入优先于子类的字段和方法,同一类中的字段和方法是没有顺序的。
@Inject注解的构造函数可以是无参或多个参数的构造函数。@Inject每个类中最多注解一个构造函数。
-
在字段注解:
用@Inject注解
● 字段不能是final的
● 拥有一个合法的名称 -
在方法上注解:
● 用@Inject注解
● 不能是抽象方法
● 不能声明自身参数类型
● 可以有返回结果
● 拥有一个合法的名称
● 可以有0个或多个参数 -
构造函数注解:
@Inject public House(Person owner) { System.out.println("---这是房屋构造函数---"); this.owner = owner; }
-
字段注解:
@Inject private Person owner;
-
方法注解:
@Inject public void setOwner(Person owner) { this.owner = owner; }
6.@Resource
是JSR-250规范定义的注解
@Resource默认按byName自动注入。
name | spring将name属性解析为bean的名字name不是id |
---|---|
type | type属性则被解析为bean的类型type |
@Resource依赖注入时查找bean的规则:
1.既不指定name属性,也不指定type属性,则自动按byName方式进行查找。如果没有找到符合的bean,则回退为一个原始类型进行查找,如果找到就注入。
2.只是指定了@Resource注解的name,则按name后的名字去bean元素里查找有与之相等的name属性的bean。
3.只指定@Resource注解的type属性,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4.既指定了@Resource的name属性又指定了type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
@Autowired + @Qualifier(“名称”):与@Resource(name=“名称”) 将按照名称自动注入等价
7.@Value
注入配置文件中的内容。只要是spring的注解类(service,Component, dao等)中都可以。
- @Value("")
@Value("张三")
private String name;
@Value("classpath:/person.properties")
private Resource file;
@Value("http://www.baidu.com")
private Resource testUrl;
- @Value("#{}")
@Value("#{}") 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法,也可以表示常量。
当bean通过@Value(“#{}”) 获取其他bean的属性,或者调用其他bean的方法时,只要该bean (Beab_A)能够访问到被调用的bean(Beab_B),即要么Beab_A 和Beab_B在同一个容器中,或者Beab_B所在容器是Beab_A所在容器的父容器。
@Value("#{T(java.lang.Math).random() * 100}")
private Integer age;
@Value("#{systemProperties['os.name']}")
private String osName;
@Value("#{1}")
private int number; //获取数字 1
- @Value("${}")
通过@Value("${}") 可以获取对应属性文件中定义的属性值。
@Value("${nick.name}")
private String nickName;
@ComponentScan、@ComponentScans详解
到目前为止,介绍了2种注册bean的方式:
- xml中bean元素的方式
- @Bean注解标注方法的方式
通常情况下,项目中大部分类都需要交给spring去管理,按照上面这2种方式,代码量还是挺大的。为了更方便bean的注册,Spring提供了批量的方式注册bean,方便大量bean批量注册,spring中的@ComponentScan就是干这个事情的。
8.@ComponentScan
@ComponentScan用于批量注册bean。
这个注解会让spring去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean注册到spring容器容器中。
@ComponentScan常用参数
value | 指定需要扫描的包 |
---|---|
basePackages | 作用同value;value和basePackages不能同时存在设置,可二选一 |
basePackageClasses | 指定一些类,spring容器会扫描这些类所在的包及其子包中的类,如:basePackageClasses = Car.class(这里注意basePackageClasses属性是一个数组,可以同时配置多个class) |
nameGenerator | 自定义bean名称生成器 |
resourcePattern | 需要扫描包中的那些资源,默认是:**/*.class,即会扫描指定包中所有的 |
useDefaultFilters | 对扫描的类是否启用默认过滤器,默认为true |
includeFilters | 过滤器:用来配置被扫描出来的那些类会被作为组件注册到容器中 |
excludeFilters | 过滤器,和includeFilters作用刚好相反,用来对扫描的类进行排除的,被排除的 类不会被注册到容器中 |
lazyInit | 是否延迟初始化被注册的bean |
@ComponentScan工作的过程:
- Spring会扫描指定的包,且会递归下面子包,得到一批类的数组
- 然后这些类会经过上面的各种过滤器,最后剩下的类会被注册到容器中
所以玩这个注解,主要关注2个问题:
第一个:需要扫描哪些包?通过 value、backPackages、basePackageClasses 这3个参数来控制
第二:过滤器有哪些?通过 useDefaultFilters、includeFilters、excludeFilters 这3个参数来
控制过滤器
默认情况下,任何参数都不设置的情况下,此时,会将**@ComponentScan修饰的类所在的包作为扫描包;默认情况下useDefaultFilters为true,这个为true的时候,spring容器内部会使用默认过滤器,规则是:凡是类上有 @Repository、@Service、@Controller、@Component 这几个注解中的任何一个的,那么这个类就会被作为bean注册到spring容器中**,所以默认情况下,只需在类上加上这几个注解中的任何一个,这些类就会自动交给spring容器来管理了
注意:
指定包名的方式扫描存在的一个隐患,若包被重名了,会导致扫描会失效,一般情况下面我们使用basePackageClasses的方式来指定需要扫描的包,这个参数可以指定一些类型,默认会扫描这些类所在的包及其子包中所有的类,这种方式可以有效避免这种问题。
9.@Component、@Repository、@Service、@Controller
这几个注解都是spring提供的
@Component这个注解可以用在任何类型上面。
通常情况下将这个注解用在类上面,标注这个类为一个组件,默认情况下,被扫描的时候会被作为bean注册到容器中。
value参数:被注册为bean的时候,用来指定bean的名称,如果不指定,默认为类名首字母小写。如:类UserService对应的beanname为userService
这4个注解本质上是没有任何差别,都可以用在类上面,表示这个类被spring容器扫描的时候,可以作为一个bean组件注册到spring容器中。
spring容器中对这4个注解的解析并没有进行区分,统一采用 @Component 注解的方式进行解析,所以这几个注解之间可以相互替换。
spring提供这4个注解,是为了让系统更清晰,通常情况下,系统是分层结构的,多数系统一般分为controller层、service层、dao层。
@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。
@Import批量注册bean
10.@Import
到目前,我们知道的批量定义bean的方式有2种:
- @Configuration结合@Bean注解的方式
- @CompontentScan扫描包的方式
如果需要注册的类是在第三方的jar中,那么我们如果想注册这些bean有2种方式:
- 通过@Bean标注方法的方式,一个个来注册
- @CompontentScan的方式:默认的@CompontentScan是无能为力的,默认情况下只会注册。@Compontent标注的类,此时只能自定义@CompontentScan中的过滤器来实现了
这2种方式都不是太好,每次有变化,调整的代码都比较多。
如果我们只想使用其中几个模块的配置类,怎么办?
通常我们的项目中有很多子模块,可能每个模块都是独立开发的,最后通过jar的方式引进来,每个模块中都有各自的@Configuration、@Bean标注的类,或者使用@CompontentScan标注的类,被@Configuration、@Bean、@CompontentScan标注的类,我们统称为bean配置类,配置类可以用来注册bean,此时如果我们只想使用其中几个模块的配置类,怎么办?
@Import可以很好的解决这2个问题
@Import使用
作用就是和xml配置的import标签作用一样,允许通过它引入@Configuration标注的类 , 引入ImportSelector接口和ImportBeanDefinitionRegistrar接口的实现,也包括 @Component注解的普通类。
总的来说:@Import可以用来批量导入需要注册的各种类,如普通的类、配置类,完后完成普通类和配置类中所有bean的注册。
@Import可以使用在任何类型上,通常情况下,类和注解上用的比较多
value参数:一个Class数组,设置需要导入的类,可以是@Configuration标注的列,可以是
ImportSelector接口或者ImportBeanDefinitionRegistrar接口类型的,或者需要导入的普通组件
类。
使用步骤
- 将@Import标注在类上,设置value参数
- 将@Import标注的类作为AnnotationConfigApplicationContext构造参数创建
AnnotationConfigApplicationContext对象 - 使用AnnotationConfigApplicationContext对象
@Import的value常见的有5种用法
- value为普通的类
- value为@Configuration标注的类
- value为@CompontentScan标注的类
- value为ImportBeanDefinitionRegistrar接口类型
- value为ImportSelector接口类型
- value为DeferredImportSelector接口类型
总结:
1、给容器中注入组件
(1)包扫描+组件标注注解
@Component:泛指各种组件
@Controller、@Service、@Repository都可以称为@Component。
@Controller:控制层
@Service:业务层
@Repository:数据访问层
(2)@Bean
导入第三方包里面的注解
(3)@Import
@Import(要导入到容器中的组件);
2、注入bean的注解
@Autowired:由bean提供
@Autowired可以作用在变量、setter方法、构造函数上; 有个属性为required,可以配置为false; @Inject:由JSR-330提供
@Inject用法和@Autowired一样。 @Resource:由JSR-250提供
@Autowired、@Inject是默认按照类型匹配的,@Resource是按照名称匹配的,@Autowired如果需要按照名称匹配需要和@Qualifier一起使用,@Inject和@Name一起使用。
@Primary
让spring进行自动装配的时候,默认使用首选的bean,和@Qualifier一个效果。
代理模式:静态代理和动态代理
Spring中有个非常重要的知识点,AOP,即面相切面编程,spring中提供的一些非常牛逼的功能都是通 过aop实现的,比如下面这些大家比较熟悉的功能
spring事务管理:@Transactional
spring异步处理:@EnableAsync
spring缓存技术的使用:@EnableCaching
spring中各种拦截器:@EnableAspectJAutoProxy
大家想玩转spring,成为一名spring高手,aop是必须要掌握的,aop这块东西比较多,我们将通过三 四篇文章来详解介绍这块的内容,由浅入深,让大家全面掌握这块知识。 说的简单点,spring中的aop就是依靠代理实现的各种功能,通过代理来对bean进行增强。 spring中的aop功能主要是通过2种代理来实现的 1. jdk动态代理 2. cglib代理
spring中解析@configuration注解,以及后面我们要 学的aop、spring中的事务、等等,这些都是依靠代理来实现的
为什么要用代理
因为AOP的底层机制就是动态代理!
学习aop之前 , 我们要先了解一下代理模式!
代理模式:为其他对象提供一种代理以控制对这个对象的访问。这段话比较官方,但我更倾向于用自己的语言理解:比如A对象要做一件事情,在没有代理前,自己来做,在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-buHlh5GX-1621778369428)(https://dadandiaoming.oss-cn-beijing.aliyuncs.com/image%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20(1)].png)
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
静态代理
静态代理角色分析
- 抽象角色 : 一般使用接口或者抽象类来实现**(租房)**
- 委托角色 : 被代理的角色**(房东)**
- 代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .(中介)
- 客户 : 使用代理角色来进行一些操作 .(租房的人)
建立以下文件:
Rent接口:(抽象角色:租房)
package com.zsq;
//抽象角色:租房
public interface Rent {
public void rent();
}
Host类:(真实角色: 房东)
package com.zsq;
//委托角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy类:(代理角色:中介)
package com.zsq;
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() { }
public Proxy(Host host) {
//将参数host的值赋给当前属性host
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client类:(客户类:一般客户都会去找代理!)
package com.zsq;
//客户类,一般客户都会去找代理!
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
/*类名 对象名 =new 类名(参数);
=左边:创建一个类的对象
=右边:调用这个类的构造函数并初始化对象,类名()这个是构造函数,用来做初始化搜索的。
Proxy proxy创建一个Proxy类的对象,对象名为proxy
new Proxy(host)调用Proxy类的含参构造函数
*/
Proxy proxy = new Proxy(host);
//你去找中介!
proxy.rent();
}
}
运行结果:
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
动态代理
- 动态代理的角色和静态代理的一样 .
- 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
- 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
-
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理–cglib
- 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!、
jdk动态代理详解
jdk中为实现代理提供了支持,主要用到2个类:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看 https://www.apiref.com/java11-zh/
【InvocationHandler:调用处理程序】
InvocationHandler接口的方法
变量和类型 | 方法 | 描述 |
---|---|---|
Object | invoke(Object proxy, 方法 method, Object[] args) | 处理代理实例上的方法调用并返回结果。 |
参数说明:
proxy
- 调用该方法的代理实例。指代我们所代理的那个真实对象
method
- 对应于在代理实例上调用的接口方法的方法
实例。方法
对象的声明类将是声明方法的接口,它可以是代理接口继承方法的代理接口的超接口。指代的是我们所要调用真实对象的某个方法的Method对象.
args
- 包含代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数,null
。 原始类型的参数包含在适当的原始包装类的实例中,例如java.lang.Integer
或java.lang.Boolean
。指代的是调用真实对象某个方法时接受的参数。
【Proxy : 代理】
构造方法
变量 | 构造器 | 描述 |
---|---|---|
protected | Proxy(InvocationHandler h) | 从子类(通常是动态代理类)构造一个新的 Proxy 实例,并为其调用处理程序指定值。 |
参数
h
- 此代理实例的调用处理程序
一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载常用方法:
变量和类型 | 方法 | 描述 |
---|---|---|
static InvocationHandler | getInvocationHandler(Object proxy) | 返回指定代理实例的调用处理程序。 |
static 类 <?> | getProxyClass(ClassLoader loader, 类<?>… interfaces) | **已过时。**在命名模块中生成的代理类被封装,并且其模块外部的代码无法访问。 |
static boolean | isProxyClass(类<?> cl) | 如果给定的类是代理类,则返回true |
static Object | newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) | 返回指定接口的代理实例,该接口将方法调用分派给指定的调用处理程序。 |
java.lang.reflect.Proxy
这是jdk动态代理中主要的一个类,里面有一些静态方法会经常用到,我们来熟悉一下:
getProxyClass方法
为指定的接口创建代理类,返回代理类的Class对象
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
参数说明:
loader:定义代理类的类加载器
interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口。是真实类所拥有的全部接口的数组。
newProxyInstance方法
创建代理类的实例对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参数
loader
- 用于定义代理类的类加载器 代理类的类加载器
interfaces
- 增强方法所在类,这个类要实现接口,支持多个接口表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了,为代理对象提供的接口是真实对象所实行的接口,即接口是我们自己定义的接口
h
- 调度方法调用的调用处理程序。实现接口InvocationHandler,创建代理对象,写增强的方法,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,最后一个参数比较特殊,是 InvocationHandler类型的,这个是个接口如下:
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable;
上面方法会返回一个代理对象,当调用代理对象的任何方法的时候,会就被 InvocationHandler 接口 的 invoke 方法处理,所以主要代码需要卸载 invoke 方法中,稍后会有案例细说。
在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。这个抽象方法在代理类中动态实现。
isProxyClass方法
判断指定的类是否是一个代理类
public static boolean isProxyClass(Class<?> cl)
参数
cl
- 要测试的类
getInvocationHandler方法
获取代理对象的 InvocationHandler 对象
public static InvocationHandler getInvocationHandler(Object proxy)
throws IllegalArgumentException
参数
proxy
- 用于返回调用处理程序的代理实例
案例背景:
现在有人要租房,我们要定义一个接口,且有租房的方法,然后房东要出租房子,那么他就要实现租房的接口,才能拥有租房的权力,但如果此时,房东觉得自己去出租房子太累了,那么他就可以找到第三方代理自己出租房子,即中介,既然要出租房子,那么他也要实现租房的接口,才能拥有租房的权力,但是,租房子的人不可能在什么都不知道的情况下就立即付钱租房子,中间肯定要有看房子的过程,而且他也要获得中介费,他才会卖力干活。所以第三方代理——中介,肯定还要增强自己的功能,不能单单只有租房的功能,还要有看房,收中介费的功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K3GENN3m-1621778369433)(https://dadandiaoming.oss-cn-beijing.aliyuncs.com/image%E6%9C%AA%E5%91%BD%E5%90%8D%E6%96%87%E4%BB%B6%20(2)].png)
案例
建立下图文件:
Rent接口:(租房的功能)
package com.zsq;
/**抽象角色:租房*/
public interface Rent {
public void rent();
}
Host类:(出租人)
package com.zsq;
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
ProxyInvocationHandler类:(第三方代理–中介)
package com.zsq;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ProxyInvocationHandler implements InvocationHandler {
//目标代理对象
private final Host host;
//构造代理对象时传入目标对象
public ProxyInvocationHandler(Host host) {
this.host=host;
}
//处理代理实例,并返回结果
C{
seeHouse();
//核心:本质利用反射实现!
//invoke(Object obj, Object... args)方法调用,`obj` -从中调用底层方法的对象(简单的说就是调用谁的方法用谁的对象)。`args` - 用于方法调用的参数。等价于“实例化对象.方法“
Object result = method.invoke(host, args);
fare();
return result;
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client类:(承租人)
package com.zsq;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
// 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new ProxyInvocationHandler(host);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
* 第二个参数interfaces,我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
* 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
//创建接口实现类代理对象
Class<?>[] interfaces={Rent.class};
//依据真实对象的类加载器,实现接口以及代理调用类,动态创建代理对象
Rent rent=(Rent) Proxy.newProxyInstance(handler.getClass().getClassLoader(),interfaces,handler);
rent.rent();
}
}
运行结果:
Proxy使用注意 :
-
jdk中的Proxy只能为接口生成代理类,如果你想给某个类创建代理类,那么Proxy是无能为力 的,此时需要我们用到下面要说的cglib了。
-
Proxy类中提供的几个常用的静态方法大家需要掌握
-
通过Proxy创建代理对象,当调用代理对象任意方法时候,会被InvocationHandler接口中的 invoke方法进行处理,这个接口内容是关键
cglib代理详解
什么是cglib
jdk动态代理只能为接口创建代理,使用上有局限性。实际的场景中我们的类不一定有接口,此时如果 我们想为普通的类也实现代理功能,我们就需要用到cglib来实现了.cglib代理也叫⼦类代理,从内存中构建出⼀个⼦类来扩展⽬标对象的功能!。
cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口;本质上它是通过动 态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。Enhancer可能是CGLIB中最常用的 一个类,和jdk中的Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创 建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方 法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定 的。基于同样的道理,Enhancer也不能对final类进行代理操作。
编写cglib代理
接下来我们就讲讲怎么写cglib代理:
-
需要引⼊cglib – jar⽂件, 但是spring的核⼼包中已经包括了cglib功能,所以直接引⼊spring-core-3.2.5.jar即可。(如果⽤maven的同学,引⼊pom依赖就好了)
-
引⼊功能包后,就可以在内存中动态构建⼦类
-
代理的类不能为final,否则报错【在内存中构建⼦类来做扩展,当然不能为final,有final就不能 继承了】
-
⽬标对象的⽅法如果为final/static, 那么就不会被拦截,即不会执⾏⽬标对象额外的业务⽅法
建立如图的文件
Message类:被代理类
package com.zsq;
//被代理类:
public class Message {
public void send(){
System.out.println("【消息发送】");
}
}
ProxyFactor类:定义一个拦截器
package com.zsq;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*定义一个拦截器。在调用目标方法时,CGLib会回调MethodInterceptor接口方法拦截,来实现你自己的代理逻辑,
类似于JDK中的InvocationHandler接口。*/
public class ProxyFactor implements MethodInterceptor {
//真实业务对象
public Object target;
public ProxyFactor(Object target) {
this.target = target;
}
//代理方法
public boolean connect(){
System.out.println("【消息代理】进行消息发送通道的连接");
return true;
}
//代理方法
public void close(){
System.out.println("【消息代理】关闭消息通道。");
}
/**
* 代理对象方法拦截器
* @param o 代理对象
* @param method 被代理的类的方法,即Message中的方法
* @param objects 调用方法传递的参数
* @param methodProxy 方法代理对象
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object returnData=null;
if(this.connect()){
//可以调用MethodProxy的invokeSuper调用被代理类的方法
returnData=methodProxy.invokeSuper(o,objects);
this.close();
}
return returnData;
}
}
JavaReflectDemo类:生成动态代理类
package com.zsq;
import org.springframework.cglib.proxy.Enhancer;
public class JavaReflectDemo {
public static void main(String[] args) {
Message realObject=new Message();
//使用Enhancer来给某个类创建代理类,步骤
//1.创建Enhancer对象
Enhancer enhancer=new Enhancer();
//2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
enhancer.setSuperclass(realObject.getClass());
/*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接
口,实现了Callback接口,当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
enhancer.setCallback(new ProxyFactor(realObject));
//4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,所以需要强转一下
Message proxyObject=(Message) enhancer.create();
//5.调用代理对象的方法
proxyObject.send();
}
}
运行结果:
Enhancer
enhancer.setSuperclass 用来设置代理类的父类,即需要给哪个类创建代理类。
enhancer.setCallback 传递的是 MethodInterceptor 接口类型的参数, MethodInterceptor 接口有个 intercept 方法,这个方法会拦截代理对象所有的方法调用。
还有一个重点是 Object result = methodProxy.invokeSuper(o, objects); 可以调用被代理 类,也就是Service1类中的具体的方法,从方法名称的意思可以看出是调用父类,实际对某个类 创建代理,cglib底层通过修改字节码的方式为Service1类创建了一个子类。
AOP入门
什么是AOP?
AOP(AspectOrient Programming)也就是面向切面编程。可以这样理解,面向对象编程(OOP)是从静态角度考虑程序结构,面向切面编程(AOP)是从动态角度考虑程序运行过程。常常通过 AOP 来处理一些具有横切性质的系统性服务,如事物管理、安全检查、缓存、对象池管理等,AOP已经成为一种非常常用的解决方案。
Spring中AOP一些概念
目标对象(target)
目标对象指将要被增强的对象,即包含主业务逻辑的类对象。
连接点(JoinPoint)
连接点,程序运行的某一个点,比如执行某个方法,在Spring AOP中Join Point总是表示一个方法的执 行
代理对象(Proxy)
AOP中会通过代理的方式,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理 对象来间接的方式目标对象,起到增强目标对象的效果。
通知(Advice)
需要在目标对象中增强的功能,如上面说的:业务方法前验证用户的功能、方法执行之后打印方法的执 行日志。
通知中有2个重要的信息:方法的什么地方,执行什么操作,这2个信息通过通知来指定。
方法的什么地方?
之前、之后、包裹目标方法、方法抛出异常后等。
如:
在方法执行之前验证用户是否有效。 在方法执行之后,打印方法的执行耗时。 在方法抛出异常后,记录异常信息发送到mq。
切入点(Pointcut )
用来指定需要将通知使用到哪些地方,比如需要用在哪些类的哪些方法上,切入点就是做这个配置的。
切面(Aspect)
通知(Advice)和切入点(Pointcut)的组合。切面来定义在哪些地方(Pointcut)执行什么操作 (Advice)。
顾问(Advisor)
Advisor 其实它就是 Pointcut 与 Advice 的组合,Advice 是要增强的逻辑,而增强的逻辑要在什么地方 执行是通过Pointcut来指定的,所以 Advice 必需与 Pointcut 组合在一起,这就诞生了 Advisor 这个 类,spring Aop中提供了一个Advisor接口将Pointcut 与 Advice 的组合起来。 Advisor有好几个称呼:顾问、通知器。 其中这4个:连接点(JoinPoint)、通知(advise)、切入点(pointcut)、顾问(advisor),在spring中都定义 了接口和类来表示这些对象,下面我们一个个来看一下
织入(Weaving)
织入是将切面应用到目标对象来创建新的代理对象的过程,切面在指定的连接点被织入到目标对象中(注入前置、后置通知等)。
在目标对象的生命周期里有多个点可以进行织入:
编译期—— 切面在目标类编译期被织入,这种方式需要特殊的编译器,如AspectJ;
类加载期—— 切面在目标类家在到JVM时被织入,这种方式需要特殊的类加载器(ClassLoader);
运行期—— 切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面的;
其中这4个:连接点(JoinPoint)、通知(advise)、切入点(pointcut)、顾问(advisor),在spring中都定义 了接口和类来表示这些对象,下面我们一个个来看一下
连接点(JoinPoint)
JoinPoint接口
package org.aopalliance.intercept;
public interface Joinpoint {
/**
* 转到拦截器链中的下一个拦截器
*/
Object proceed() throws Throwable;
/**
* 返回保存当前连接点静态部分【的对象】,这里一般指被代理的目标对象
*/
Object getThis();
/**
* 返回此静态连接点 一般就为当前的Method(至少目前的唯一实现是MethodInvocation,所以连
接点得静态部分肯定就是本方法)
*/
AccessibleObject getStaticPart();
}
几个重要的子接口和实现类,如下:
Invocation接口
package org.aopalliance.intercept;
/**
* 此接口表示程序中的调用
* 调用是一个连接点,可以被拦截器拦截。
*/
public interface Invocation extends Joinpoint {
/**
* 将参数作为数组对象获取。可以更改此数组中的元素值以更改参数。
* 通常用来获取调用目标方法的参数
*/
Object[] getArguments();
}
MethodInvocation接口
package org.aopalliance.intercept;
import java.lang.reflect.Method;
/**
* 方法调用的描述,在方法调用时提供给拦截器。
* 方法调用是一个连接点,可以被方法拦截器拦截。
*/
public interface MethodInvocation extends Invocation {
/**
* 返回正在被调用得方法~~~ 返回的是当前Method对象。
* 此时,效果同父类的AccessibleObject getStaticPart() 这个方法
*/
Method getMethod();
}
通知(Advice)
通知中用来实现被增强的逻辑,通知中有2个关注点,再强调一下:方法的什么地方,执行什么操作。
Advice接口
通知的顶层接口,这个接口内部没有定义任何方法。
package org.aopalliance.aop;
public interface Advice {
}
Advice 4个子接口
MethodBeforeAdvice接口(前置通知)
方法执行前通知,需要在目标方法执行前执行一些逻辑的,可以通过这个实现。 通俗点说:需要在目标方法执行之前增强一些逻辑,可以通过这个接口来实现。before方法:在调用给 定方法之前回调。
package org.springframework.aop;
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* 调用目标方法之前会先调用这个before方法
* method:需要执行的目标方法
* args:目标方法的参数
* target:目标对象
*/
void before(Method method, Object[] args, @Nullable Object target) throws
Throwable;
}
如同
public Object invoke(){
调用MethodBeforeAdvice#before方法
return 调用目标方法;
}
AfterReturningAdvice接口(后置通知)
方法执行后通知,需要在目标方法执行之后执行增强一些逻辑的,可以通过这个实现。 不过需要注意一点:目标方法正常执行后,才会回调这个接口,当目标方法有异常,那么这通知会被跳 过。
package org.springframework.aop;
public interface AfterReturningAdvice extends AfterAdvice {
/**
* 目标方法执行之后会回调这个方法
* method:需要执行的目标方法
* args:目标方法的参数
* target:目标对象
*/
void afterReturning(@Nullable Object returnValue, Method method, Object[]
args, @Nullable Object target) throws Throwable;
}
如同
public Object invoke(){
Object retVal = 调用目标方法;
调用AfterReturningAdvice#afterReturning方法
return retVal;
}
ThrowsAdvice接口(异常处理通知)
目标方法出现异常调用
package org.springframework.aop;
public interface ThrowsAdvice extends AfterAdvice {
此接口上没有任何方法,因为方法由反射调用,实现类必须实现以下形式的方法,前3个参数是可选 的,最后一个参数为需要匹配的异常的类型。
void afterThrowing([Method, args, target], ThrowableSubclass);
有效方法的一些例子如下:
public void afterThrowing(Exception ex)
public void afterThrowing(RemoteException)
public void afterThrowing(Method method, Object[] args, Object target, Exception
ex)
public void afterThrowing(Method method, Object[] args, Object target,
ServletException ex)
MethodInterceptor接口(环绕通知)
目标方法执行前后都会调用方法,且能增强结果.方法拦截器,这个接口最强大,可以实现上面3种类型的通知,上面3种通知最终都通过适配模式将其转 换为MethodInterceptor方式去执行。
package org.aopalliance.intercept;
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* 拦截目标方法的执行,可以在这个方法内部实现需要增强的逻辑,以及主动调用目标方法
*/
Object invoke(MethodInvocation invocation) throws Throwable;
}
使用方式如:
public class TracingInterceptor implements MethodInterceptor {
Object invoke(MethodInvocation i) throws Throwable {
System.out.println("method "+i.getMethod()+" is called on "+
i.getThis()+" with args "+i.getArguments());
Object ret=i.proceed();//转到拦截器链中的下一个拦截器
System.out.println("method "+i.getMethod()+" returns "+ret);
return ret;
}
}
拦截器链
一个目标方法中可以添加很多Advice,这些Advice最终都会被转换为 MethodInterceptor 类型的方法 拦截器,最终会有多个 MethodInterceptor ,这些 MethodInterceptor 会组成一个方法调用链。
Aop内部会给目标对象创建一个代理,代理对象中会放入这些 MethodInterceptor 会组成一个方法调 用链,当调用代理对象的方法的时候,会按顺序执行这些方法调用链,一个个执行,最后会通过反射再 去调用目标方法,进而对目标方法进行增强。
切入点(PointCut)
PointCut接口
package org.springframework.aop;
public interface Pointcut {
/**
* 类过滤器, 可以知道哪些类需要拦截
*/
ClassFilter getClassFilter();
/**
* 方法匹配器, 可以知道哪些方法需要拦截
*/
MethodMatcher getMethodMatcher();
/**
* 匹配所有对象的 Pointcut,内部的2个过滤器默认都会返回true
*/
Pointcut TRUE = TruePointcut.INSTANCE;
}
ClassFilter接口
类过滤器。
@FunctionalInterface
public interface ClassFilter {
/**
* 用来判断目标类型是否匹配
*/
boolean matches(Class<?> clazz);
}
MethodMatcher接口
方法过滤器。
public interface MethodMatcher {
/**
* 执行静态检查给定方法是否匹配
* @param method 目标方法
* @param targetClass 目标对象类型
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 是否是动态匹配,即是否每次执行目标方法的时候都去验证一下
*/
boolean isRuntime();
/**
* 动态匹配验证的方法,比第一个matches方法多了一个参数args,这个参数是调用目标方法传入的
参数
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* 匹配所有方法,这个内部的2个matches方法任何时候都返回true
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
顾问(Advisor)
通知定义了需要做什么,切入点定义了在哪些类的哪些方法中执行通知,那么需要将他们2个组合起来 才有效啊。 顾问(Advisor)就是做这个事情的。
在spring aop中,你可以将advisor理解为切面,切面中通常有2个关键信息:
- 需要增强的目标方法列表,这个通过切入点(Pointcut)来指定
- 需要在目标方法中增强的逻辑,这个通过(Advice)通知来指定
Advisor接口
package org.springframework.aop;
import org.aopalliance.aop.Advice;
/**
* 包含AOP通知(在joinpoint处执行的操作)和确定通知适用性的过滤器(如切入点[PointCut])的基
本接口。
* 这个接口不是供Spring用户使用的,而是为了支持不同类型的建议的通用性。
*/
public interface Advisor {
/**
* 返回引用的通知
*/
Advice getAdvice();
}
**上面这个接口通常不会直接使用,**这个接口有2个子接口,通常我们会和这2个子接口来打交道,下面看 一下这2个子接口。
PointcutAdvisor接口
通过名字就能看出来,这个和Pointcut有关,内部有个方法用来获取 Pointcut ,AOP使用到的大部分 Advisor都属于这种类型的。 在目标方法中实现各种增强功能基本上都是通过PointcutAdvisor来实现的。
package org.springframework.aop;
/**
* 切入点类型的Advisor
*/
public interface PointcutAdvisor extends Advisor {
/**
* 获取顾问中使用的切入点
*/
Pointcut getPointcut();
}
DefaultPointcutAdvisor类
PointcutAdvisor的默认实现,这是最常用的Advisor实现,它可以用于任何Pointcut和Advice类型,代 码相当简单,里面定义了2个属性:pointcut和advisor,由使用者指定。
IntroductionAdvisor接口
一个Java类,没有实现A接口,在不修改Java类的情况下,使其具备A接口的功能。可以通过IntroductionAdvisor给目标类引入更多接口的功能,这个功能是不是非常牛逼