Spring5学习笔记

1,803 阅读24分钟

总览

  1. Spring 核心学习内容 IOC、AOP, jdbcTemplate, 声明式事务
  2. IOC: 控制反转 , 可以管理 java 对象
  3. AOP : 切面编程
  4. JDBCTemplate : 是 spring 提供一套访问数据库的技术, 应用性强,相对好理解
  5. 声明式事务: 基于 ioc/aop 实现事务管理, 理解有需要小伙伴花时间
  6. IOC, AOP 是重点同时难点

Spring几个重要的概念

  1. Spring 可以整合其他的框架(老韩解读: Spring 是管理框架的框架)
  2. Spring 有两个核心的概念: IOC 和 AOP
  3. IOC [Inversion Of Control 反转控制]

● 传统的开发模式[JdbcUtils / 反射]

程序------>环境 程序读取环境配置,然后自己创建对象

● IOC 的开发模式 [EmpAction EmpService EmpDao Emp]

程序<-----容器 容器创建好对象,程序直接使用

  1. DI—Dependency Injection 依赖注入,可以理解成是 IOC 的另外叫法.
  2. 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包管理分为两个方面

  1. 创建bean对象
  2. 给bean注入属性

Bean的配置方式

  1. 基于xml文件配置方式
  2. 基于注解方式

基于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);  
  
}

细节

  1. 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException
  2. 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或XxxService 在一个线程中只需要一个对象实例(单例)的情况
  3. 老师这里在说明一下: 在容器配置文件(比如 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 对象的 相互引用

  1. 这里的ref就是依赖注入, spring底层帮你实现
  2. 注意在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中。具体实现方法如下:

  1. 数组类型的属性注入:
<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的字符串数组。

  1. 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的字符串列表。

  1. 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的字符串集合。

  1. 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的字符串键值对集合。

  1. 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);  
	}  
  
}

  1. 通过静态工厂获取
  2. class 是工厂的路径
  3. factory-method 表示的是指定工厂类是由哪一个对象返回
  4. constructor—-arg 的value指的是要指定返回哪一个工厂对象
<bean id="my_monster01" 
	  class="com.hspedu.Spring.Factory.MyStaticFactory" 
	  factory-method="getMonster">  
	<constructor-arg value="1"/>  
</bean>

通过实例工厂获取对象

  1. 因为是非静态的所以需要造对象
  2. factory-bean 指定使用拿一个实例工厂
  3. factory-method 指定使用实例工厂的哪一个方法
  4. 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 创建顺序

  1. 在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象
<bean id="student01" class="com.hspedu.bean.Student" />
<bean id="department01" class="com.hspedu.bean.Department" />
  1. 会先创建 department01 对象,再创建 student01 对象.
<bean id="student01" class="com.hspedu.bean.Student" 
	  设置了这个属性, 创建的循序就会发生变化
	  depends-on="department01"/><bean id="department01" class="com.hspedu.bean.Department" />

一个问题

  1. 先看下面的配置, 请问两个 bean 创建的顺序是什么? 并分析执行流程
    1. 先创建 id=memberDAOImpl
    2. 再创建 id = memberServiceImpl
    3. 调用 memberServiceImpl.setMemberDAO() 完成引用
  1. 先看下面的配置, 请问两个 bean 创建的顺序是什么, 并分析执行流程
    1. 先创建 id = memberServiceImpl
    2. 再创建 id=memberDAOImpl
      1. 用 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>

细节

  1. 默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到singletonObjects 集合
  2. 当 设置为多实例机制后, 该bean 是在getBean()时才创建
  3. 如 果 是 单 例 singleton, 同 时 希 望 在 getBean 时 才创建, 可以指定懒加载lazy-init="true" (注意默认是 false)
  4. 通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
  5. 如果 scope="prototype" 这时你的 lazy-init 属性的值不管是ture, 还是false都是在getBean 时候,才创建对象.

bean 的生命周期

● 说明: bean 对象创建是由 JVM 完成的,然后执行如下方法

  1. 执行构造器
  2. 执行 set 相关方法
  3. 调用 bean 的初始化的方法(需要配置) 可以 自定义名字
  4. 使用 bean
  5. 当容器关闭时候,调用 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()..");  
	}  
}

配置文件

  1. init-method= 指定bean初始化函数, 在getter方法之后
  2. destroy-method 指定bean销毁时指定的方法
  3. 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();  
}

