Spring5学习笔记
总览
- Spring 核心学习内容 IOC、AOP, jdbcTemplate, 声明式事务
- IOC: 控制反转 , 可以管理 java 对象
- AOP : 切面编程
- JDBCTemplate : 是 spring 提供一套访问数据库的技术, 应用性强,相对好理解
- 声明式事务: 基于 ioc/aop 实现事务管理, 理解有需要小伙伴花时间
- IOC, AOP 是重点同时难点
Spring几个重要的概念
- Spring 可以整合其他的框架(老韩解读: Spring 是管理框架的框架)
- Spring 有两个核心的概念: IOC 和 AOP
- IOC [Inversion Of Control 反转控制]
● 传统的开发模式[JdbcUtils / 反射]
程序------>环境 程序读取环境配置,然后自己创建对象
● IOC 的开发模式 [EmpAction EmpService EmpDao Emp]
程序<-----容器 容器创建好对象,程序直接使用
- DI—Dependency Injection 依赖注入,可以理解成是 IOC 的另外叫法.
- Spring 最大的价值,通过配置,给程序提供需要使用的 web 层[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity]对象, 这个是核心价值所在,也是 ioc 的具体体现, 实现解耦
创建代码实现
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
这里读取的是out目录下的beans.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">
<!--
1. 配置monster对象 / JavaBean2. 在beans中可以配置多个bean
3. bean 表示地就是一个Java对象
4. class属性用于指定类的全路径 -> spring底层反射创建
5. id 属性表示Java对象在spring容器中的id, 通过id可以获取 【唯一的】
6. <property name="monsterId" value="100" /> 用于给对象赋值
-->
<bean class="com.hspedu.Spring.bean.Monster" id="monster01">
<!--
这里底层也是通过对应属性的setter方法实现的
-->
<property name="monsterId" value="100" />
<property name="name" value="牛魔王" />
<property name="skill" value="芭蕉扇"/>
</bean>
<bean class="com.hspedu.Spring.bean.Monster" id="monster02">
<property name="monsterId" value="200" />
<property name="name" value="铁扇公主" />
<property name="skill" value="哈哈哈"/>
</bean>
</beans>
@Test
public void testSpringBean() throws Exception {
// 创建容器 ApplicationContext// 这个容器和一个配置文件关联
// ioc是重量级的对象, 耗费的资源非常的多
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
// 通过getBean() 获取对应的对象
// 默认返回的是Object, 但是运行类型是Monster
Object monster01 = ioc.getBean("monster01");
Monster monster02 = (Monster) ioc.getBean("monster01");
System.out.println("monster01 = " + monster01);
System.out.println("monster02 = " + monster02 + ", monster02的name=" + monster02.getName());
// 可以不用强转
// 使用getBean()的其他重载方法
Monster monster03 = ioc.getBean("monster01", Monster.class);
System.out.println("monster03 = " + monster03);
// 查看容器注入了那些bean对象, 会输出bean的id
String[] beanDefinitionNames = ioc.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
}
手动实现底层
仔细看看, 有助于理解
/**
* ClassName: HspApplicationContext
* Package: com.hspedu.Spring.hspapplicationcontext
*
* @Author: leikooo
* @Creat: 2023/5/17 - 15:50
* @Description: 1. 这个程序用于实现Spring的一个简单容器机制
* 2. 后面还会详细的实现
* 3. 这里我们通过beans.xml文件记性解析, 并生成对象, 放入放到容器之中
* 4. 提供一个方法 getBean(id) 返回对用的对象
* 5. 只是一个开胃小点心
*/
public class HspApplicationContext {
private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>();
// 构造器
// 接收容器的配置文件
public HspApplicationContext(String iocBeanXmlFile) {
SAXReader saxReader = new SAXReader();
String path = HspApplicationContext.class.getResource("/").getPath();
System.out.println(path);
try {
Document document = saxReader.read(new File(path + iocBeanXmlFile));
// 得到根元素
Element rootElement = document.getRootElement();
// 得到第一个monster01对象
Element bean = rootElement.elements("bean").get(0);
// 获取第一个monster01的相关属性
String id = bean.attributeValue("id");
String classFullPatch = bean.attributeValue("class");
// System.out.println("id = " + id);
List<Element> property = bean.elements("property");
int monsterId = Integer.parseInt(property.get(0).attributeValue("value"));
String name = property.get(1).attributeValue("value");
String skill = property.get(2).attributeValue("value");
// System.out.println("monsterId = " + monsterId);
// System.out.println("name = " + name);
// System.out.println("skill = " + skill);
// 使用反射创建对象
Class<?> aClass = Class.forName(classFullPatch);
Monster monster = (Monster) aClass.newInstance();
// 使用反射赋值
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
if ("setName".equals(method.getName())) {
method.invoke(monster, name);
} else if ("setMonsterId".equals(method.getName())) {
method.invoke(monster, monsterId);
} else if ("setSkill".equals(method.getName())) {
method.invoke(monster, skill);
}
}
// System.out.println("monster = " + monster);
// 最后把创建好的对象放入到 singletonObjectssingletonObjects.put(id, monster);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Object getBean(String id) {
return singletonObjects.get(id);
}
}
Spring 管理 Bean-IOC
Spring 配置/管理 bean 介绍
Bean包管理分为两个方面
- 创建bean对象
- 给bean注入属性
Bean的配置方式
- 基于xml文件配置方式
- 基于注解方式
基于xml文件配置方式
通过类型来获取 bean
xml配置文件
<bean class="com.hspedu.Spring.bean.Monster">
<property name="monsterId" value="1010"/>
<property name="name" value="牛魔王~"/>
<property name="skill" value="芭蕉扇~"/>
</bean>
测试文件
public void test1() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
// 必须是单个对象
Monster bean = ioc.getBean(Monster.class);
System.out.println("bean = " + bean);
}
细节
- 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException
- 这种方式的
应用场景
:比如 XxxAction/Servlet/Controller, 或XxxService 在一个线程中只需要一个对象实例(单例)的情况 - 老师这里在说明一下: 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过setter 方法完成的, 这也是为什么我们需要提供 setter 方法的原因
通过构造器配置 bean
<!-- 使用构造器配置monster对象
1. 这里使用 constructor-arg 可以指定构造器
2. index 表示构造器第几个元素 索引从0开始
3. 除了index还有其他的方式
-->
--- 通过index属性指定构造器
<bean class="com.hspedu.Spring.bean.Monster" id="monster03">
<constructor-arg value="200" index="0"/>
<constructor-arg value="白骨精" index="1"/>
<constructor-arg value="吸血" index="2"/>
</bean>
--- 使用name属性来指定构造器
<bean class="com.hspedu.Spring.bean.Monster" id="monster04">
<constructor-arg value="200" name="monsterId"/>
<constructor-arg value="白骨精" name="name"/>
<constructor-arg value="吸血" name="skill"/>
</bean>
---- 使用type属性来指定构造器
<bean class="com.hspedu.Spring.bean.Monster" id="monster05">
<constructor-arg value="200" type="java.lang.Integer"/>
<constructor-arg value="白骨精" type="java.lang.String"/>
<constructor-arg value="吸血" type="java.lang.String"/>
</bean>
这里会调用Monster的对应的全参构造器 和 无参构造器
通过 p 名称空间配置 bean
需要在xml中引入 xmlns:p="www.springframework.org/schema/p"
<bean class="com.hspedu.Spring.bean.Monster" id="monster"
p:monsterId="100"
p:name="红孩儿"
p:skill="吐火"
/>
引用/注入其它 bean 对象
在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的 相互引用
- 这里的ref就是依赖注入, spring底层帮你实现
- 注意在spring容器中, 他作为一个整体来执行, 即使如果引用到一个bean对象, 对你的配置顺序没有要求
<!-- 依赖注入
1. 这里的ref就是依赖注入, spring底层帮你实现
2. 注意在spring容器中, 他作为一个整体来执行, 即使如果引用到一个bean对象, 对你的配置顺序没有要求
3. 建议还是按照顺序写, 便于阅读
-->
<bean class="com.hspedu.Spring.DAO.MemberDAOImpl" id="memberDAO"/>
<bean class="com.hspedu.Spring.Service.MemberServiceImpl" id="memberService">
<property name="memberDAO" ref="memberDAO"/>
</bean>
look一下底层没毛病
引用/注入内部 bean 对象
使用内部类注入
<bean class="com.hspedu.Spring.Service.MemberServiceImpl" id="memberService02">
<property name="memberDAO">
<bean class="com.hspedu.Spring.DAO.MemberDAOImpl"/>
</property>
</bean>
引用/注入集合/数组类型
在Spring中,我们可以使用XML配置文件将集合类型的属性注入到Bean中。具体实现方法如下:
- 数组类型的属性注入:
<bean id="myBean" class="com.example.MyBean">
<property name="myArray">
<array>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</array>
</property>
</bean>
在上述示例中,我们使用<array>
标签注入一个名为myArray
的字符串数组。
- List类型的属性注入:
<bean id="myBean" class="com.example.MyBean">
<property name="myList">
<list>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</list>
</property>
</bean>
在上述示例中,我们使用<list>
标签注入一个名为myList
的字符串列表。
- Set类型的属性注入:
<bean id="myBean" class="com.example.MyBean">
<property name="mySet">
<set>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</set>
</property>
</bean>
在上述示例中,我们使用<set>
标签注入一个名为mySet
的字符串集合。
- Map类型的属性注入:
<bean id="myBean" class="com.example.MyBean">
<property name="myMap">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
<entry key="key3" value="value3"/>
</map>
</property>
</bean>
在上述示例中,我们使用<map>
标签注入一个名为myMap
的字符串键值对集合。
- Properties 类型的注入
Properties这个是Map接口下的一个具体实现类 key 是 String Vlue 也是 String 类型的
<bean id="myBean" class="com.example.MyBean">
<property name="myProperties">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
上述实例代码中, 我们使用 <property>
标签注入给一个名为 myProperties
中
具体案例实现代码
public class Master {
private String name;
private List<Monster> monsterList;
private Map<String, Monster> monsterMap;
private Set<Monster> monsterSet;
private String[] monsterName;
// 这个 Properties 是 Hashtable 的子类 , 是 key-value 的形式
// 这里 Properties key 和 value 都是 Stringprivate Properties pros;
private Properties pros;
// 省咯 无参构造器, 全参构造器, getter和setter方法
}
使用xml配置 Master属性
<bean class="com.hspedu.Spring.bean.Master" id="master">
<property name="name" value="台上老君"/>
<!-- 给list属性赋值-->
<property name="monsterList">
<list>
<ref bean="monster01"/>
<ref bean="monster02"/>
<ref bean="monster03"/>
<!-- 内部bean 一般不用分配id, 外部没法使用-->
<bean class="com.hspedu.Spring.bean.Monster">
<property name="name" value="老鼠精"/>
<property name="monsterId" value="404"/>
<property name="skill" value="活得长"/>
</bean>
</list>
</property>
<property name="monsterMap">
<!-- 传入map属性-->
<map>
<entry>
<key>
<value>monster03</value>
</key>
<ref bean="monster03"/>
</entry>
<!-- 可以简化-->
<entry value-ref="monster04" key="monster04"/>
</map>
</property>
<!-- 传入set -->
<property name="monsterSet">
<set>
<!-- 引用外部bean-->
<ref bean="monster01" />
<!-- 定义的内部bean-->
<bean class="com.hspedu.Spring.bean.Monster">
<property name="monsterId" value="500"/>
<property name="name" value="金角大王" />
<property name="skill" value="吐火" />
</bean>
</set>
</property>
<!-- 设置数组的值 -->
<property name="monsterName">
<!-- 这个array标签value是什么需要根据业务除理-->
<array>
<value>小妖怪</value>
<value>大妖怪</value>
<value>老妖怪</value>
</array>
</property>
<property name="pros">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
通过 util 名称空间创建 list
使用util
名称空间创建集合类型的Bean通常是在创建一些简单的数据结构时使用,例如配置文件中的一些固定数据,或者是一些开发和测试时需要使用的数据。但是,如果需要创建复杂的数据结构,或者需要在运行时动态生成数据,或者需要进行复杂的数据处理操作,建议使用Java代码来实现,这样可以更灵活和高效地操作数据。
<!-- 定义一个util:list-->
<util:list id="myBook">
<value>三国演义</value>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
</util:list>
<bean class="com.hspedu.Spring.bean.BookStore" id="bookStore">
<!-- 可以直接在ref 标签引入 -->
<property name="bookList" ref="myBook"/>
</bean>
级联属性赋值
spring 的 ioc 容器, 可以直接给对象属性的属性赋值
设置级联属性
<bean class="com.hspedu.Spring.bean.Dept" id="dept" />
<bean class="com.hspedu.Spring.bean.Emo" id="emo">
<property name="name" value="Jack" />
<property name="dept" ref="dept" />
<property name="dept.name" value="Java开发部门" />
</bean>
名称点属性就ok
通过静态工厂获取对象
工厂类
public class MyStaticFactory {
private static Map<String, Monster> monsterMap;
// 使用静态代码块, 只会执行一次
static {
monsterMap = new HashMap<String, Monster>();
monsterMap.put("1", new Monster(1, "牛魔王", "芭蕉扇"));
monsterMap.put("2", new Monster(2, "狐狸精", "美人计"));
}
/**
* 返回对应的 monster
*/
public static Monster getMonster(String key) {
return monsterMap.get(key);
}
}
- 通过静态工厂获取
- class 是工厂的路径
- factory-method 表示的是指定工厂类是由哪一个对象返回
- constructor—-arg 的value指的是要指定返回哪一个工厂对象
<bean id="my_monster01"
class="com.hspedu.Spring.Factory.MyStaticFactory"
factory-method="getMonster">
<constructor-arg value="1"/>
</bean>
通过实例工厂获取对象
- 因为是非静态的所以需要造对象
- factory-bean 指定使用拿一个实例工厂
- factory-method 指定使用实例工厂的哪一个方法
- constructor-arg 传入的参数
<bean id="myInstanceFactory" class="com.hspedu.Spring.Factory.MyInstanceFactory" />
<bean id="my_monster02" factory-bean="myInstanceFactory" factory-method="getMonster">
<!-- 由于这个对象一开始就会创建, 所以不管获取几次都是同一个对象-->
<constructor-arg value="monster_02" />
</bean>
工厂类
public class MyInstanceFactory {
private Map<String, Monster> monster_map;
// 非静态代码块
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "猴子精", "吃人"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public Monster getMonster(String key) {
return monster_map.get(key);
}
}
FactoryBean 配置对象 【重要】
FactoryBean
是 Spring 提供的另一种创建 Bean 的方式,与普通的 Bean 不同的是,FactoryBean
产生的 Bean 并不是通过构造函数或工厂方法来创建的。相反,FactoryBean
定义了一种创建 Bean 的方式,即使用 FactoryBean
的实现类的 getObject()
方法来创建 Bean。它可以让我们在创建 Bean 的过程中进行更加细致的控制和定制。
Creating a bean that returns a specific object type
public class MyFactoryBean implements FactoryBean<String> {
@Override
public String getObject() throws Exception {
return "Hello, world!";
}
@Override
public Class<?> getObjectType() {
return String.class;
}
}
XML configuration
<bean id="myFactoryBean" class="com.example.MyFactoryBean"/>
This bean will always return the string "Hello, world!". You can use this bean to create a constant value, for example.
这里还有一种写法, 使用setter方法配置相关属性
配置文件
<bean id="myBeanFactory" class="com.hspedu.Spring.Factory.MyBeanFactory">
<property name="key" value="monster_01" />
</bean>
Java文件 MyBeanFactory
public class MyBeanFactory implements FactoryBean<Monster> {
private String key;
private Map<String, Monster> monster_map;
// 非静态代码块
{
monster_map = new HashMap<String, Monster>();
monster_map.put("monster_01", new Monster(100, "猴子精", "吃人~~~"));
monster_map.put("monster_02", new Monster(200, "九头金雕", "如来神掌"));
}
public void setKey(String key) {
this.key = key;
}
@Override
public Monster getObject() throws Exception {
return monster_map.get(key);
}
@Override
public Class<?> getObjectType() {
return Monster.class;
}
@Override
public boolean isSingleton() {
// 是否返回的是单例
return true;
}
}
调用函数, 获得对应的属性值
@Test
public void getBeanByBeanFactory() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
// 底层调用了 getObject() 方法
Monster bean = ioc.getBean("myBeanFactory", Monster.class);
System.out.println("bean = " + bean);
}
bean 配置信息重用(继承)
在 spring 的 ioc 容器, 提供了一种继承的方式来实现 bean 配置信息的重用
在 Spring 配置文件中,可以使用 Bean 配置信息重用(或称为继承)来减少冗余配置,提高配置文件的可读性,方便对系统进行维护。
具体来说,Bean 配置信息重用可以通过将多个 Bean 配置信息定义在一个通用的父 Bean 中,然后让多个子 Bean 继承这个父 Bean 的配置信息来实现。子 Bean 可以继承父 Bean 的属性值,构造函数、初始化方法等配置信息,并且还可以根据需要重载父 Bean 的某些配置信息,以达到更精细化的配置效果。
abstract="true" 那么这个bean只能被于继承, 不能被实例化 子类也可以覆盖父类定义的子类属性的值
<!-- 配置monster对象
如果bean指定了 abstract="true" 那么只能被用于继承不能被实例化
-->
<bean id="monster10" class="com.hspedu.Spring.bean.Monster" abstract="true">
<property name="monsterId" value="10"/>
<property name="name" value="蜈蚣精~~"/>
<property name="skill" value="蜇人//"/>
</bean>
<!-- 1. 在配置一个monster 但是这个属性值和 monster01 一样
2. parent= "" 指定当前配置的属性值从 id="monster01"-->
<bean id="monster11" class="com.hspedu.Spring.bean.Monster"
parent="monster10" />
<bean id="monster12" class="com.hspedu.Spring.bean.Monster"
parent="monster10" />
bean 创建顺序
- 在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
- 会先创建 department01 对象,再创建 student01 对象.
<bean id="student01" class="com.hspedu.bean.Student"
设置了这个属性, 创建的循序就会发生变化
depends-on="department01"/><bean id="department01" class="com.hspedu.bean.Department" />
一个问题
- 先看下面的配置, 请问两个 bean 创建的顺序是什么? 并分析执行流程
- 先创建 id=memberDAOImpl
- 再创建 id = memberServiceImpl
- 调用 memberServiceImpl.setMemberDAO() 完成引用
- 先看下面的配置, 请问两个 bean 创建的顺序是什么, 并分析执行流程
- 先创建 id = memberServiceImpl
- 再创建 id=memberDAOImpl
-
- 用 memberServiceImpl.setMemberDAO() 完成引用
bean 对象的单例和多例
在 spring 的 ioc 容器, 在默认是按照单例创建的,即配置一个bean 对象后,ioc 容器只会创建一个 bean 实例。 如果,我们希望 ioc 容器配置的某个 bean 对象,是以多个实例形式创建的则可以通过配置scope="prototype"
来指定
<!--
1. 这里不写scope属性默认是单例的即只有一个对象
2. 如果scope="prototype" 那么就是每一次都创造一个新的对象
-->
<bean class="com.hspedu.Spring.bean.Cat" id="cat" scope="prototype">
<property name="name" value="小花猫"/>
<property name="age" value="12"/>
</bean>
细节
- 默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到
singletonObjects
集合 - 当 设置为多实例机制后, 该bean 是在
getBean()
时才创建 - 如 果 是 单 例 singleton, 同 时 希 望 在 getBean 时 才创建, 可以指定懒加载
lazy-init="true"
(注意默认是 false) - 通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
- 如果
scope="prototype"
这时你的 lazy-init 属性的值不管是ture, 还是false都是在getBean 时候,才创建对象.
bean 的生命周期
● 说明: bean 对象创建是由 JVM 完成的,然后执行如下方法
- 执行构造器
- 执行 set 相关方法
- 调用 bean 的初始化的方法(需要配置) 可以
自定义名字
- 使用 bean
- 当容器关闭时候,调用 bean 的销毁方法(需要配置)
house 类
public class House {
private String name;
public House() {
System.out.println("House() 构造器");
}
public String getName() {
return name;
}
public void setName(String name) {
System.out.println("House setName()...");
this.name = name;
}
// 老师说明, 这个发明是有程序员编写的
// 根据自己的业务逻辑
// 名子不是固定的
public void init() {
System.out.println("House init()..");
}
// 老师说明, 这个发明是有程序员编写的
// 根据自己的业务逻辑
// 名子不是固定的
public void destory() {
System.out.println("House destory()..");
}
}
配置文件
- init-method= 指定bean初始化函数, 在getter方法之后
- destroy-method 指定bean销毁时指定的方法
- init 和 destroy 方法指定的时机由Spring容器指定
<!-- 配置对象, 演示整个bean生命周期 -->
<bean class="com.hspedu.Spring.bean.House" id="house"
init-method="init" destroy-method="destory">
<property name="name" value="北京豪宅" />
</bean>
测试文件
ConfigurableApplicationContext
接口 继承了 ApplicationContext
接口, 所以可以 ApplicationContext
的实现类可以向下转型, 同时因为close
方法在ConfigurableApplicationContext
这里面才有
@Test
public void testBeanInitAndDes() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
House house = ioc.getBean("house", House.class);
System.out.println("house = " + house);
// 关闭容器
// ioc的编译类型是 ApplicationContext 运行类型是ClassPathXmlApplicationContext
// 因为ClassPathXmlApplicationContext 实现了 ConfigurableApplicationContext// 同时 ConfigurableApplicationContext 有close() 方法
// 而且 ConfigurableApplicationContext 继承 ApplicationContext 接口
((ConfigurableApplicationContext) ioc).close();
}
细节
- 初始化 init 方法和 destory 方法, 是程序员来指定
- 销毁方法就是当关闭容器时,才会被调用, 直接退出不会调用
配置 bean 的后置处理器 【难点】
- 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
- 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
- 程序员可以在后置处理器中编写自己的代码
定义一个后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在bean的init方法调用前执行
* @param bean 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
* @param beanName 就是 ioc 容器配置的 bean 的名称
* @return 程序员对传入的bean记性返回/修改, 返回的 bean 对象
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization()~~" + "bean = "
+ bean + " beanName = " + beanName);
return bean;
}
/**
* 在bean的init方法调用后执行
* @param bean 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
* @param beanName 就是 ioc 容器配置的 bean 的名称
* @return 就是返回的 bean 对象
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization()~~");
return bean;
}
}
配置文件
<bean class="com.hspedu.Spring.bean.House" id="house"
init-method="init" destroy-method="destory">
<property name="name" value="大豪宅"/>
</bean>
<!-- 老韩解读
1. 当我们在bean02.xml文件配置了MyBeanPostProcessor
2. 这时后置处理器就会作用在该容器创建的bean对象
3. 已经是针对所有对象编程 => 切面编程AOP
-->
<bean class="com.hspedu.Spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor" />
小结
1、怎么执行到这个方法?=> 使用 AOP(反射+动态代理+IO+容器+注解)
2、有什么用?
=> 可以对 IOC 容器中所有的对象进行统一处理
,比如日志处理/权限的校验/安全的验证/事务管理
.
-初步体验案例: 如果类型是 House 的统一改成 上海豪宅
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
* 在bean的init方法调用前执行
*
* @param bean 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
* @param beanName 就是 ioc 容器配置的 bean 的名称
* @return 程序员对传入的bean记性返回/修改, 返回的 bean 对象
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessBeforeInitialization()~~" + "bean = "
+ bean + " beanName = " + beanName);
if (bean instanceof House) {
((House) bean).setName("美国豪宅");
}
return bean;
}
/**
* 在bean的init方法调用后执行
*
* @param bean 就是 ioc 容器返回的 bean 对象, 如果这里被替换会修改,则返回的 bean 对象也会被修改
* @param beanName 就是 ioc 容器配置的 bean 的名称
* @return 就是返回的 bean 对象
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("postProcessAfterInitialization()~~" + "bean = "
+ bean + " beanName = " + beanName);
return bean;
}
}
3、针对容器的所有对象吗? 是的=>切面编程特点 4、后面我们会自己实现这个底层机制,这个是一个比较难理解的知识点, 现在老韩不做过多的纠结,后面我会带小伙伴实现这个机制
通过属性文件给 bean 注入值
- 在 spring 的 ioc 容器,通过属性文件给 bean 注入值
bean配置文件
需要注意xmlns, 可能会报错!! 原因 : xmlns:context 中的url地址 xsi:schemaLocation 中没有一样的url 具体看看我的语雀文档 www.yuque.com/leikooo/wsk…
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--
指定属性文件
1. location="" 指定属性文件的位置, 需要带上classpath根目录下
2. 属性文件有中文的话, idea会自动帮你转化. 如果没有转化那么就在网站上转换完成就行
-->
<context:property-placeholder location="classpath:my.properties"/>
<!--
通过属性文件给monster赋值, 同时这时的属性值是通过 ${属性名} 来引用的
属性名就是 properties配置文件中的 key
-->
<bean class="com.hspedu.Spring.bean.Monster" id="monster">
<property name="monsterId" value="${monsterId}"/>
<property name="name" value="${name}"/>
<property name="skill" value="${skill}"/>
</bean>
</beans>
配置文件
monsterId=1000
name=jack
skill=hello
基于 XML 的 bean 的自动装配
byType
- 可以通过
autowire
实现自动装配 - autowire="byType" 通过类型自动完成赋值/引用
- 比如OrderService 中有 OrderDAO 属性, 如果容器中如果过有OrderDAO 这个类型的对象就会自动装配
- 如果使用byType 那么不能有两个(或以上)这个类型的对象
- 如果没有没有属性那么 autowire没有必要写
<bean class="com.hspedu.Spring.DAO.OrderDAO" id="orderDAO"/>
<bean autowire="byType" class="com.hspedu.Spring.Service.OrderService" id="orderService" />
<bean autowire="byType" class="com.hspedu.Spring.web.OrderAction" id="orderAction" />
byName
- 如果我们设置的是 autowrie="byName" 表示通过名字自动完成装配
- 例如 :
<bean autowire="byName" class="com.hspedu.Spring.Service.OrderService" id="orderService" />
- 先看OrderService的属性 private OrderDAO orderDAO
- 根据这个属性的 setXxx() 方法的 xxx来找对象的id
- public void setOrderDAO(OrderDAO orderDAO){...} 会根据id=orderDAO 对象来进行自动装配
- 如果没有就配置失败
演示
配置bean的xml文件
注意看, 这里的id是 orderDAO2
<bean class="com.hspedu.Spring.DAO.OrderDAO" id="orderDAO2"/>
<bean autowire="byName" class="com.hspedu.Spring.Service.OrderService" id="orderService" />
<bean autowire="byName" class="com.hspedu.Spring.web.OrderAction" id="orderAction" />
</beans>
对应的Java文件
public class OrderService {
private OrderDAO orderDAO;
public OrderDAO getOrderDAO() {
return orderDAO;
}
// 注意看里是 setOrderDAO2
public void setOrderDAO2(OrderDAO orderDAO) {
this.orderDAO = orderDAO;
}
}
Spring EL 表达式 【了解】
- Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操作对象。
- 和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象
- SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL 表达式。
- 不是重点,如果看到有人这样使用,能看懂即可
<?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 class="com.hspedu.Spring.bean.Monster" id="monster01">
<property name="monsterId" value="100"/>
<property name="name" value="牛魔王"/>
<property name="skill" value="芭蕉扇"/>
</bean>
<bean id="spELBean" class="com.hspedu.Spring.bean.SpELBean">
<!-- sp el 给字面量 也可以直接赋值 -->
<property name="name" value="#{'韩顺平教育'}"/>
<!-- sp el 引用其它 bean --><property name="monster" value="#{monster01}"/>
<!-- sp el 引用其它 bean 的属性值 -->
<property name="monsterName" value="#{monster01.name}"/>
<!-- sp el 调用普通方法 赋值 -->
<property name="crySound" value="#{spELBean.crySound()}"/>
<!-- sp el 调用静态方法 赋值 -->
<property name="bookName" value="#{T(com.hspedu.Spring.bean.SpELBean).read(' 天龙八部')}"/>
<!-- sp el 通过运算赋值 -->
<property name="result" value="#{89*1.2}"/>
</bean>
</beans>
基于注解配置 bean
● 基本介绍 基于注解的方式配置 bean, 主要是项目开发中的组件,比如Controller、Service、和DAO.
● 组件注解的形式有
- @Component 表示当前注解标识的是一个组件
- @Controller 表示当前注解标识的是一个控制器,通常用于Servlet
- @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于Service 类
- @Repository 表示当前注解标识的是一个持久化层的类,通常用于Dao 类
快速入门
@Component
public class MyComponent {
}
@Controller
public class UserAction {
}
@Repository
public class UserDAO {
}
@Service
public class UserService {
}
注意细节
- 需要导入 spring-aop-5.3.8.jar , 别忘了
- 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.hspedu.spring.component" />
可以使用通配符 * 来指定 ,比如 com.hspedu.spring.* 表示
--老韩提问: com.hspedu.spring.component
会不会去扫描它的子包? 答:会的
-
Spring 的 IOC 容器不能检测一个使用了
@Controller
注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 【也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的】 -
为什么是 .class 而不是 . Java。答 : 因为运行之后就切换到工作路径下去了, 即
out目录
表示只扫描满足要求的类.[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]
<context:component-scan base-package="com.hspedu.spring.component"resource-pattern="User*.class" />
- 排除某些注解类
<!--
需求希望排除某个包/及其子包下面的某种类型的注解
1. context:exclude-filter 使用这个标签
2. type 指定排除的方式, 一般使用注解的方式
3. expression 要写某一个注解的全路径, 比如: org.springframework.stereotype.Service
-->
<context:component-scan base-package="com.hspedu.Spring.component">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
- 指定自动扫描哪些注解类
注意, 需要use-default-filters="false" 这个必须指定
<!--
1. context:include-filter 这个表示要去扫描那些类
2. type="annotation" 按照注解的方式过滤/扫描
3. expression="org.springframework.stereotype.Controller" 指定扫描类型的全路径
-->
<context:component-scan base-package="com.hspedu.Spring.component" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
- 默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的
value
属性指定id
值,并且 value 可以省略 `
@Controller(value="userAction01")
@Controller("userAction01")
- 扩展-@Controller 、@Service、@Component 区别 : (回去看看一下老师的讲解的注解基础) zhuanlan.zhihu.com/p/454638478
自己实现注解方式
具体看实现代码
自动装配
基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
@AutoWired 的规则说明
- 在 IOC 容器中查找待装配的组件的类型,如果有唯一的bean 匹配,则使用该bean装配
- 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的
属性的属性名
作为 id 值再进行查找, 找到就装配,找不到就抛异常 - 先按类型, 再按名字
UserAction
类
@Controller
public class UserAction {
@Autowired
private UserService userService200;
public void sayOk() {
System.out.println("UserAction sayOk()~");
userService200.hi();
System.out.println("UserAction 中的 userService的hash值是 = " + userService200);
}
}
UserService
类
@Service
public class UserService {
public void hi() {
System.out.println("UserService hi() ~~");
}
}
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="com.hspedu.Spring.component"/>
<bean id="userService200" class="com.hspedu.Spring.component.UserService" />
<bean id="userService300" class="com.hspedu.Spring.component.UserService" />
</beans>
测试方法
@Test
public void setPropertyAutowired() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans08.xml");
UserAction userAction = ioc.getBean("userAction", UserAction.class);
userAction.sayOk();
Object userService = ioc.getBean("userService200");
System.out.println("userService = " + userService);
System.out.println("userAction = " + userAction);
}
@Resource 的规则说明
@Resource
有两个属性是比较重要的,分是name
和type
, Spring 将@Resource注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用name属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用byType 自动注入策略- 如果@Resource 没有指定 name 和 type , 则
先使用byName
注入策略, 如果匹配不上,再使用 byType 策略
, 如果都不成功,就会报错 - 没有指定, 则先按名字再按类型
@Controller
public class UserAction {
/*
1. @Resource(name = "userService") 表示装配的是 id=userService的对象
2. 使用type属性, 需要保证对应的类就一个
3. 如果不写的话, 先按 name 再按 type
*/
@Resource
private UserService userService200;
public void sayOk() {
System.out.println("UserAction sayOk()~");
userService200.hi();
System.out.println("UserAction 中的 userService的hash值是 = " + userService200);
}
}
细节
- 老韩建议,不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以注入.
- 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
- @AutoWired 可以配合注解 @Qualifier 进行指定装配的id
@Controller
public class UserAction {
// 者两个需要一起写
@Autowired
@Qualifier(value="userService200")
private UserService userService200;
public void sayOk() {
System.out.println("UserAction sayOk()~");
userService200.hi();
System.out.println("UserAction 中的 userService的hash值是 = " + userService200);
}
}
泛型依赖注入
- 只要让BasicService和BaseDao建立关系, 那么继承他们的泛型接口也会自动实现注入
- 传统方法是将 PhoneDao /BookDao 自动装配到 BookService/PhoneSerive 中,当这种继承关系多时,就比较麻烦,可以使用 spring 提供的泛型依赖注入
BaseService
类
@Service
public abstract class BaseService<T> {
@Autowired
private BaseDAO<T> baseDao;
public void save() {
baseDao.save();
}
}
BaseDAO
类
@Repository
public abstract class BaseDAO<T> {
public abstract void save();
}
}
实现后的效果
AOP
动态代理 【重要!!!!】
小案例入手
Vehicle
接口
public interface Vehicle {
void run();
String fly(int height);
}
ship
类 实现了Vehicle
接口
public class Ship implements Vehicle{
@Override
public void run() {
// System.out.println("交通工具开始运行了...");
System.out.println("大轮船在水上 running...");
// System.out.println("交通工具停止运行了...");
}
@Override
public String fly(int height) {
System.out.println("轮船在天上飞 = " + height);
return "轮船在天上飞 = " + height;
}
}
VehicleProxyProvider
代理类
public class VehicleProxyProvider {
// 定义一个属性
// target_vehicle 表示真正要执行的对象
// 该对象需要实现Vehicle接口
private Vehicle targetVehicle;
public VehicleProxyProvider(Vehicle targetVehicle) {
this.targetVehicle = targetVehicle;
}
// 编写一个方法, 返回一个代理对象
public Vehicle getProxy() {
/*
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
老师解读
1. Proxy.newProxyInstance() 可以返回一个代理对象
2. ClassLoader loader 类的加载器
3. Class<?>[] interfaces 就是将来代理类的接口信息
4. InvocationHandler h 调用出库去/对象 , 有一个非常重要的方法
*/
ClassLoader classLoader = targetVehicle.getClass().getClassLoader();
// 拿到对象的接口信息, 底层是通过接口来调用
Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();
/*
InvocationHandler h 这个是一个接口, 不能直接对象, 所以需要 匿名内部类
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
*/
InvocationHandler h = new InvocationHandler() {
/**
* invoke 方法是将来执行我们的 targetVehicle 的方法, 会调用
* @param proxy 代表代理对象
* @param method 就是通过代理对象调用方法 代理对象.run()
* @param args 表示调用代理对象调用方法的xx参数 代理对象.run(xx)
* @return 代理对象.run(xx) 返回的数据
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里以后就是前置通知
System.out.println("交通工具开始运行了...");
// 这里反射加动态代理
Object invoke = method.invoke(targetVehicle, args);
System.out.println("交通工具停止运行了...");
return invoke;
}
};
Object instance = Proxy.newProxyInstance(classLoader, interfaces, h);
return (Vehicle) instance;
}
}
测试程序
@Test
public void proxyRun() throws Exception {
Vehicle ship = new Car();
// 创建了 vehicleProxyProvider 并且传入了要代理的对象ship
VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider(ship);
// 获取代理对象
// proxy可以代理执行方法
// 编译类型是 Vehicle
// 运行类型是 代理类型 class com.sun.proxy.$Proxy4
// 当执行到run方法时会执行到代理对象的invoke
Vehicle proxy = vehicleProxyProvider.getProxy();
System.out.println("运行类型是 : " + proxy.getClass());
// 这个动态体现在很多不同的方面 1.对象 2. 方法
String fly = proxy.fly(100);
System.out.println("fly = " + fly);
}
下面是对getTargetVehicle()
方法中每一行代码的详细解释:
public Vehicle getTargetVehicle() {
// 获取目标车辆的类加载器
ClassLoader classLoader = targetVehicle.getClass().getClassLoader();
// 获取目标车辆实现的接口信息
Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();
// 创建一个InvocationHandler对象
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 从AOP来看, 这个也是一个横切关注点
System.out.println("方法执行前 - 日志-方法名-" + method.getName() + "-参数 " + Arrays.asList(args));
result = method.invoke(smartAnimal, args);
System.out.println("方法执行正常结束 - 日志-方法名-" + method.getName() + "-结果 result= " + result);
return result;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
// 如果出现异常就会进入到catch {}
// 从AOP来看, 这个也是一个横切关注点
System.out.println("方法执行异常结束 !!! --" + method.getName() + "--" + e.getClass().getName());
} finally {
// 不管有没有异常, 都会执行
// 从AOP来看, 这个也是一个横切关注点, 最终通知
System.out.println("方法执行结束 -- 日志 -- 方法" + method.getName());
}
};
// 使用类加载器、接口信息和InvocationHandler创建代理对象
Object instance = Proxy.newProxyInstance(classLoader, interfaces, h);
// 将代理对象转换为Vehicle类型并返回
return (Vehicle) instance;
}
解释每一行代码的作用:
-
ClassLoader classLoader = targetVehicle.getClass().getClassLoader();
:获取目标车辆对象的类加载器。类加载器用于加载和创建新的类实例。 -
Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();
:获取目标车辆对象所实现的接口信息。这将用于创建代理对象,确保代理对象与目标车辆对象实现相同的接口。 -
InvocationHandler h = new InvocationHandler() { ... }
:创建一个匿名内部类作为InvocationHandler
接口的实现。InvocationHandler
接口用于定义代理对象的调用处理程序。 -
在匿名内部类的
invoke()
方法内部,我们定义了方法调用前后的处理逻辑。在这个例子中,它会在调用目标车辆对象的方法之前打印"轮船开始运行~",在方法调用后打印"轮船结束运行~"。 -
Object instance = Proxy.newProxyInstance(classLoader, interfaces, h);
:使用类加载器、接口信息和InvocationHandler
创建代理对象。Proxy.newProxyInstance()
方法根据提供的参数创建一个代理对象,该代理对象将在方法调用时委托给InvocationHandler
的invoke()
方法进行处理。 -
return (Vehicle) instance;
:将代理对象转换为Vehicle
类型并返回。由于代理对象实现了Vehicle
接口,所以可以将其转换为Vehicle
类型,以便在代码其他部分使用。
通过这个代理对象,你可以在调用目标车辆对象的方法之前和之后执行额外的逻辑,例如打印日志、权限检查等。
AOP基本介绍
● AOP 实现方式
- 基于动态代理的方式[内置 aop 实现]
- 使用框架 aspectj 来实现 真正的SpringAOP!!
AOP快速入门
说明
- 需要引入核心的 aspect 包
- 在切面类中声明通知方法
- 前置通知:@Before
- 返回通知:@AfterReturning
- 异常通知:@AfterThrowing
catch
{ } 里面 - 后置通知:@After 在
finally
{ } 里面 - 环绕通知:@Around 将四个通知合并管理
切片类
@Component // 会注入到Spring容器
@Aspect // 表示是一个切面类 【底层切面编程的支撑(动态代理 + 反射 + 动态绑定 ~~)】
public class SmartAnimalAspect {
/**
* 希望将f1 切入到dog-getSum前执行
* 1. @Before 表示前置通知 目标函数执行方法前执行
* 2. value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))" 表示那个类的那个方法
* 3. f1方法可以理解为一个切入方法, 方法名由程序猿指定 比如:showBeginLog
* 4. JoinPoint joinPoint 在底层执行时会自动传入joinPoint对象
*/
@Before(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))")
public static void showBeginLog(JoinPoint joinPoint) {
// 拿到方法签名
Signature signature = joinPoint.getSignature();
System.out.println("方法执行前 - 日志 - 方法名- :" + signature.getName() + " -参数 " + Arrays.asList(joinPoint.getArgs()));
}
// 把f2切入到正常结束之后的通知
@AfterReturning(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))")
public void showSuccessLog(JoinPoint joinPoint) {
System.out.println("方法执行正常结束-日志-方法名: " + joinPoint.getSignature().getName());
}
@AfterThrowing(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))")
public void showExceptionLog(JoinPoint joinPoint) {
System.out.println("方法执行异常-日志-方法名: " + joinPoint.getSignature().getName());
}
// 切入到方法执行之后 finally {}
@After(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))")
public void showFinallyEndingLog(JoinPoint joinPoint) {
System.out.println("方法最终执行完毕 finally{} -日志-方法名: " + joinPoint.getSignature().getName());
}
}
测试方法
@Test
public void test1() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans10.xml");
// 这里我们通过接口类型来获取注入到Dog对象1
// 不能按照dog类型获取
// 在底层任然还是dog对象, 但是当getBean()时就相当于 之前写的 getproxy() 方法, 而不只是单纯的拿到dog对象
SmartAnimal bean = ioc.getBean(SmartAnimal.class);
// System.out.println(bean.getClass());
int sum = bean.getSum(1, 2);
System.out.println("======");
int sub = bean.getSub(10, 3);
}
细节说明
- 关于切面类方法命名可以自己规范一下, 比如 showBeginLog() . showSuccessEndLog()showExceptionLog(), showFinallyEndLog()
- 切入表达式的更多配置,比如使用
模糊配置
@Before(value="execution(* com.hspedu.aop.proxy.SmartDog.*(..))")
- 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value="execution(* *.*(..))")
必须要开启的设置
<!-- 开启基于注解的 AOP 功能 -->
<aop:aspectj-autoproxy/>
- 当 spring 容器开启了 , 我们获取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型了!
- 当 spring 容器开启了 , 我们获取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型.
AOP-切入表达式
- 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
- 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
- 切入表达式也可以对没有实现接口的类,进行切入
@Component // 把Car视为一个组件, 注入到容器之中
public class Car {
public void run() {
System.out.println("小汽车在running~~");
}
}
@Test
public void testHomeWork2() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans11.xml");
Car bean = ioc.getBean(Car.class);
bean.run();
// class com.hspedu.Spring.AOP.homework.Car$$EnhancerBySpringCGLIB$$e919e523
System.out.println("bean.getClass() = " + bean.getClass());
}
AOP-JoinPoint
JoinPoint 是指程序执行过程中能够被拦截的特定点,例如方法的调用、方法的执行、异常的抛出等。
- getArgs():获取方法参数数组。使用
joinPoint.getArgs()
可以获取被拦截方法的参数数组。 - getSignature():获取方法签名。使用
joinPoint.getSignature()
可以获取被拦截方法的方法签名,包括方法名、返回类型等信息。 - getTarget():获取目标对象。使用
joinPoint.getTarget()
可以获取被拦截方法所属的目标对象。 - getThis():获取代理对象。使用
joinPoint.getThis()
可以获取代理对象,即实际执行方法的对象。 - proceed():继续执行方法。在环绕通知(Around Advice)中,使用
joinPoint.proceed()
可以继续执行被拦截的方法。 - getStaticPart():获取静态部分。使用
joinPoint.getStaticPart()
可以获取静态部分的信息,包括被拦截方法的签名和参数。 - getSourceLocation():获取源码位置。使用
joinPoint.getSourceLocation()
可以获取被拦截方法在源码中的位置信息。 - getModifiers() : 返回目标方法的修饰符号 返回的是数字 例如 : 如果一个方法具有
public static
修饰符,那么它的修饰符整数值就是 9(1(public) + 8(static))public
:1private
:2protected
:4static
:8final
:16synchronized
:32volatile
:64transient
:128native
:256abstract
:1024strictfp
:2048
public static void showBeginLog(JoinPoint joinPoint) {
// 拿到方法签名
Signature signature = joinPoint.getSignature();
System.out.println("方法执行前 - 日志 - 方法名- :" + signature.getName() + " -参数 " + Arrays.asList(joinPoint.getArgs()));
joinPoint.getSignature().getName(); // 获取目标方法名
joinPoint.getSignature().getDeclaringType().getSimpleName(); // 获取目标方法所属类的简单类名
joinPoint.getSignature().getDeclaringTypeName();// 获取目标方法所属类的类名
joinPoint.getSignature().getModifiers(); // 获取目标方法声明类型(public、private、protected)
Object[] args = joinPoint.getArgs(); // 获取传入目标方法的参数,返回一个数组
joinPoint.getTarget(); // 获取被代理的对象
joinPoint.getThis(); // 获取代理对象自己
}
AOP-返回通知获取结果
需求 : 如何在返回通知方法获取返回结果
要得到结果需要在 @AfterReturning 这里面获取, 其他的比如@Before那么就不行
/*
1. 如果我们希望把目标方法执行的结果 ,返回切入方法
2. 可以再 @AfterReturning 增加属性, returning = "res"
3. 同时在切入方法 增加属性 Object res
4. 注意名称需要一致
*/
@AfterReturning(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))", returning = "res")
public void showSuccessLog(JoinPoint joinPoint, Object res) {
// 目标方法的返回结果
System.out.println("返回的结果是: " + res);
System.out.println("方法执行正常结束-日志-方法名: " + joinPoint.getSignature().getName());
}
AOP-异常通知中获取异常
- 异常通知方法中获取异常
在方法中使用 Throwable
接收
@AfterThrowing(value = "execution(public int com.hspedu.Spring.AOP.aspectj.Dog.getSum(int ,int))", throwing = "mes")
public void showExceptionLog(JoinPoint joinPoint, Throwable mes) {
System.out.println("方法执行异常-日志-方法名: " + joinPoint.getSignature().getName());
// 异常是mes = java.lang.ArithmeticException: / by zero
System.out.println("异常是mes = " + mes);
}
AOP-环绕通知【了解】
- 环绕通知可以完成其它四个通知要做的事情
注意
- 切入表达式形参需要是 ProceedingJoinPoint
- 需要 try-catch-finally
@Component
@Aspect
public class SmartAnimalAspect2 {
// 切入表达式, 这个就是可以代替前面4个注解
@Around(value = "execution(public int Dog.getSum(int ,int))")
public Object doAround(ProceedingJoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
Object result = null;
try {
System.out.println("AOP环绕通知 --" + name + "方法开始执行--形参有: " + Arrays.asList(args));
result = joinPoint.proceed();
System.out.println("AOP环绕通知 " + name + " 方法执行结束 -- 结果是 " + result);
} catch (Throwable e) {
System.out.println("AOP环绕通知, 出现异常: " + e);
} finally {
System.out.println("AOP环绕通知最终通知 ~~");
}
return result;
}
}
AOP-切入点表达式重用
● 切入点表达式重用
为了统一管理切入点表达式,可以使用切入点表达式重用技术。
注意
- @Before(value = "myPointCut()") 需要写 ""
- @Pointcut 对应方法需要写的注解
@Component
@Aspect
public class UsbAspect {
@Pointcut(value = "execution(public int com.hspedu.Spring.AOP.homework.Phone.work())")
public void myPointCut() {
}
@Before(value = "myPointCut()")
public void showBeginLog(JoinPoint joinPoint) {
System.out.println("前置通知-调用的方法名是 " + joinPoint.getSignature().getName());
}
@AfterReturning(value = "myPointCut()", returning = "res")
public void showSuccessLog(JoinPoint joinPoint, Object res) {
System.out.println("正常执行后-输出的方法名" + joinPoint.getSignature().getName() + "-返回值是 " + res);
}
@AfterThrowing(value = "myPointCut()", throwing = "mes")
public void showExceptionLog(JoinPoint joinPoint, Throwable mes) {
System.out.println("出现异常 方法名:" + joinPoint.getSignature().getName() + "异常是 " + mes);
}
}
AOP-切面优先级问题
- 如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制.
- @order(value=n) 来控制 n 值越小,优先级越高.
- order 注解在 org.springframework.core.annotation.Order
执行顺序
类似Filter
的过滤链式调用机制
@Aspect
@Order(1)
public class FirstAspect {
// 切面逻辑
}
@Aspect
@Order(2)
public class SecondAspect {
// 切面逻辑
}
AOP-基于 XML 配置 AOP
- 前面我们是通过注解来配置 aop 的,在 spring 中,我们也可以通过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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 基于xml, 实现aop编程-->
<bean class="com.hspedu.Spring.AOP.xml.Dog" id="dog"/>
<bean class="com.hspedu.Spring.AOP.xml.SmartAnimalAspect" id="animalAspect"/>
<!-- 必须要引入名称空间 xmlns:aop="http://www.springframework.org/schema/aop"-->
<aop:config>
<!-- 先配置切入点, 在配置切面对象-->
<aop:pointcut id="myPointCut" expression="execution(public int com.hspedu.Spring.AOP.xml.Dog.getSum(int , int))"/>
<!-- 这里就是制定切面对象-->
<aop:aspect ref="animalAspect" order="10">
<!-- 配置前置通知-->
<aop:before method="showBeginLog" pointcut-ref="myPointCut" />
<!-- 返回通知-->
<aop:after-returning method="showSuccessLog" pointcut-ref="myPointCut" returning="res"/>
<!-- 异常通知-->
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="mes"/>
<!-- 最终通知-->
<aop:after method="showFinallyEndingLog" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
转载自:https://juejin.cn/post/7237856777588654137