本博客参照了韩顺平老师的 Spring 课程讲义!
1 Spring 基本介绍 1.1 官方资料 1.1.2 Spring5 下载 ……
1.1.4 离线文档 : 解压 spring-5.3.8-dist.zipspring-framework-5.3.8\docs\reference\html\index.html 1.1.5 离线API: 解压 spring-5.3.8-dist.zipspring-framework-5.3.8\docs\javadoc-api\index.html 1.2 Spring 学习的核心内容 一图胜千言
老韩解读上图:
1、Spring 核心学习内容 IOC、AOP, jdbcTemplate, 声明式事务
2、IOC: 控制反转 , 可以管理 java 对象
3、AOP : 切面编程
4、 JDBCTemplate : 是 spring 提供一套访问数据库的技术, 应用性强,相对好理解
5、声明式事务: 基于 ioc/aop 实现事务管理, 理解需要小伙伴花时间
6、IOC, AOP 是重点同时难点
1.3 Spring 几个重要概念
Spring 可以整合其他的框架(老韩解读: Spring 是管理框架的框架)
Spring 有两个核心的概念: IOC 和 AOP
IOC [Inversion Of Control 反转控制]
● 传统的开发模式[JdbcUtils / 反射] 程序——>环境 //程序读取环境配置,然后自己创建对象.
老韩解读上图(以连接到数据库为例说明)
1、程序员编写程序, 在程序中读取配置信息
2、**(程序员)创建对象**, new Object???() || 反射方式
3、使用对象完成任务
● IOC 的开发模式 [EmpAction EmpService EmpDao Emp] 程序<—–容器 //容器创建好对象,程序直接使用
老韩解读上图
1、Spring 根据配置文件 xml || 注解, 创建对象 , 并放入到容器(如:ConcurrentHashMap)中,并且可以完成对象之间的依赖
2、**当需要使用某个对象实例的时候, 就直接从容器中获取即可 **
3、程序员可以更加关注如何使用对象完成相应的业务 (以前是 new … ==> 注解/配置方式)
4、DI—Dependency Injection 依赖注入 ,可以理解成是 IOC 的另外叫法.
5、Spring最大的价值,通过配置,给程序提供需要使用的web 层 [Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity]对象 ,这个是核心价值所在,也是 ioc 的具体体现, 实现了解耦
1.4 Spring 快速入门 1.4.1 需求说明
通过 Spring 的方式[配置文件], 获取 JavaBean: Monster 的对象, 并给该的对象属性赋值, 输出该对象信息
1.4.2 完成步骤 ……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <beans > <bean class ="com.study.spring.bean.Monster" id ="monster01" > <property name ="monsterId" value ="100" /> <property name ="name" value ="牛魔王" /> <property name ="skill" value ="芭蕉扇" /> </bean > <bean class ="com.study.spring.bean.Monster" id ="monster02" > <property name ="monsterId" value ="1001" /> <property name ="name" value ="牛魔王~" /> <property name ="skill" value ="芭蕉扇~" /> </bean > </beans >
1.4.3 注意事项和细节 1、 说明 ClassPathXmlApplicationContext ioc =new ClassPathXmlApplicationContext(“beans.xml”);
——为什么读取到 beans.xml
2、解释一下类加载路径. 可以给学员输出一下: // 获取「类加载路径 」
// File f = new File(this.getClass().getResource(“/“).getPath());
// System.out.println(f);
—输出——项目文件夹\工程名\out\production\工程名
3、debug 看看 spring 容器结构/机制, 记住你是 OOP 程序员,重要!
老韩说明: 注意配置 debugger, 否则你看到的 debug 视图和老师不一样
4、ioc structure summary: ioc容器(ClassPathXmlApplicationContext):
——beanFactory(DefaultListableBeanFactory)
————beanDefinitionMap(ConcurrentHashMap)
——————table(ConcurrentHashMap$Node[512])
——————可以存放beans.xml中的bean节点配置的bean对象信息
——————key是 在beans.xml中配置的bean的id,value是这个bean的各种信息(如:属性、属性值、类信息、是不是懒加载等信息)
————singletonObjects(ConcurrentHashMap)
——————table(ConcurrentHashMap$Node[512])
——————在beans.xml文件中配置的对象默认是单例的,会初始化在该table中;还有一些spring初始化的——————单例对象
————beanDefinitionNames(ArrayList)
————存放了beans.xml中,所有配置的bean的id,方便查看
用户想要一个对象—>先去beanDefinitionMap的table中找其定义的位置看看:如果是多例,则直接创建一个对象并返回;如果是单例的,则去singletonObjects的table中取出事先创建好的对象返回给用户
1.5 手动开发- 简单的 Spring 基于 XML 配置的程序 1.5.1 需求说明
自己写一个简单的 Spring 容器, 通过读取 beans.xml, 获取第 1 个 JavaBean: Monster 的对象, 并给该的对象属性赋值, 放入到容器中, 输出该对象信息.
也就是说,不使用 Spring 原生框架,我们自己简单模拟实现
可以让小伙伴了解 Spring 容器的简单机制
1.5.2 思路分析
思路分析/图解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 package com.study.spring.hspapplicationcontext;import com.study.spring.bean.Monster;import org.dom4j.Document;import org.dom4j.DocumentException;import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.File;import java.lang.reflect.Method;import java.util.List;import java.util.concurrent.ConcurrentHashMap;public class HspApplicationContext { private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap <>(); public HspApplicationContext (String iocBeanXmlFile) throws Exception { String path = this .getClass().getResource("/" ).getPath(); SAXReader saxReader = new SAXReader (); Document document = saxReader.read(new File (path + iocBeanXmlFile)); Element rootElement = document.getRootElement(); Element bean = (Element) rootElement.elements("bean" ).get(0 ); String id = bean.attributeValue("id" ); String classFullPath = bean.attributeValue("class" ); List<Element> property = bean.elements("property" ); Integer monsterId = Integer.parseInt(property.get(0 ).attributeValue("value" )); String name = property.get(1 ).attributeValue("value" ); String skill = property.get(2 ).attributeValue("value" ); Class<?> aClass = Class.forName(classFullPath); Monster o = (Monster) aClass.newInstance(); o.setMonsterId(monsterId); o.setName(name); o.setSkill(skill); singletonObjects.put(id, o); } public Object getBean (String id) { return singletonObjects.get(id); } }
1.6 🌟Spring 原生容器底层结构 1.6.1 一图胜千言
1.6.2 课后作业题
在 beans.xml 中, 我们注入 2 个 Monster 对象, 但是不指定 id,如下
1 2 3 4 5 6 7 8 9 10 <bean class ="com.study.spring.beans.Monster" > <property name ="monsterId" value ="1010" /> <property name ="name" value ="牛魔王~" /> <property name ="skill" value ="芭蕉扇~" /> </bean > <bean class ="com.study.spring.beans.Monster" > <property name ="monsterId" value ="666" /> <property name ="name" value ="牛魔王~~!!" /> <property name ="skill" value ="芭蕉扇~~" /> </bean >
问题 1:运行会不会报错
问题 2:如果不报错, 你能否找到分配的 id, 并获得到该对象.
答:系统会默认分配 id ,分配 id 的规则是 全类名#0 , 全类名#1 这样的规则来分配 id , 我们可以通过 debug 方式来查看.
2 Spring 管理 Bean-IOC 2.1 Spring 配置/管理 bean 介绍 2.1.1 Bean 管理包括两方面 2.1.1.1 创建 bean 对象 2.1.1.2 给 bean 注入属性 2.1.2 Bean 配置方式 2.1.2.1 基于 xml 文件配置方式 2.1.2.2 基于注解方式 2.2 基于 XML 配置 bean 2.2.1 通过类型来获取 bean 2.2.1.1 应用案例 ● 案例说明:
通过 spring 的 ioc 容器, 获取一个 bean 对象
说明:获取 bean 的方式:按类型
1 Monster monster = ioc.getBean(Monster.class);
2.2.1.2 细节说明
按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常 NoUniqueBeanDefinitionException
这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程中只需要一个对象实例(单例)的情况
老师这里在说明一下: 在容器配置文件(比如 beans.xml)中给属性赋值, 底层是通过 setter 方法完成的 , 这也是为什么我们需要提供 setter 方法的原因
2.2.2 通过构造器配置 bean 2.2.2.1 应用案例 ● 案例说明:
在 spring 的 ioc 容器, 可以通过构造器来配置 bean 对象
● 完成步骤
在 beans.xml 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <bean id ="monster03" class ="com.study.spring.bean.Monster" > <constructor-arg value ="200" index ="0" /> <constructor-arg value ="白骨精" index ="1" /> <constructor-arg value ="吸人血" index ="2" /> </bean > <bean id ="monster04" class ="com.study.spring.bean.Monster" > <constructor-arg value ="200" name ="monsterId" /> <constructor-arg value ="白骨精" name ="name" /> <constructor-arg value ="吸人血" name ="skill" /> </bean > <bean id ="monster05" class ="com.study.spring.bean.Monster" > <constructor-arg value ="300" type ="java.lang.Integer" /> <constructor-arg value ="白骨精~" type ="java.lang.String" /> <constructor-arg value ="吸人血~" type ="java.lang.String" /> </bean >
2.2.2.2 使用细节
通过 index 属性来区分是第几个参数
通过 type 属性来区分是什么类型(按照顺序)
2.2.3 通过 p 名称空间配置 bean 1 2 3 4 5 6 7 8 9 10 11 12 13 在beans标签中添加 xmlns:p="http://www.springframework.org/schema/p" <bean id ="monster06" class ="com.study.spring.bean.Monster" p:monsterId ="500" p:name ="红孩儿" p:skill ="吐火" />
2.2.4 引用/注入其它 bean 对象(某类中有其他类型的数据,也需要初始化) 2.2.4.1 实例演示 ● 案例说明:在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的相互引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <bean class ="com.study.spring.dao.MemberDAOImpl" id ="memberDAO" /> <bean class ="com.study.spring.service.MemberServiceImpl" id ="memberService" > <property name ="memberDAO" ref ="memberDAO" /> </bean >
2.2.5 引用/注入内部 bean 对象 1 2 3 4 5 <bean id ="memberServiceImpl02" class ="com.study.spring.service.MemberServiceImpl" > <property name ="memberDAO" > <bean class ="com.study.spring.dao.MemberDAOImpl" /> </property > </bean >
2.2.6 引用/注入集合/数组类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <bean class ="com.study.spring.bean.Master" id ="master" > <property name ="name" value ="太上老君" /> <property name ="monsterList" > <list > <ref bean ="monster01" /> <ref bean ="monster02" /> <bean class ="com.study.spring.bean.Monster" > <property name ="name" value ="老鼠精" /> <property name ="monsterId" value ="100" /> <property name ="skill" value ="吃粮食" /> </bean > </list > </property > <property name ="monsterMap" > <map > <entry > <key > <value > monster03</value > </key > <ref bean ="monster03" /> </entry > <entry > <key > <value > monster04</value > </key > <ref bean ="monster04" /> </entry > </map > </property > <property name ="monsterSet" > <set > <ref bean ="monster05" /> <ref bean ="monster06" /> <bean class ="com.study.spring.bean.Monster" > <property name ="name" value ="金角大王" /> <property name ="skill" value ="吐水" /> <property name ="monsterId" value ="666" /> </bean > </set > </property > <property name ="monsterName" > <array > <value > 小妖怪</value > <value > 大妖怪</value > <value > 老妖怪</value > </array > </property > <property name ="pros" > <props > <prop key ="username" > root</prop > <prop key ="password" > 123456</prop > <prop key ="ip" > 127.0.0.1</prop > </props > </property > </bean >
2.2.6.2 使用细节
主要掌握 List/Map/Properties 三种集合的使用.
Properties 集合的特点
2.2.7 通过 util 名称空间创建 list 2.2.7.1 实例演示 ● 案例说明
spring 的 ioc 容器, 可以通过 util 名称空间创建 list 集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 xmlns:util="http://www.springframework.org/schema/util" <util:list id ="myBookList" > <value > 三国演义</value > <value > 红楼梦</value > <value > 西游记</value > <value > 水浒传</value > </util:list > <bean class ="com.study.spring.bean.BookStore" id ="bookStore" > <property name ="bookList" ref ="myBookList" /> </bean >
2.2.8 级联属性赋值 2.2.8.1 实例演示 ● 案例说明
spring 的 ioc 容器, 可以直接给对象属性的属性赋值, 即级联属性赋值
1 2 3 4 5 6 7 8 9 <bean class ="com.study.spring.bean.Dept" id ="dept" /> <bean class ="com.study.spring.bean.Emp" id ="emp" > <property name ="name" value ="jack" /> <property name ="dept" ref ="dept" /> <property name ="dept.name" value ="Java开发部门" /> </bean >
2.2.9 通过静态工厂获取对象 2.2.9.1 实例演示 ● 案例说明
在 spring 的 ioc 容器, 可以通过静态工厂获取 bean 对象
创 建com\study\spring\factory\MyStaticFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class MyStaticFactory { private static Map<String, Monster> monsterMap; static { monsterMap = new HashMap <>(); monsterMap.put("monster01" , new Monster (100 ,"牛魔王" ,"芭蕉扇" )); monsterMap.put("monster02" , new Monster (200 ,"狐狸精" ,"美人计" )); } public static Monster getMonster (String key) { return monsterMap.get(key); } }
修改 beans.xml , 增加配置
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="my_monster01" class ="com.study.spring.factory.MyStaticFactory" factory-method ="getMonster" > <constructor-arg value ="monster02" /> </bean >
2.2.10 通过实例工厂获取对象 2.2.10.1 实例演示 ● 案例说明在 spring 的 ioc 容器, 可以通过实例工厂获取 bean 对象
● 完成步骤1. 创 建com\study\spring\factory\MyInstanceFactory.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MyInstanceFactory { private Map<String, Monster> monster_map; { monster_map = new HashMap <>(); monster_map.put("monster03" , new Monster (300 , "牛魔王~" , "芭蕉扇~" )); monster_map.put("monster04" , new Monster (400 , "狐狸精~" , "美人计~" )); } public Monster getMonster (String key) { return monster_map.get(key); } }
配置 beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <bean class ="com.study.spring.factory.MyInstanceFactory" id ="myInstanceFactory" /> <bean class ="com.study.spring.factory.MyInstanceFactory" id ="myInstanceFactory2" /> <bean id ="my_monster02" factory-bean ="myInstanceFactory" factory-method ="getMonster" > <constructor-arg value ="monster03" /> </bean > <bean id ="my_monster03" factory-bean ="myInstanceFactory2" factory-method ="getMonster" > <constructor-arg value ="monster03" /> </bean >
2.2.12 通过 FactoryBean 获取对象(重点) 2.2.12.1 应用实例 ● 案例说明
在 spring 的 ioc 容器,通过 FactoryBean 获取 bean 对象(重点)
创 建com\study\spring\factory\MyFactoryBean.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class MyFactoryBean implements FactoryBean <Monster> { private String key; private Map<String, Monster> monster_map; { monster_map = new HashMap <>(); monster_map.put("monster03" , new Monster (300 , "牛魔王~" , "芭蕉扇~" )); monster_map.put("monster04" , new Monster (400 , "狐狸精~" , "美人计~" )); } 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 false ; } }
配置 beans.xml
1 2 3 4 5 6 7 8 9 <bean id ="my_monster05" class ="com.study.spring.factory.MyFactoryBean" > <property name ="key" value ="monster04" /> </bean >
2.2.13 bean 配置信息重用(继承) 2.2.13.1 应用实例 ● 说明
在 spring 的 ioc 容器, 提供了一种继承的方式来实现 bean 配置信息的重用
● 应用实例演示
配置 beans.xml
1 2 3 4 5 6 7 8 9 10 11 <bean id ="monster12" class ="com.study.spring.bean.Monster" abstract ="true" > <property name ="monsterId" value ="100" /> <property name ="name" value ="蜈蚣精~" /> <property name ="skill" value ="蜇人~" /> </bean > <bean id ="monster13" class ="com.study.spring.bean.Monster" parent ="monster12" />
2.2.14 bean 创建顺序 2.2.14.1 实例演示 ● 说明
在 spring 的 ioc 容器, 默认是按照配置的顺序创建 bean 对象
1 2 3 4 5 6 7 8 <bean id ="student01" class ="com.hspedu.bean.Student" depends-on ="department01" /> <bean id ="department01" class ="com.hspedu.bean.Department" />
2.2.14.2 一个问题
● 问题说明
先看下面的配置, 请问两个 bean 创建的顺序是什么? 并分析执行流
先创建 id=memberDAOImpl
再创建 id = memberServiceImpl
调用 memberServiceImpl.setMemberDAO() 完成引用
先看下面的配置, 请问两个 bean 创建的顺序是什么, 并分析执行流程
先创建 id = memberServiceImpl
再创建 id=memberDAOImpl
用 memberServiceImpl.setMemberDAO() 完成引用. (像是栈
2.2.15 bean 对象的单例和多例 2.2.15.1 应用实例 ● 说明
在 spring 的 ioc 容器, 在默认是按照单例创建的, 即配置一个 bean 对象后, ioc 容器只会创建一个 bean 实例。
如果,我们希望 ioc 容器配置的某个 bean 对象, 是以多个实例形式创建的则可以通过配置scope=”prototype” 来指定
配置 beans.xml:
1 2 3 4 5 6 7 8 9 10 11 12 13 <bean id ="cat" class ="com.study.spring.bean.Cat" scope ="prototype" lazy-init ="false" > <property name ="id" value ="100" /> <property name ="name" value ="小花猫" /> </bean >
2.2.15.2 使用细节
默认是单例 singleton, 在启动容器时, 默认就会创建 , 并放入到 singletonObjects 集合
当 设置为多实例机制后, 该 bean 是在 getBean()时才创建
如果是单例 singleton, 同时希望在 getBean 时才创建 , 可以指定懒加载lazy-init=”true” (注意默认是 false)
通常情况下, lazy-init 就使用默认值 false , 在开发看来, 用空间换时间是值得的, 除非有特殊的要求.
如果 scope=”prototype” 这时你的 lazy-init 属性的值不管是 ture, 还是 false 都是在getBean 时候,才创建对象.
2.2.16 bean 的生命周期 2.2.16.1 应用实例 ● 说明: bean 对象创建是由 JVM 完成的,然后执行如下方法
1. 执行构造器
2. 执行 set 相关方法
3. 调用 bean 的初始化的方法(需要配置)
4. 使用 bean
5. 当容器关闭时候,调用 bean 的销毁方法(需要配置)
● 应用实例演示
创建 com.hspedu.spring.beans;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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()=" + name); this .name = name; } public void init () { System.out.println("House init().." ); } public void destroy () { System.out.println("House destroy().." ); } @Override public String toString () { return "House{" + "name='" + name + '\'' + '}' ; } }
配置 beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 <bean class ="com.hspedu.spring.bean.House" id ="house" init-method ="init" destroy-method ="destroy" > <property name ="name" value ="北京豪宅" /> </bean >
2.2.16.2 使用细节
初始化 init 方法和 destory 方法, 是程序员来指定
销毁方法就是当关闭容器时,才会被调用.
2.2.17 配置 bean 的后置处理器 【这个比较难】 2.2.17.1 应用实例 ● 说明
在 spring 的 ioc 容器,可以配置 bean 的后置处理器
该处理器/对象会在 bean 初始化方法调用前 和初始化方法调用后 被调用
程序员可以在后置处理器中编写自己的代码
后置处理器创建好后,也注册在beans.xml中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @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 null ; } @Override public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization().. bean=" + bean + " beanName=" + beanName); return bean; }
1 2 3 4 5 6 7 <bean class ="com.hspedu.spring.bean.MyBeanPostProcessor" id ="myBeanPostProcessor" />
2.2.17.2 其它说明 1、 怎么执行到这个方法?=> 使用 AOP(反射+动态代理+IO+容器+注解)
2、有什么用?=> 可以对 IOC 容器中所有的对象进行统一处理 ,比如 日志处理/权限的校验/安全的验证/事务管理.
-初步体验案例: 如果类型是 House 的统一 改成 上海豪宅
3、针对容器的所有对象吗? 是的=>切面编程特点
4、后面我们会自己实现这个底层机制,这个是一个比较难理解的知识点, 现在不做过多的纠结,后面我会带小伙伴实现这个机制
2.2.18 通过属性文件给 bean 注入值 2.2.18.1 应用实例 ● 说明
在 spring 的 ioc 容器,通过属性文件给 bean 注入值
● 应用实例演示
src/ 创建 my.properties
1 2 3 monsterId =1000 name =\u4e4c\u9f9f\u7cbe skill =\u7f29\u8116\u5b50
修改 src\beans.xml , 继续完成配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 xmlns:context="http://www.springframework.org/schema/context" <context:property-placeholder location ="classpath:my.properties" /> <bean id ="monster100" class ="com.hspedu.spring.beans.Monster" > <property name ="monsterId" value ="${id}" /> <property name ="name" value ="${name}" /> <property name ="skill" value ="${skill}" /> </bean >
2.2.19 基于 XML 的 bean 的自动装配 2.2.19.1 应用实例 ● 说明
在 spring 的 ioc 容器,可以实现自动装配 bean
● 应用实例演示
这里说的 Action 就是我们前面讲过的 Servlet->充当 Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <bean class ="com.hspedu.spring.dao.OrderDao" id ="orderDao" /> <bean autowire ="byName" class ="com.hspedu.spring.service.OrderService" id ="orderService" /> <bean autowire ="byName" class ="com.hspedu.spring.web.OrderAction" id ="orderAction" />
2.2.19.2 其它说明
1. 这个知识点作为了解即可, 后面我们主要还是使用基于注解的方式(重点.)
1. 但是机制和原理类似
2.3 🌟基于注解配置 bean 2.3.18 基本使用 2.3.18.1 说明 ● 基本介绍基于注解的方式配置 bean, 主要是项目开发中的组件, 比如 Controller、 Service、 和 Dao.
● 组件注解的形式有
@Component 表示当前注解标识的是一个组件 ,最宽泛,只是把该类注册到spring ioc容器中
@Controller 表示当前注解标识的是一个控制器, 通常用于 Servlet
@Service 表示当前注解标识的是一个处理业务逻辑的类 , 通常用于 Service 类
@Repository 表示当前注解标识的是一个持久化层的类 , 通常用于 Dao 类
2.3.18.2 快速入门 ● 应用实例使用注解的方式来配置 Controller / Service / Respository / Component
● 代码实现
1. 引入 spring-aop-5.3.8.jar , 在 spring/libs 下拷贝即可
2.创建 UserAction.java UserService.java, UserDao.java MyComponent.java
3.配置 beans.xml(配置自动扫描的包 )
1 2 <context:component-scan base-package ="com.study.spring.component" />
2.3.18.3 注意事项和细节说明
需要导入 spring-aop-5.3.8.jar , 别忘了
必须在 Spring 配置文件中指定”自动扫描的包”,IOC 容器才能够检测到当前项目中哪些类被标识了注解, 注意到导入 context 名称空间
1 2 <context:component-scan base-package ="com.hspedu.spring.component" />
可以使用通配符 * 来指定 ,比如 com.hspedu.spring.* 表示–提问: com.hspedu.spring.component 会不会去扫描它的子包? –答:会的
Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的 ]
<context:component-scan base-package=”com.hspedu.spring.component”resource-pattern=”User* .class” />resource-pattern=”User*.class”: 表示只扫描满足要求的类 .[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]
<context:component-scan base-package=”com.hspedu.spring.component” >
<context:exclude-filter type=”annotation”expression=”org.springframework.stereotype.Service”/>
1 2 3 4 5 1) <context:exclude-filter> 放在<context:component-scan>内,表示扫描过滤掉当前包的某些类 2) type="annotation" 按照注解类型进行过滤. 3) expression :就是注解的全类名,比如 org.springframework.stereotype.Service 就是@Service 注解的全类名,其它比@Controller @Repository 等 依次类推 4) 上面表示过滤掉 com.hspedu.spring.component 包下,加入了@Service 注解的类 5) 完成测试, 修改 beans.xml, 增加 exclude-filter , 发现 UserService, 不会注入到容器.
指定自动扫描哪些注解类
1 2 3 4 5 6 7 8 9 10 11 12 13 <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:include-filter type ="annotation" expression ="org.springframework.stereotype.Repository" /> </context:component-scan >
默认情况:标记注解后,默认把类名首字母小写作为 id 的值。也可以使用注解的 value 属性指定 id 值,并且 value 可以省略。
扩展-@Controller 、@Service、@Component 区别 : (回去看看一下老师的讲解的注解基础) https://zhuanlan.zhihu.com/p/454638478
2.3.19 🌟手动开发简单的 Spring 基于注解配置的程序 -老韩要求: 小伙伴要至少独立写 2 遍
2.3.19.1 需求说明
自 己 写 一 个 简 单 的 Spring 容 器 , 通 过 读 取 类 的 注 解 (@Component @Controller@Service @Reponsitory), 将对象注入到 IOC 容器
也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合 技术实现, 打通 Spring 注解方式开发的技术痛点
2.3.19.2 思路分析
思路分析+程序结构
1)我们使用注解方式完成, 这里老韩不用 xml 来配
2)程序框架图
2.3.19.3 代码实现 ● 应用实例
手动实现注解的方式来配置 Controller / Service / Respository / Component
我们使用自定义注解来完成.
● 代码实现
仍然使用前面的 \com\hspedu\spring\component\ 包下的类
创建 ComponentScan.java
1 2 3 4 5 6 7 8 9 10 11 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentScan { String value () default "" ; }
创 建 HspSpringConfig.java
1 2 3 4 5 6 7 8 9 10 package com.hspedu.spring.annotation;@ComponentScan(value = "com.hspedu.spring.component") public class HspSpringConfig {}
创 建 HspSpringApplicationContext.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 public class HspSpringApplicationContext { private Class configClass; private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap <>(); public HspSpringApplicationContext (Class configClass) { this .configClass = configClass; System.out.println("this.configClass=" + this .configClass); ComponentScan componentScan = (ComponentScan) this .configClass.getDeclaredAnnotation(ComponentScan.class); String path = componentScan.value(); System.out.println("要扫描的包= " + path); ClassLoader classLoader = HspApplicationContext.class.getClassLoader(); path = path.replace("." , "/" ); URL resource = classLoader.getResource(path); System.out.println("resource=" + resource); File file = new File (resource.getFile()); if (file.isDirectory()) { File[] files = file.listFiles(); for (File f : files) { System.out.println("=====================" ); System.out.println("=" + f.getAbsolutePath()); String fileAbsolutePath = f.getAbsolutePath(); if (fileAbsolutePath.endsWith(".class" )) { String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\" ) + 1 , fileAbsolutePath.indexOf(".class" )); String classFullName = path.replace("/" , "." ) + "." + className; try { Class<?> aClass = classLoader.loadClass(classFullName); if (aClass.isAnnotationPresent(Component.class) || aClass.isAnnotationPresent(Controller.class) || aClass.isAnnotationPresent(Service.class) || aClass.isAnnotationPresent(Repository.class)) { if (aClass.isAnnotationPresent(Component.class)) { Component component = aClass.getDeclaredAnnotation(Component.class); String id = component.value(); if (!"" .endsWith(id)) { className = id; } } Class<?> clazz = Class.forName(classFullName); Object instance = clazz.newInstance(); ioc.put(StringUtils.uncapitalize(className) , instance); } } catch (Exception e) { e.printStackTrace(); } } } } } public Object getBean (String name) { return ioc.get(name); } }
2.3.19.4 注意事项和细节说明 还可以通过@Component(value = “xx”) @Controller(value = “yy”) @Service(value = “zz”)中指定的 value, 给 bean 分配 id
2.3.20 自动装配 2.3.20.1 应用实例 ● 基本说明
基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
@AutoWired 的规则说明
🌟在 IOC 容器中查找待装配的组件的类型 ,如果有唯一的 bean 匹配,则使用该 bean 装配
🌟如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值 再进行查找, 找到就装配,找不到就抛异常
@Resource 的规则说明
🌟@Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略(同样,容器中只能有一个该类型的对象)
🌟如果@Resource 没有指定 name 和 type ,则先使用byName注入策略(用你的对象名字找ioc容器中的id), 如果匹配不上,再使用 byType 策略(ioc容器中该类型对象必须唯一), 如果都不成功,就会报错
总结:不管是@Autowired 还是 @Resource都保证属性名是规范的写法就可以注入.
2.3.20.2 注意事项和细节说明
如待装配的类型对应的 bean 在 IOC 容器中有多个, 则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配, 找不到就抛异常
2.3.21 泛型依赖注入 2.3.21.1 泛型依赖解释 ● 基本说明
为了更好的管理有继承和相互依赖的 bean 的自动装配 , spring 还提供基于泛型依赖的注入机制
在继承关系复杂情况下, 泛型依赖注入就会有很大的优越性
2.3.21.2 应用实例 ● 应用实例需求
各个类关系图
传统方法是将 PhoneDao /BookDao 自动装配到 BookService/PhoneSerive 中,当这种继承关系多时,就比较麻烦,可以使用 spring 提供的泛型依赖注入
(跟配置mybatis plus的service层、impl层、mapper层之间的关系很像)
3 AOP 3.1 官方文档 3.1.1 AOP 讲解: spring-framework-5.3.8/docs/reference/html/core.html#aop
3.1.2 AOP APIs : spring-framework-5.3.8/docs/reference/html/core.html#aop-api
3.2 动态代理-精致小案例 3.2.1 需求说明 ● 需求说明
有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后有统一的输出.
3.2.2 解决方案-传统方式
传统的解决思路, 在各个方法的[前, 执行过程, 后]输出日志提示信息
来思考一下, 解决方案好吗? ===> 代码冗余, 其实就是单个对象的调用,并没有很好的解决
3.2.3 解决方案-🌟动态代理方式 !!!!!!!! ● 解决方案 2-代码实现
动态代理解决思路,在调用方法时,使用反射机制,根据方法去决定调用哪个对象方法
1 2 3 4 5 6 7 8 9 10 11 package com.hspedu.spring.proxy2;public interface Vehicle { public void run () ; public String fly (int height) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 package com.hspedu.spring.proxy2;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class VehicleProxyProvider { private Vehicle target_vehicle; public VehicleProxyProvider (Vehicle target_vehicle) { this .target_vehicle = target_vehicle; } public Vehicle getProxy () { ClassLoader classLoader = target_vehicle.getClass().getClassLoader(); Class<?>[] interfaces = target_vehicle.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler () { @Override public Object invoke (Object o, Method method, Object[] args) throws Throwable { System.out.println("交通工具开始运行了...." ); Object result = method.invoke(target_vehicle, args); System.out.println("交通工具停止运行了...." ); return result; } }; Vehicle proxy = (Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.hspedu.spring.proxy2;import org.junit.Test;public class TestVehicle { @Test public void run () { Vehicle vehicle = new Ship (); vehicle.run(); } @Test public void proxyRun () { Vehicle vehicle = new Car (); VehicleProxyProvider vehicleProxyProvider = new VehicleProxyProvider (vehicle); Vehicle proxy = vehicleProxyProvider.getProxy(); System.out.println("proxy的编译类型是 Vehicle" ); System.out.println("proxy的运行类型是 " + proxy.getClass()); String result = proxy.fly(10000 ); System.out.println("result=" + result); } }
3.3 动态代理深入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package com.hspedu.spring.aop.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;public class MyProxyProvider { private SmartAnimalable target_obj; public MyProxyProvider (SmartAnimalable target_obj) { this .target_obj = target_obj; } public SmartAnimalable getProxy () { ClassLoader classLoader = target_obj.getClass().getClassLoader(); Class<?>[] interfaces = target_obj.getClass().getInterfaces(); InvocationHandler invocationHandler = new InvocationHandler () { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Object result = null ; try { System.out.println("方法执行前-日志-方法名-" + method.getName() + "-参数 " + Arrays.asList(args)); result = method.invoke(target_obj, args); System.out.println("方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= " + result); } catch (Exception e) { e.printStackTrace(); System.out.println("方法执行异常-日志-方法名-" + method.getName() + "-异常类型=" + e.getClass().getName()); } finally { System.out.println("方法最终结束-日志-方法名-" + method.getName()); } return result; } }; SmartAnimalable proxy = (SmartAnimalable)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); return proxy; } }
3.4 老韩分析: 问题再次出现 3.4.1 问题提出 ● 问题提出
在 MyProxyProvider.java 中, 我们的输出语句功能比较弱,在实际开发中,我们希望是以一个方法的形式,嵌入到真正执行的目标方法前,怎么办?
也就是如图分析
3.4.2 用老韩的土方法解决
需求分析: 使用老韩的土方法解决前面的问题 => 后面使用 Spring 的 AOP 组件完成, 先过苦日子, 再过甜日子
先新建一个包,把相关文件拷贝过来,进行修改完成,思路更加清晰.
老师学习小技巧:新建一个包,保留原来的代码
具体实现方式省略…………把红框里的代码在同一类中自行封装成一个个方法即可
该方法问题分析:耦合度高
3.4.3 对土方法解耦-开发简易的 AOP 类 把这些方法抽出,放到一个类中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class HspAOP { public static void before (Object proxy, Method method, Object[] args) { System.out.println("HspAOP-方法执行前-日志-方法名-" + method.getName() + "-参数 " + Arrays.asList(args)); } public static void after (Method method, Object result) { System.out.println("HspAOP-方法执行正常结束-日志-方法名-" + method.getName() + "-结果result= " + result); } }
3.4.4 再次分析-提出 Spring AOP 3.4.4.1 土方法 不够灵活 3.4.4.2 土方法 复用性差 3.4.4.3 土方法 还是一种硬编码(因为没有注解和反射支撑) 3.4.4.4 Spring AOP 闪亮登场-底层是 ASPECTJ 3.4.4.5 有了前面的技术引导, 理解 Spring AOP 就水到渠成 3.5 AOP 的基本介绍 ● 什么是 AOP
AOP 的全称(aspect oriented programming) ,面向切面编程
● 2 张示意图说明 AOP 的相关概念
一张简易图说明 AOP
一张详细图说明 AOP
● AOP 实现方式
基于动态代理的方式[内置 aop 实现]
使用框架 aspectj 来实现
3.6 AOP 编程快速入门 3.6.1 基本说明 ● 说明
需要引入核心的 aspect 包
在切面类中声明通知方法
前置通知:@Before
返回通知:@AfterReturning
异常通知:@AfterThrowing
后置通知:@After
环绕通知:@Around
3.6.2 快速入门实例 ● 需求说明
我们使用 aop 编程的方式,来实现手写的动态代理案例效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 package com.hspedu.spring.aop.aspectj;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.*;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import java.util.Arrays;@Order(value = 1) @Aspect @Component public class SmartAnimalAspect3 { @Before(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))") public void showBeginLog (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs())); } @AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res") public void showSuccessEndLog (JoinPoint joinPoint, Object res) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res); } @AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable") public void showExceptionLog (JoinPoint joinPoint, Throwable throwable) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable); } @After(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))") public void showFinallyEndLog (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName()); } }
1 2 <aop:aspectj-autoproxy />
3.6.3 细节说明
关于切面类方法命名可以自己规范一下, 比如 showBeginLog(), showSuccessEndLog(), showExceptionLog() , showFinallyEndLog()
切入表达式的更多配置,比如使用模糊配置@Before(value=”execution(* com.hspedu.aop.proxy.SmartDog.*(..))”)
表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
@Before(value=”execution(* . (..))”)
当 spring 容器开启了 < aop:aspectj-autoproxy/> , 我们获取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型了!
当 spring 容器开启了 < aop:aspectj-autoproxy/> , 我们获取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型
3.7 AOP-切入表达式 3.7.1 具体使用
3.7.2 注意事项和细节
切入表达式可以指向类的方法, 这时切入表达式会对该类/对象生效 (类名.*)
切入表达式也可以指向接口的方法 , 这时切入表达式会**对实现了接口的类/对象生效 **
切入表达式也可以对没有实现接口的类,进行切入
老师补充: 动态代理 jdk 的 Proxy 与 Spring 的 CGlib
🌟https://www.cnblogs.com/threeAgePie/p/15832586.html
—对没有实现接口的类生成的代理是CGlib动态代理
两个动态代理的区别
JDK动态代理是面向接口的,只能增强实现类中接口中存在的方法。CGlib是面向父类的,可以增强父类的所有方法
JDK得到的对象是JDK代理对象实例,而CGlib得到的对象是被代理对象的子类
3.8 AOP-JoinPoint 3.8.1 应用实例 ● 通过 JoinPoint 可以获取到调用方法的签名
● 应用实例需求
说明: 在调用前置通知获取到调用方法的签名, 和其它相关信息
● 应用实例-代码实现
前面我们已经举例说明过了
● 其它常用方法一览
1 2 3 4 5 6 7 8 public void beforeMethod (JoinPoint joinPoint) { joinPoint.getSignature().getName(); joinPoint.getSignature().getDeclaringType().getSimpleName(); joinPoint.getSignature().getDeclaringTypeName(); joinPoint.getSignature().getModifiers(); Object[] args = joinPoint.getArgs(); joinPoint.getTarget(); }
3.9 AOP-返回通知获取结果 3.9.1 应用实例 ● 如何在返回通知方法获取返回结果
看一个需求:
在返回通知方法获取返回的结果
1 2 3 4 5 6 7 8 9 @AfterReturning(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", returning = "res") public void showSuccessEndLog (JoinPoint joinPoint, Object res) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res); }
3.10 AOP-异常通知中获取异常 同理……
1 2 3 4 5 6 @AfterThrowing(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))", throwing = "throwable") public void showExceptionLog (JoinPoint joinPoint, Throwable throwable) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect3-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable); }
3.11 AOP-环绕通知【了解】 3.11.1 应用实例 ● 环绕通知可以完成其它四个通知要做的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Around(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))") public Object doAround (ProceedingJoinPoint joinPoint) { Object result = null ; String methodName = joinPoint.getSignature().getName(); try { Object[] args = joinPoint.getArgs(); List<Object> argList = Arrays.asList(args); System.out.println("AOP环绕通知[-前置通知]" + methodName + "方法开始了--参数有:" + argList); result = joinPoint.proceed(); System.out.println("AOP环绕通知[-返回通知]" + methodName + "方法结束了--结果是:" + result); } catch (Throwable throwable) { System.out.println("AOP环绕通知[-异常通知]" + methodName + "方法抛异常了--异常对象:" + throwable); } finally { System.out.println("AOP环绕通知[-后置通知]" + methodName + "方法最终结束了..." ); } return result; }
3.12 AOP-切入点表达式重用 3.12.1 应用实例 ● 切入点表达式重用(其实就是把切入表达式提取出来,这样可以避免当一个方法需要多次切入时,每个切入方法都需要重写一遍切入表达式的情况)
为了统一管理切入点表达式,可以使用切入点表达式重用技术
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 @Pointcut(value = "execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float, float))) ") public void myPointCut() { } //希望将myPointCut方法切入到SmartDog-getSum前执行-前置通知 /** * 老师解读 * 1. @Before 表示前置通知:即在我们的目标对象执行方法前执行 * 2. value = " execution(public float com.hspedu.spring.aop.aspectj.SmartDog.getSum(float , float ) * 指定切入到哪个类的哪个方法 形式是: 访问修饰符 返回类型 全类名.方法名(形参列表) * 3. showBeginLog方法可以理解成就是一个切入方法, 这个方法名是可以程序员指定 比如:showBeginLog * 4. JoinPoint joinPoint 在底层执行时,由AspectJ切面框架, 会给该切入方法传入 joinPoint对象 * , 通过该方法,程序员可以获取到 相关信息 * * @param joinPoint */ @Before(value = "myPointCut()") public void showBeginLog (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect-切面类showBeginLog()[使用的myPointCut()]-方法执行前-日志-方法名-" + signature.getName() + "-参数 " + Arrays.asList(joinPoint.getArgs())); } @AfterReturning(value = "myPointCut()", returning = "res") public void showSuccessEndLog (JoinPoint joinPoint, Object res) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect-切面类showSuccessEndLog()-方法执行正常结束-日志-方法名-" + signature.getName() + " 返回的结果是=" + res); } @AfterThrowing(value = "myPointCut()", throwing = "throwable") public void showExceptionLog (JoinPoint joinPoint, Throwable throwable) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect-切面类showExceptionLog()-方法执行异常-日志-方法名-" + signature.getName() + " 异常信息=" + throwable); } @After(value = "myPointCut()") public void showFinallyEndLog (JoinPoint joinPoint) { Signature signature = joinPoint.getSignature(); System.out.println("SmartAnimalAspect-切面类showFinallyEndLog()-方法最终执行完毕-日志-方法名-" + signature.getName()); }
3.13 AOP-切面优先级问题 3.13.1 应用实例 ● 切面优先级问题:
如果同一个方法,有多个切面在同一个切入点切入,那么执行的优先级如何控制.
● 基本语法:
@order(value=n) 来控制 n 值越小,优先级越高.
3.13.2 注意事项和细节说明
不能理解成: 优先级高的每个消息通知都先执行, 这个和方法调用机制(和 Filter 过滤器链式调用类似)
优先级越高,前置通知先执行(队列),但其他的通知都是最后执行(栈)
3.14 AOP-基于 XML 配置 AOP 3.14.1 应用实例 ● 基本说明:
前面我们是通过注解来配置 aop 的, 在 spring 中, 我们也可以通过 xml 的方式来配置 AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <?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" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd" > <bean class ="com.hspedu.spring.aop.xml.SmartAnimalAspect" id ="smartAnimalAspect" /> <bean class ="com.hspedu.spring.aop.xml.SmartDog" id ="smartDog" /> <aop:config > <aop:pointcut id ="myPointCut" expression ="execution(public float com.hspedu.spring.aop.xml.SmartDog.getSum(float, float)))" /> <aop:aspect ref ="smartAnimalAspect" order ="10" > <aop:before method ="showBeginLog" pointcut-ref ="myPointCut" /> <aop:after-returning method ="showSuccessEndLog" pointcut-ref ="myPointCut" returning ="res" /> <aop:after-throwing method ="showExceptionLog" pointcut-ref ="myPointCut" throwing ="throwable" /> <aop:after method ="showFinallyEndLog" pointcut-ref ="myPointCut" /> </aop:aspect > </aop:config > </beans >
4 手动实现 Spring 底层机制【 初始化 IOC容器+依赖注入+BeanPostProcessor 机制+AOP】 4.1.1 引言: 前面我们实际上已经用代码简单实现了 4.1.1.1 Spring XML 注入 bean 4.1.1.2 Spring 注解方式注入 bean 4.1.1.3 Spring AOP 动态代理实现 4.1.2 继续思考-原生 Spring 如何实现依赖注入和 singleton、 prototype 4.1.2.1 实例演示-Maven 项目 其实就是第二章,去看一下即可……
● 快速给小伙伴完成这个小案例(提示: 创建新项目, 不和原来的 Spring 混在一起)
创建工程
修改 pom.xml, 引入需要的 jar 包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.3.8</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 5.3.8</version > </dependency > </dependencies >
创 建UserAction.java
1 2 3 4 5 6 7 8 9 10 11 @Component public class UserAction {}
创 建UserDao.java
1 2 3 4 5 6 7 8 @Component public class UserDao { public void hi () { System.out.println("UserDao-hi()---" ); } }
创 建UserService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Component public class UserService { @Autowired private UserDao userDao; public void m1 () { userDao.hi(); } @PostConstruct public void init () { System.out.println("UserService-init()" ); } }
创建 beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd" > <context:component-scan base-package ="com.hspedu.spring.component" /> <context:component-scan base-package ="com.hspedu.spring.aop" /> <aop:aspectj-autoproxy /> <bean class ="com.hspedu.spring.process.MyBeanPostProcessor" id ="myBeanPostProcessor" /> </beans >
创建 AppMain.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class AppMain { public static void main (String[] args) { ApplicationContext ioc = new ClassPathXmlApplicationContext ("beans.xml" ); UserAction userAction = (UserAction) ioc.getBean("userAction" ); UserAction userAction2 = (UserAction) ioc.getBean("userAction" ); System.out.println("userAction=" + userAction); System.out.println("userAction2=" + userAction2); UserDao userDao = (UserDao) ioc.getBean("userDao" ); System.out.println("userDao=" + userDao); UserService userService = (UserService) ioc.getBean("userService" ); System.out.println("userService=" + userService); userService.m1(); SmartAnimalable smartDog = ioc.getBean(SmartAnimalable.class); smartDog.getSum(10 , 2 ); } }
4.1.2.2 思考问题 4.1.2.2.1 Spring 底层实现, 如何实现 IOC 容器创建和初始化【前面我们实现过, 现在要再深入】 4.1.2.2.2 Spring 底层实现, 如何实现 getBean, 根据 singleton 和 prototype 来返回 bean 实例 4.1.3 继续思考-原生 Spring 如何实现 BeanPostProcessor 4.1.3.1 实例演示
创 建MyBeanPostProcessor.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class MyBeanPostProcessor implements BeanPostProcessor { public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { System.out.println("postProcessBeforeInitialization 被 调 用 " + beanName + " bean= " + bean.getClass()); return bean; } public Object postProcessAfterInitialization (Object bean, String beanName) throws BeansException { System.out.println("postProcessAfterInitialization 被 调 用 " + beanName + " bean= " + bean.getClass()); return bean; } }
4.1.3.2 思考问题 4.1.3.2.1 Spring 底层实现, 如何实现 Bean 后置处理器机制 ——当然是动态代理呀
4.1.4 继续思考-原生 Spring 是如何实现 AOP 4.1.4.1 实例演示 看第三章……
4.1.4.2 简单分析 AOP 和 BeanPostProcessor 关系 ……
老韩解读:
AOP 底层是基于 BeanPostProcessor 机制的.
即在 Bean 创建好后,根据是否需要 AOP 处理 ,决定返回代理对象,还是原生 Bean
在返回代理对象时,就可以根据要代理的类和方法来返回
其实这个机制并不难,本质就是在 BeanPostProcessor 机制 + 动态代理技术
4.1.4.3 思考问题 4.1.4.3.1 Spring 底层实现, 如何实现 AOP 编程 4.1.5 我们的目标: 不用 Spring 框架, 模拟 Spring 底层实现, 也能完成相同的功能 4.2 🌟Spring 整体架构分析 4.2.1 🌟一图胜千言
4.3 手动实现 Spring 底层机制 【初始化 IOC 容器+依赖注入+BeanPostProcessor 机制+AOP】
4.3.1 实现任务阶段 1- 编写自己 Spring 容器, 实现扫描包, 得到 bean 的 class 对象 4.3.1.1 知识扩展: 类加载器 ● java 的类加载器 3 种
Bootstrap 类加载器————–对应路径 jre/lib
Ext 类加载器——————–对应路径 jre/lib/ext
App 类加载器——————-对应路径 classpath
4.3.1.2 说明: 编写自己 Spring 容器, 实现扫描包, 得到 bean 的 class 对象 🌟看p12-P41 先了解spring底层机制 具体实现日后有空再说
JDBC等知识由于之后都用mybatis等工具,也先不学
事务先不学了,之后补!
5.Spring面试准备 IOC实现原理总结: 具体来说,Spring IOC 的实现过程如下:
读取配置文件或解析注解信息,将其转换为内部的对象定义和依赖关系。在 Spring 中,可以使用 XML 文件或注解 来配置对象和依赖关系。Spring 通过解析配置文件或注解信息,将其转换为内部的对象定义和依赖关系(BeanDefinition)放到容器(BeanFactory)中。对象定义包括对象的类型、属性、构造函数等信息,依赖关系包括对象之间的依赖关系、依赖注入方式等信息。
实例化bean对象:Spring 会根据对象定义的类型和构造函数信息,使用反射机制来创建对象。
设置属性:实例化后的仍然是一个原生的状态,并没有进行依赖注入。 这一步Spring根据BeanDefinition中的信息进行属性填充,依赖注入。
调用Aware接口:Spring会检测该对象是否实现了xxxAware接口,如果有会在这里执行完成。Aware主要是能获取到Spring容器中的一些资源,然后可以供后续步骤,例如初始化阶段使用。
BeanPostProcessor前置处理:postProcessBeforeInitialzation方法。上述几个步骤后,bean对象已经被正确构造,但如果想要对象被初始化前再进行一些自定义的处理,就可以通过BeanPostProcessor接口的该方法来实现。
初始化阶段:该阶段Spring首先会看是否是实现了InitializingBean接口的afterPropertiesSet方法以及是否有自定义的init-method等,如果有会进行调用执行。
BeanPostProcessor后置处理:postProcessAfterInitialzation方法。当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理,与前面前置处理相对的,这个函数会在InitialzationBean完成后执行,因此称为后置处理。
bean初始化完成可以被使用了。
总的来说,Spring IOC 的实现原理是通过反射机制动态创建对象,依赖注入,对象初始化。通过解耦对象之间的依赖关系,使得应用程序更加灵活、可维护、可扩展。
BeanFactory和ApplicationContext的关系 BeanFactory和ApplicationContext是Spring框架中的两个重要的接口。它们都用于管理Spring Bean对象,但是它们在功能上有一些不同点。
BeanFactory是Spring框架中最基本的容器,它提供了最基础的IOC和DI的支持,它的主要功能是用于创建、管理和查找Bean对象。BeanFactory只是个接口,并不是IOC容器的具体实现, 它为其他具体的IOC容器提供了最基本的规范,例如DefaultListableBeanFactory,ApplicationContext 等容器实现或容器接口都是基于BeanFactory,再在其基础之上附加了其他的功能,原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。原始BeanFactory是延时加载,也就是说在容器启动时不会注入bean,而是在需要使用bean的时候,才会对该bean进行加载实例化。
ApplicationContext接口是基于BeanFactory扩展而来,也是一个容器接口,具有BeanFactory所有的功能,同时继承了MessageSource,所以提供了更完整的框架功能,支持国际化、资源文件访问、载入多个上下文配置文件,使得每一个上下文都专注于一个特定层次,提供在监听器中注册bean事件。ApplicationContext 是预加载,在容器启动的时候一次性加载所有的bean,所以运行的时候速度相对BeanFactory比较快,缺点就是耗内存。
总的来说,BeanFactory是Spring框架中最基本的容器,提供最基础的IOC和DI的支持;而ApplicationContext是在BeanFactory的基础上扩展而来的,提供了更多的功能和特性。ApplicationContext是Spring框架中使用较为广泛的容器。
谈谈你对AOP的理解? Spring AOP(面向切面编程)是 Spring 框架中的一个重要模块,用于解决系统中的横切关注点(cross-cutting concerns)问题。所谓横切关注点,指的是系统中分散在各个模块中、与主业务逻辑无关的代码,例如日志记录、事务管理、权限控制等。
Spring AOP 采用代理模式实现,它通过在运行期间动态代理目标对象,将横切关注点织入到系统中,从而实现了业务逻辑与横切关注点的分离。Spring AOP 主要由以下几个概念组成:
切面(Aspect):切面是一个类,它包含了一组横切关注点和相应的逻辑。一个切面通常会跨越多个对象,因此它不仅定义了横切关注点,还定义了横切关注点与业务逻辑的关系。
连接点(Join Point):连接点是在程序执行期间可以插入切面的点。例如方法调用、异常抛出等。
切入点(Pointcut):切入点是一组连接点的集合,它定义了在哪些连接点上应用切面。例如所有的方法调用、所有的异常抛出等。
通知(Advice):通知是切面在特定连接点执行的代码。Spring AOP 提供了五种类型的通知:前置通知(Before)、后置通知(After)、返回通知(After-returning)、异常通知(After-throwing)和环绕通知(Around)。
切面织入(Weaving):切面织入是将切面应用到目标对象并创建代理对象的过程。
Spring AOP 通过配置文件或注解的方式来定义切面、连接点、切入点和通知等信息,并使用代理模式将切面织入到目标对象中。通过 AOP 技术,可以有效地解耦业务逻辑和横切关注点,提高了系统的可维护性和可扩展性。
动态代理了解吗? Java动态代理是Java中一种重要的代理模式,它允许在运行时动态地生成代理类和对象,无需编写静态代理类。
在Java中,动态代理可以通过Java自带的两种方式实现:基于接口的动态代理和基于类的动态代理。
基于接口的动态代理
基于接口的动态代理是Java官方提供的一种动态代理实现方式。在这种实现方式中,代理类必须实现一个或多个接口,然后在运行时动态创建代理对象。JDK中提供了一个Proxy类和一个InvocationHandler接口来实现基于接口的动态代理。
首先,需要定义一个实现InvocationHandler接口的代理类,该类实现了代理类的逻辑。这个类中有一个invoke方法,这个方法在代理类的方法被调用时被执行。在运行时通过Proxy类的静态方法newProxyInstance生成代理类对象。这个方法需要三个参数:ClassLoader、代理类需要实现的接口数组和InvocationHandler实现类的实例。当通过代理类对象调用方法时,这个方法首先被转发到InvocationHandler的invoke方法中。在invoke方法中,可以根据代理类方法的不同来执行不同的逻辑,包括调用被代理对象的方法和执行其他的逻辑。最终,代理类的方法被执行完毕,返回结果。
基于类的动态代理
基于类的动态代理是通过字节码生成技术实现的。在这种实现方式中,代理类不需要实现接口,而是通过继承一个已有的类来实现代理功能。在Java中,可以通过CGLIB库实现基于类的动态代理。
CGLIB(Code Generation Library)是一个高性能的代码生成库,它可以在运行时动态生成字节码来实现类的增强功能。通过CGLIB库,可以直接在运行时创建目标对象的子类,从而实现基于类的动态代理。
基于类的动态代理相比于基于接口的动态代理,可以代理那些没有实现任何接口的类,更加灵活。但是它的实现原理比较复杂,需要在运行时动态生成字节码,会带来一定的性能开销。
SpringMVC的执行流程了解吗?
SpringMVC是基于MVC设计模式实现的Web框架,其工作流程如下:
客户端发送HTTP请求至前端控制器DispatcherServlet。
DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler即处理器(Controller)。
HandlerMapping根据请求URL查找对应的Controller,同时生成用于执行该请求的HandlerExecutionChain对象(包含Interceptor链)。
DispatcherServlet调用HandlerAdapter执行Handler。在执行过程中,HandlerAdapter将把ModelAndView对象传递给DispatcherServlet。
Handler执行完成后,返回一个ModelAndView对象给HandlerAdapter。
HandlerAdapter将ModelAndView对象传递给DispatcherServlet。
DispatcherServlet调用ViewResolver解析视图(View)。
ViewResolver解析出View对象后,将其返回给DispatcherServlet。
DispatcherServlet调用View对象的render()方法进行视图渲染。
DispatcherServlet将渲染后的视图返回给客户端。
在这个过程中,DispatcherServlet是整个SpringMVC的核心,它负责协调各个组件的工作。HandlerMapping负责将请求映射到对应的Controller,而HandlerAdapter负责执行Controller。ViewResolver则根据逻辑视图名(如JSP文件名)解析出View对象,最后由View渲染出实际的页面内容。通过这种分工协作的方式,SpringMVC可以实现灵活、高效、可扩展的Web应用程序开发。