细节

  1. 初始化 init 方法和 destory 方法, 是程序员来指定
  2. 销毁方法就是当关闭容器时,才会被调用, 直接退出不会调用

配置 bean 的后置处理器 【难点】

  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码

定义一个后置处理器

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
  1. 可以通过autowire 实现自动装配
  2. autowire="byType" 通过类型自动完成赋值/引用
  3. 比如OrderService 中有 OrderDAO 属性, 如果容器中如果过有OrderDAO 这个类型的对象就会自动装配
  4. 如果使用byType 那么不能有两个(或以上)这个类型的对象
  5. 如果没有没有属性那么 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
  1. 如果我们设置的是 autowrie="byName" 表示通过名字自动完成装配
  2. 例如 :
<bean autowire="byName" class="com.hspedu.Spring.Service.OrderService" id="orderService" />
  1. 先看OrderService的属性 private OrderDAO orderDAO
  2. 根据这个属性的 setXxx() 方法的 xxx来找对象的id
  3. public void setOrderDAO(OrderDAO orderDAO){...} 会根据id=orderDAO 对象来进行自动装配
  4. 如果没有就配置失败

演示

配置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 表达式 【了解】

  1. Spring Expression Language,Spring 表达式语言,简称 SpEL。支持运行时查询并可以操作对象。
  2. 和 EL 表达式一样,SpEL 根据 JavaBean 风格的 getXxx()、setXxx()方法定义的属性访问对象
  3. SpEL 使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL 表达式。
  4. 不是重点,如果看到有人这样使用,能看懂即可
<?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.

● 组件注解的形式有

  1. @Component 表示当前注解标识的是一个组件
  2. @Controller 表示当前注解标识的是一个控制器,通常用于Servlet
  3. @Service 表示当前注解标识的是一个处理业务逻辑的类,通常用于Service 类
  4. @Repository 表示当前注解标识的是一个持久化层的类,通常用于Dao 类

快速入门

@Component  
public class MyComponent {  
  
}
@Controller  
public class UserAction {  

}
@Repository  
public class UserDAO {  

}
@Service  
public class UserService {  

}

注意细节

  1. 需要导入 spring-aop-5.3.8.jar , 别忘了
  2. 必须在 Spring 配置文件中指定"自动扫描的包",IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.hspedu.spring.component" />

可以使用通配符 * 来指定 ,比如 com.hspedu.spring.* 表示

--老韩提问: com.hspedu.spring.component 会不会去扫描它的子包? 答:会的

  1. Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 【也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的】

  2. 为什么是 .class 而不是 . Java。答 : 因为运行之后就切换到工作路径下去了, 即out目录

表示只扫描满足要求的类.[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]
<context:component-scan base-package="com.hspedu.spring.component"resource-pattern="User*.class" />
  1. 排除某些注解类
<!--  
	需求希望排除某个包/及其子包下面的某种类型的注解  
	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>
  1. 指定自动扫描哪些注解类

注意, 需要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>
  1. 默认情况:标记注解后,类名首字母小写作为 id 的值。也可以使用注解的value属性指定id 值,并且 value 可以省略 `
@Controller(value="userAction01") 
@Controller("userAction01")
  1. 扩展-@Controller 、@Service、@Component 区别 : (回去看看一下老师的讲解的注解基础) zhuanlan.zhihu.com/p/454638478

自己实现注解方式

具体看实现代码

自动装配

基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource

@AutoWired 的规则说明
  1. 在 IOC 容器中查找待装配的组件的类型,如果有唯一的bean 匹配,则使用该bean装配
  2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
  3. 先按类型, 再按名字

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 的规则说明
  1. @Resource 有两个属性是比较重要的,分是 nametype, Spring 将@Resource注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用name属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用byType 自动注入策略
  2. 如果@Resource 没有指定 name 和 type , 则先使用byName注入策略, 如果匹配不上, 再使用 byType 策略, 如果都不成功,就会报错
  3. 没有指定, 则先按名字再按类型
@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);  
	}  
  
}
细节
  1. 老韩建议,不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以注入.
  2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
  3. @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;
}

解释每一行代码的作用:

  1. ClassLoader classLoader = targetVehicle.getClass().getClassLoader();:获取目标车辆对象的类加载器。类加载器用于加载和创建新的类实例。

  2. Class<?>[] interfaces = targetVehicle.getClass().getInterfaces();:获取目标车辆对象所实现的接口信息。这将用于创建代理对象,确保代理对象与目标车辆对象实现相同的接口。

  3. InvocationHandler h = new InvocationHandler() { ... }:创建一个匿名内部类作为InvocationHandler接口的实现。InvocationHandler接口用于定义代理对象的调用处理程序。

  4. 在匿名内部类的invoke()方法内部,我们定义了方法调用前后的处理逻辑。在这个例子中,它会在调用目标车辆对象的方法之前打印"轮船开始运行~",在方法调用后打印"轮船结束运行~"。

  5. Object instance = Proxy.newProxyInstance(classLoader, interfaces, h);:使用类加载器、接口信息和InvocationHandler创建代理对象。Proxy.newProxyInstance()方法根据提供的参数创建一个代理对象,该代理对象将在方法调用时委托给InvocationHandlerinvoke()方法进行处理。

  6. return (Vehicle) instance;:将代理对象转换为Vehicle类型并返回。由于代理对象实现了Vehicle接口,所以可以将其转换为Vehicle类型,以便在代码其他部分使用。

通过这个代理对象,你可以在调用目标车辆对象的方法之前和之后执行额外的逻辑,例如打印日志、权限检查等。

AOP基本介绍

● AOP 实现方式

  1. 基于动态代理的方式[内置 aop 实现]
  2. 使用框架 aspectj 来实现 真正的SpringAOP!!

AOP快速入门

说明

  1. 需要引入核心的 aspect 包
  2. 在切面类中声明通知方法
    1. 前置通知:@Before
    2. 返回通知:@AfterReturning
    3. 异常通知:@AfterThrowing catch{ } 里面
    4. 后置通知:@After 在finally{ } 里面
    5. 环绕通知:@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);  
}

细节说明

  1. 关于切面类方法命名可以自己规范一下, 比如 showBeginLog() . showSuccessEndLog()showExceptionLog(), showFinallyEndLog()
  2. 切入表达式的更多配置,比如使用模糊配置
@Before(value="execution(* com.hspedu.aop.proxy.SmartDog.*(..))")
  1. 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value="execution(* *.*(..))")


必须要开启的设置
<!-- 开启基于注解的 AOP 功能 --> 
<aop:aspectj-autoproxy/>

  1. 当 spring 容器开启了 , 我们获取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型了!
  2. 当 spring 容器开启了 , 我们获取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型.

AOP-切入表达式

  1. 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
  2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
  3. 切入表达式也可以对没有实现接口的类,进行切入
@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 是指程序执行过程中能够被拦截的特定点,例如方法的调用、方法的执行、异常的抛出等。

  1. getArgs():获取方法参数数组。使用 joinPoint.getArgs() 可以获取被拦截方法的参数数组。
  2. getSignature():获取方法签名。使用 joinPoint.getSignature() 可以获取被拦截方法的方法签名,包括方法名、返回类型等信息。
  3. getTarget():获取目标对象。使用 joinPoint.getTarget() 可以获取被拦截方法所属的目标对象。
  4. getThis():获取代理对象。使用 joinPoint.getThis() 可以获取代理对象,即实际执行方法的对象。
  5. proceed():继续执行方法。在环绕通知(Around Advice)中,使用 joinPoint.proceed() 可以继续执行被拦截的方法。
  6. getStaticPart():获取静态部分。使用 joinPoint.getStaticPart() 可以获取静态部分的信息,包括被拦截方法的签名和参数。
  7. getSourceLocation():获取源码位置。使用 joinPoint.getSourceLocation() 可以获取被拦截方法在源码中的位置信息。
  8. getModifiers() : 返回目标方法的修饰符号 返回的是数字 例如 : 如果一个方法具有 public static 修饰符,那么它的修饰符整数值就是 9(1(public) + 8(static))
    • public:1
    • private:2
    • protected:4
    • static:8
    • final:16
    • synchronized:32
    • volatile:64
    • transient:128
    • native:256
    • abstract:1024
    • strictfp: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-环绕通知【了解】

  • 环绕通知可以完成其它四个通知要做的事情

注意

  1. 切入表达式形参需要是 ProceedingJoinPoint
  2. 需要 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-切入点表达式重用

● 切入点表达式重用

为了统一管理切入点表达式,可以使用切入点表达式重用技术。

注意

  1. @Before(value = "myPointCut()") 需要写 ""
  2. @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>