Spring框架——主流框架

文章目录

  • Spring(轻量级容器框架)
  • Spring 学习的核心内容-一图胜千言
    • IOC 控制反转 的开发模式
    • Spring快速入门
    • Spring容器剖析
    • 手动开发- 简单的 Spring 基于 XML 配置的程序
    • 课堂练习
  • Spring 管理 Bean-IOC
    • Spring 配置/管理 bean 介绍
      • Bean 管理包括两方面:
    • Bean 配置方式
      • 基于 xml 文件配置方式
        • 通过类型来获取 bean
        • 通过构造器配置 bean
        • 通过 p 名称空间配置 bean
        • 引用/注入其它 bean 对象
        • 引用/注入集合/数组类型
        • 通过 util 名称空间创建 list
        • 级联属性赋值
        • 通过静态工厂获取bean对象
        • 通过实例工厂获取对象
        • 通过 FactoryBean 获取对象(重点)★
        • 通过配置信息(继承)配置bean
        • bean 对象的创建顺序
        • bean对象的单例和多例
        • bean的生命周期
        • 配置 bean 的后置处理器
        • 配置bean的后置处理器★
        • 通过属性文件给 bean 注入值
        • 基于 XML 的 bean 的自动装配
      • 基于注解方式 ★
        • 自动装配
          • AutoWired的自动装配
          • Resource的自动装配
        • 泛型依赖注入
  • AOP 切面编程
    • 动态代理
    • Spring-AOP介绍
      • ● AOP 实现方式
      • AOP 快速入门
        • ● 说明
        • 说明
      • AOP-切入表达式
      • AOP-JoinPoint
      • AOP-返回通知获取结果
      • AOP-异常通知中获取异常
      • AOP-环绕通知【了解】
      • AOP-切入点表达式重用
      • AOP-切面优先级问题
      • AOP-基于 XML 配置 AOP

Spring(轻量级容器框架)

Spring 学习的核心内容-一图胜千言

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

IOC 控制反转 的开发模式

程序<-----容器 //容器创建好对象,程序直接使用.
在这里插入图片描述
上图中:
1、Spring 根据配置文件 xml/注解, 创建对象, 并放入到容器(ConcurrentHashMap)中, 并且可以完成对象之间的依赖
2、当需要使用某个对象实例的时候, 就直接从容器中获取即可
3、程序员可以更加关注如何使用对象完成相应的业务, (以前是 new … ==> 注解/配置方式)
4. DI—Dependency Injection 依赖注入,可以理解成是 IOC 的另外叫法.
5. Spring 最大的价值,通过配置,给程序提供需要使用的web 层[Servlet(Action/Controller)]/Service/Dao/[JavaBean/entity] 对象, 这个是核心价值所在,也是 ioc 的具体体现, 实现解耦.

在这里插入图片描述

Spring快速入门

通过 Spring 的方式[配置文件],获取 JavaBean: Monster 的对象,并给该的对象属性赋
值,输出该对象信息.

  1. 下载Spring安装包
  2. 创建 Java 工程:spring5 , 为了清晰 Spring5 的各 jar 包作用,老师使用 Java 工程
  3. 引入开发 spring5 的基本包
    在这里插入图片描述
  4. 配置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,bean表示一个对象3. class属性是用于指定类的全路径 -> spring底层使用反射创建4. id属性表示该java对象在spring容器中的id, 将来通过id可以获取到该对象--><bean class="com.spring.bean.Monster" id="monster01"><property name="id" value="100"/><property name="name" value="牛魔王"/><property name="skill" value="芭蕉扇"/></bean>
</beans>
  1. 编写JavaBean
public class Monster {private int id;private String name;private String skill;// 无参构造器一定要给,Spring反射创建对象时需要使用。public Monster() {}public Monster(int id, String name, String skill) {this.id = id;this.name = name;this.skill = skill;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSkill() {return skill;}public void setSkill(String skill) {this.skill = skill;}
}
  1. 编写Test
    @Testpublic void getMonster() {// 1. 创建与配置文件关联的容器 ApplicationContext// 2.该容器和容器配置文件关联ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");// 3. 根据xml里bean的id可以通过getBean获取对应的对象// 此时默认返回的是Object,但是运行类型是Monster//Object monster01 = ioc.getBean("monster01");Monster monster01 = (Monster) ioc.getBean("monster01");    // 可以直接强转, 此时可以调用里面的属性. monster01.getName()// 也可以在获取的时候,直接指定Class 类型Monster monster = ioc.getBean("monster", Monster.class);// 4. 输出看效果System.out.println(monster01 + " 运行类型" + monster01.getClass());}

解释一下类加载路径

// 获取类加载的路径
// File f = new File(this.getClass().getResource("/").getPath());
// System.out.println(f);

debug 看看 spring 容器结构/机制, 记住你是 OOP 程序员,重要! 截图


debug的数据显示方式配置截图

Spring容器剖析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
流程方式:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

查看容器注入了哪些 bean 对象,会输出 bean 的 id

String[] str = ioc.getBeanDefinitionNames();
// for (String string : str) {
// System.out.println("..." + string);
}

底层图:
在这里插入图片描述

手动开发- 简单的 Spring 基于 XML 配置的程序

需求说明

  1. 自己写一个简单的 Spring 容器, 通过读取 beans.xml,获取第 1 个 JavaBean: Monster 的对象,并给该的对象属性赋值,放入到容器中, 输出该对象信息
  2. 也就是说,不使用 Spring 原生框架,我们自己简单模拟实现

在这里插入图片描述
本质就是两部分组成: 解析XML + 反射

导入dom4j
在这里插入图片描述

/*** @Author: GQLiu* @DATE: 2024/1/16 10:18* 用于实现Spring的一个简单容器机制* 这里做的是解析xml文件, 拿到里面的属性值.*/
public class LgqApplicationContext {// 1. 编写一个单例对象池SingletonObjectConcurrentHashMap<String, Object> SingletonObject = new ConcurrentHashMap<>();// 构造器// 接收一个容器的配置文件, 就是xml文件, 要保证该文件默认在srcpublic LgqApplicationContext(String iocBeanXmlFile) throws DocumentException, ClassNotFoundException, InstantiationException, IllegalAccessException {// 获取类加载路径String path = this.getClass().getResource("/").getPath();	// /E:/JavaCode/Spring/out/production/Spring/// 创建SaxreaderSAXReader saxReader = new SAXReader();// 得到Document对象Document document = saxReader.read(new File(path + iocBeanXmlFile));// 获取rootElement对象Element rootElement = document.getRootElement();System.out.println(rootElement.elements());         // rootElement.elements() 表示根标签下的所有标签,是一个list// 得到第一个bean-monster01Element bean = (Element) rootElement.elements("bean").get(0);   // 获取bean标签的list的索引为0的Element元素//        System.out.println(bean);String classFullPath = bean.attributeValue("class");    // 得到类全路径String id = bean.attributeValue("id");                  // 获取idList<Element> property = bean.elements("property");     // 获取bean下的property属性列表, 转成Element类型.// 遍历 这里直接根据下标直接获取Integer 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 + " "  +  name + " " + skill);System.out.println(classFullPath);System.out.println("当前的id是?" + id);// 使用反射创建对象// 首先根据全类名获取Class对象Class<?> clazz = Class.forName(classFullPath);// 使用newInstance()创建对象实例Monster monster = (Monster) clazz.newInstance();// 创建完对象实例后,需要对属性值进行赋值monster.setId(monsterId);monster.setName(name);monster.setSkill(skill);System.out.println(monster);// 将Monster放入到SingletonObjects中this.SingletonObject.put(id,monster);}public Object getBean(String id) {return this.SingletonObject.get(id);}
}

课堂练习

● 课堂练习 (10-15min): 创建一个 Car 类(id , name , price ), 具体要求如下:

  1. 创建 ioc 容器文件(配置文件),并配置一个 Car 对象(bean) .
  2. 通过 java 程序到 ioc 容器获取该 bean 对象,输出
Car.xml<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean class="com.spring.bean.Car" id="Ferrari01"><property name="id" value="0001"/><property name="name" value="Ferrari"/><property name="price" value="999999999"/></bean>
</beans>
/*** @Author: GQLiu* @DATE: 2024/1/16 13:27*/
public class homework02 {public static void main(String[] args) {// 根据配置文件创建容器ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("car.xml");Car car = ioc.getBean("Ferrari01", Car.class);	// 根据id获取对象实例System.out.println(car);}
}

Spring 管理 Bean-IOC

Spring 配置/管理 bean 介绍

Bean 管理包括两方面:

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

Bean 配置方式

基于 xml 文件配置方式

通过类型来获取 bean

就是通过类的class属性.获取类的对象实例.

  1. 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException
  2. 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程
    中只需要一个对象实例(单例)的情况
 配置xml
<bean id="monster01" class="com.hspedu.spring.beans.Monster">
<property name="monsterId" value="1"/>
<property name="name" value="牛魔王"/>
<property name="skill" value="牛魔王拳"/>
</bean>
@Testpublic void getMonsterByType() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");//1. 按类型来获取 bean, 要求 ioc 容器中的同一个类的 bean 只能有一个, 否则会抛出异常NoUniqueBeanDefinitionException//2. 这种方式的应用场景:比如 XxxAction/Servlet/Controller, 或 XxxService 在一个线程中只需要一个对象实例(单例)的情况Monster monster = ioc.getBean(Monster.class);System.out.println(monster);}
通过构造器配置 bean
配置xml<!--通过构造器获取bean对象--><bean class="com.spring.bean.Monster" id="Monster02"><!--1. 通过 index 属性来区分是第几个参数2. 通过 type 属性来区分是什么类型(按照顺序)3. 通过name属性指定是哪个变量.--><constructor-arg name="id" value="626"/><constructor-arg name="name" value="蜘蛛精"/><constructor-arg name="skill" value="吐丝"/></bean>
//通过构造器配置 bean@Testpublic void getMonsterByConstructor() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("Monster02", Monster.class);System.out.println(monster);}
通过 p 名称空间配置 bean

在 spring 的 ioc 容器, 可以通过 p 名称空间来配置 bean 对象

在 beans.xml 配置, 增加命名空间配置:

在这里插入图片描述

配置xml<!--通过 p 名称空间配置 bean--><bean class="com.spring.bean.Monster" id="Monster03"p:id="4"p:name="红孩儿"p:skill="三味真火"/>
// 通过p名称空间配置bean@Testpublic void getMonsterByP_Label() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("Monster03", Monster.class);System.out.println(monster);}
引用/注入其它 bean 对象

在 spring 的 ioc 容器, 可以通过 ref 来实现 bean 对象的相互引用

配置xml<!--引用/注入其它 bean 对象通过 ref实现bean对象的相互引用--><bean id="memberDAOImpl" class="com.spring.dao.MemberDAOImpl"/><bean id="memberServiceImpl" class="com.spring.service.MemberServiceImpl"><property name="memberDAO" ref="memberDAOImpl"/></bean>
// 通过注解方式实现对象引用// 在service类中是没有dao对象实例的,但是通过spring方法可以调用dao对象实例的方法, 因为spring框架给我们自动创建了dao对象.@Testpublic void getMonsterByRef(){ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");MemberServiceImpl memberServiceImpl = ioc.getBean("memberServiceImpl", MemberServiceImpl.class);memberServiceImpl.add();}

在这里插入图片描述

引用/注入集合/数组类型

应用实例
在这里插入图片描述

  1. 创建Monster类和Master主人类
Properties 类 是 Hashtable 的子类 , 是 key-value 的形式存储的.
Master.java
这里只写属性了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;
  1. 编写beans.xml, 给集合, 数组, map properties 赋值
<!--配置Master bean-->
<bean class="com.spring.bean.Master" id="master"><!--为Master 的 name 赋值--><property name="name" value="太上老君"/><!--给bean对象的list集合赋值--><property name="monsterList"><list><ref bean="monster01"/><ref bean="Monster02"/></list></property><!--给bean对象的map集合赋值--><property name="monsterMap"><map><entry><key>       <!--  key就是map的k --><value>monster03</value>  <!--这里的value是字符串表示--></key><!--这里的value是引用的bean--><ref bean="Monster03"/></entry><entry><key><value>monster02</value></key><ref bean="Monster02"/></entry></map></property><!--给bean对象的set集合赋值--><property name="monsterSet"><set><ref bean="monster01"/><ref bean="Monster02"/></set></property><!--给数组属性赋值--><property name="monsterName"><array><value>小妖怪</value>  <!--直接给value赋值就是直接给数组赋值--><value>大怪兽</value></array></property><!--给Properties属性赋值 结构:k(String) - v(String)--><property name="pros"><props><prop key="username">root</prop><prop key="pwd">123456</prop><prop key="ip">127.0.0.1</prop></props></property>
<bean/>
// 测试@Testpublic void SetCollectionByPro() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Master master = ioc.getBean("master", Master.class);System.out.println(master);}
通过 util 名称空间创建 list
编写JavaBean BookStorepublic class BookStore {private List<String> book;public BookStore() {}public List<String> getBook() {return book;}public void setBook(List<String> book) {this.book = book;}@Overridepublic String toString() {return "BookStore{" +"book=" + book +'}';}
}

配置util BookStore

	<util:list id="books"><value>红楼梦</value><value>三国演义</value><value>水浒传</value><value>西游记</value></util:list><bean id="bookStore" class="com.spring.bean.BookStore"><property name="book" ref="books"/></bean>

编写测试方法:

// 测试 util 名称空间@Testpublic void getListByUtil() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");BookStore bookStore = ioc.getBean("bookStore", BookStore.class);System.out.println(bookStore);}
级联属性赋值

雇员Emp有姓名name和部门dept,dept是Dept类型的.
部门Dpet类有一个属性, 是name, 表示部门名称.

在beans.xml中配置部门和雇员<!--配置Dept对象--><bean class="com.spring.bean.Dept" id="dept"/><!--配置Emp对象--><bean class="com.spring.bean.Emp" id="emp"><property name="name" value="jack"/><property name="dept" ref="dept"/><!--这里我们希望给dept的name属性指定值[级联属性赋值]--><property name="dept.name" value="销售部门"/></bean>
// 编写测试用例// 测试属性的级联赋值@Testpublic void setBeanByRelation() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Emp emp = ioc.getBean("emp", Emp.class);System.out.println(emp);}

在这里插入图片描述

通过静态工厂获取bean对象
// beans.xml中配置静态工厂的参数说明
<!--配置monster对象, 通过静态工厂获取--><!--1. 通过静态工厂获取/配置bean2. class是静态工厂的全路径3. factory-method 表示 是指定静态工厂类的 哪个方法 返回我们的对象4. constructor-arg 中value指定要返回静态工厂的 哪个对象--><bean id="StaticFactory" class="com.spring.factory.MyStaticFactory" factory-method="getMonster"><constructor-arg value="monster02"/></bean>
// 编写静态工厂
/*** @Author: GQLiu* @DATE: 2024/1/17 10:56* 静态工厂类 , 可以返回一个Monster对象*/
public class MyStaticFactory {private static Map<String, Monster> monsterMap;// 使用static代码块进行初始化// 随着类加载的执行而执行,只会执行一次。不会随着类的加载而执行。static {monsterMap = new HashMap<>();monsterMap.put("monster01", new Monster(100, "牛魔王", "芭蕉扇"));monsterMap.put("monster02", new Monster(200, "狐狸精", "美人计"));}public MyStaticFactory() {}public static Monster getMonster(String id) {return monsterMap.get(id);}
}
// 测试通过静态工厂获取bean对象
// 通过静态工厂获取bean@Testpublic void getBeanByStaticFactory() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("StaticFactory", Monster.class);System.out.println(monster);}
通过实例工厂获取对象
实例工厂类
public class MyInstanceFactory {private Map<String, Monster> monsterMap;{monsterMap = new HashMap<>();monsterMap.put("monster088", new Monster(300, "牛魔王", "芭蕉扇"));monsterMap.put("monster099", new Monster(400, "狐狸精", "美人计"));}public MyInstanceFactory() {}public Monster getMonster(String key){return this.monsterMap.get(key);}
}
<!--配置实例工厂对象--><bean class="com.spring.factory.MyInstanceFactory" id="myInstanceFactory"/><!--配置monster对象,通过实例工厂获取--><!--1. factory-bean表示指定使用哪个实例工厂返回bean2. factory-method表示指定实例工厂的哪个方法返回bean3. constructor-arg value="monster099" 表示获取到实例工厂中的哪个实例--><bean id="InstanceFactory" factory-bean="myInstanceFactory" factory-method="getMonster"><constructor-arg value="monster099"/></bean>
// 测试// 通过实例工厂获取bean@Testpublic void getBeanByInstanceFactory() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("InstanceFactory", Monster.class);Monster monster2 = ioc.getBean("InstanceFactory", Monster.class);System.out.println(monster);  // Monster{id=400, name='狐狸精', skill='美人计'}System.out.println(monster == monster2);    // true// true是因为实例工厂都是用的一个,所以里面的monster对象也都是从一个实例工厂的getMonster获取的(也就是HashMap一样)。所以monster==monster2// 这里如果生成 2 个实例工厂,那就不是true而是false了// 如果是静态工厂,就算生成了 2 个静态工厂,那产生的monster也是一样的。}
通过 FactoryBean 获取对象(重点)★
  1. 创建FactoryBean类
    通过实现FactoryBean<?>接口来创建FactoryBean对象.
/*** @Author: GQLiu* @DATE: 2024/1/20 16:57*/
public class MyFactoryBean implements FactoryBean<Monster> {// 这里的key就是配置时,要根据key获取对应对象的那个keyprivate String key;private Map<String, Monster> monsterMap;{monsterMap = new HashMap<>();monsterMap.put("monster088", new Monster(500, "牛魔王", "芭蕉扇"));monsterMap.put("monster099", new Monster(600, "狐狸精", "美人计"));}public String getKey() {return key;}public void setKey(String key) {this.key = key;}// getObject就是获取Map中的指定上面private String key的对象@Overridepublic Monster getObject() throws Exception {return this.monsterMap.get(key);}// 返回class类.@Overridepublic Class<?> getObjectType() {return Monster.class;}@Overridepublic boolean isSingleton() {return true;  //单例的。}
}
  1. 在xml中配置FactoryBean
<!--配置FactoryBean--><bean id="FactoryBean" class="com.spring.factory.MyFactoryBean"><property name="key" value="monster088"/></bean>
  1. 编写测试用例:
// 通过FactoryBean获取bean@Testpublic void getBeanByFactoryBean() {ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("FactoryBean", Monster.class);System.out.println(monster);}

就相当于多套了一层壳, key是通过在类里指定的方式获取的, 前面都是根据构造器的参数获取的:<constructor-arg value="monster099"/>, 这里是通过指定类中 的key.

通过配置信息(继承)配置bean
parent 表示继承自哪个bean实例<bean id="monster8" class="com.spring.bean.Monster" parent="monster"/>abstract表示这个bean是抽象的,不能实例化.<bean id="monster" class="com.spring.bean.Monster" abstract="true">
bean 对象的创建顺序
  1. 在默认情况下, bean创建的顺序是按照配置顺序来的.
  2. 但是如果我们增加depends-on="department"的javabean, 那就会先去创建department的javabean, 然后再去创建当前的javabean.

在这里插入图片描述

bean对象的单例和多例
单例: scope = "singleton"
多例: scope = "prototype"

在这里插入图片描述
使用细节

  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 id="monster10" class="com.spring.bean.Monster" lazy-init="true" scope="prototype"/>
bean的生命周期

bean的创建和销毁 init-method destroy-method

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

  1. 执行构造器
  2. 执行 set 相关方法
  3. 调用 bean 的初始化的方法(需要配置)
  4. 使用 bean
  5. 当容器关闭时候,调用 bean 的销毁方法(需要配置)
// 测试bean的生命周期@Testpublic void testBeanTimeLine() {// 创建容器时会调用 init-method 方法.ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");House house = ioc.getBean("house", House.class);// 关闭容器// 销毁容器时,会调用 destroy-method 方法ioc.close();}
配置 bean 的后置处理器

● 说明

  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码
配置bean的后置处理器★
  1. 在 spring 的 ioc 容器,可以配置 bean 的后置处理器
  2. 该处理器/对象会在 bean 初始化方法调用前和初始化方法调用后被调用
  3. 程序员可以在后置处理器中编写自己的代码
  4. 可以对 IOC 容器中所有的对象进行统一处理 ,比如 日志处理/权限的校验/安全的验证/事务管理
  • 首先创建后置处理器对象
    创建类, 继承自BeanPostProcessor .实现 其两个方法: postProcessBeforeInitialization & postProcessAfterInitialization
/*** @Author: GQLiu* @DATE: 2024/1/20 20:23* 配置bean的后置处理器*/
public class MyBeanPostProcessor implements BeanPostProcessor {/*** 什么时候创建: 在Bean的init方法前被调用** @param bean     传入的IOC容器中创建/配置bean* @param beanName 配置的bean的id* @return 对返回的bean进行处理, 并返回.* @throws BeansException*/@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {// 初步案例体验: 如果类型是House的, 统一改成name为北京豪宅// 对多个对象进行处理,就是切面编程if (bean instanceof House) {((House) bean).setName("北京豪宅");}System.out.println("postProcessBeforeInitialization  bean=" + bean + " beanname=" + beanName);return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);}/*** 么时候创建: 在Bean的init方法后被调用** @param bean     传入的IOC容器中创建/配置bean* @param beanName 配置的bean的id* @return 对返回的bean进行处理, 并返回.* @throws BeansException*/@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("postProcessAfterInitialization  bean=" + bean + " beanname=" + beanName);return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);}
}
  • beans2.xml文件中配置bean和后置处理器
    <bean class="com.spring.bean.House" id="house" init-method="init" destroy-method="destroy"><property name="name" value="大豪宅"/></bean><!--配置后置处理器对象1. 当在beans2.xml配置文件中配置了MyBeanPostProcessor,后置处理器的方法会被调用2. 修改会针对所有的对象, 所以是切面编程AOP--><bean class="com.spring.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>
  • 测试后置处理器
  // 测试后置处理器的使用@Testpublic void testBeanPostProcessor() {// 创建容器时会调用 init-method 方法.ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans2.xml");House house = ioc.getBean("house", House.class);// 关闭容器// 销毁容器时,会调用 destroy-method 方法ioc.close();}
通过属性文件给 bean 注入值
配置xml文件<!--1. location="classpath:my.properties" 表示指定属性文件的位置2. classPath表示类路径.3. 这时我们的属性值通过${属性名}方式获取.--><context:property-placeholder location="classpath:my.properties"/><bean id="monster100" class="com.spring.bean.Monster"><property name="id" value="${id}"/><property name="name" value="${name}"/><property name="skill" value="${skill}"/></bean>

在src下编写my.properties属性文件

my.propertiesid=998
name=jack
skill=hello
// 测试// 通过属性文件给 bean 注入值@Testpublic void setProByProFile() {// 创建容器时会调用 init-method 方法.ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");Monster monster = ioc.getBean("monster100", Monster.class);System.out.println(monster);    // Monster{id=998, name='jack', skill='hello'}// 关闭容器// 销毁容器时,会调用 destroy-method 方法ioc.close();}
基于 XML 的 bean 的自动装配

在 spring 的 ioc 容器,可以实现自动装配 bean
自动装配有两种方式, 一种是byName, 一种是byType.

配置<!--自动装配:1. autowire="byType" 表示在创建orderService时, 通过类型给对象属性自动完成赋值/引用.2. 比如OrderService 对象有orderDAO属性,就会在容器中找有没有OrderDAO类型对象.如果有就会自动装配.3. 如果用byType 方式装配, 则容器中不能有相同类型的两个对象.4. 如果你的对象没有属性, autowire就没有必要写.********************************1. autowire="byName" 表示通过名字完成装配2. Spring容器会先看OrderService属性, 再根据这个属性的setXxx()方法来找对象id. 如果没有就装配失败.--><!--配置OrderDao对象--><bean class="com.spring.dao.OrderDAO" id="orderDAO"/><!--配置OrderService对象--><bean autowire="byName" class="com.spring.service.OrderService" id="orderService"/><!--配置OrderAction对象--><bean autowire="byType" class="web.OrderAction" id="orderAction"/>
测试// 通过自动装配给对象赋值@Testpublic void setBeanByAutowire() {// 创建容器时会调用 init-method 方法.ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans2.xml");OrderAction orderAction = ioc.getBean("orderAction", OrderAction.class);System.out.println(orderAction);//验证是否装配上了OrderService;System.out.println(orderAction.getOrderService());// 验证是否装配上了OrderDAOSystem.out.println(orderAction.getOrderService().getOrderDao());// 关闭容器// 销毁容器时,会调用 destroy-method 方法ioc.close();}

基于注解方式 ★

● 基本介绍
基于注解的方式配置 bean, 主要是项目开发中的组件,比如 Controller、Service、和 Dao.

● 组件注解的形式有

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

步骤:
1… 引入 spring-aop-5.3.8.jar , 在 spring/libs 下拷贝即可
2.

<!-- 配置自动扫描的包,注意需要加入 context 名称空间 -->
<context:component-scan base-package="com.hspedu.spring.component" />

// 测试用例

@Test
public void getBeanByAnnotation() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
UserAction userAction = ioc.getBean(UserAction.class);
System.out.println(userAction);
UserDao userDao = ioc.getBean(UserDao.class);
System.out.println(userDao);
MyComponent myComponent = ioc.getBean(MyComponent.class);
System.out.println(myComponent);
UserService userService = ioc.getBean(UserService.class);
System.out.println(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 会不会去扫描它的子包?
答:会的
3. Spring 的 IOC 容器不能检测一个使用了@Controller 注解的类到底是不是一个真正的控制器。注解的名称是用于程序员自己识别当前标识的是什么组件。其它的@Service@Repository 也是一样的道理 [也就是说 spring 的 IOC 容器只要检查到注解就会生成对象,但是这个注解的含义 spring 不会识别,注解是给程序员编程方便看的]
4. resource-pattern表示只扫描满足要求的类:[使用的少,不想扫描,不写注解就可以, 知道这个知识点即可]:

<context:component-scan base-package="com.hspedu.spring.component" resource-pattern="User*.class"/>
  1. 排除哪些类 , 以 annotaion 注解为例
<context:component-scan base-package="com.hspedu.spring.component" ><context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context>
  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. 指定自动扫描哪些注解类
1. use-default-filters="false": 不再使用默认的过滤机制
2. context:include-filter: 表示只是扫描指定的注解的类
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")指定的就是下面的id:<bean class="com.spring.dao.OrderDAO" id="orderDAO"/>

自动装配
  1. 基于注解配置 bean,也可实现自动装配,使用的注解是:@AutoWired 或者 @Resource
  2. @AutoWired 的规则说明
    1. 在 IOC 容器中查找待装配的组件的类型,如果有唯一的 bean 匹配,则使用该 bean 装配
    2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
  3. @Resource 的规则说明
    1. @Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略
    2. 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错
  4. 老韩建议,不管是@Autowired 还是 @Resource 都保证属性名是规范的写法就可以
    注入
AutoWired的自动装配
  • 配置xml , 使用注解。
<!--基于注解的配置:指定自动扫描的包--><context:component-scan base-package="com.spring.component"/>
UserAction/*** @Author: GQLiu* @DATE: 2024/1/21 11:19*/@Controller
public class UserAction {// 下面的Autowired 表示自动装配@Autowiredprivate UserService userService;public void sayok(){System.out.println("UserAction的sayok~");userService.hi();}
}

使用Autowired也可以指定 id 进行组装,

@Autowired
@Qualifier(value="指定id")
UserService @Service
public class UserService {public void hi(){System.out.println("UserService的hi~");}
}
// 测试方法// 测试注解的自动装配@Testpublic void AutowireByAnntation() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("bean3.xml");UserAction userAction = ioc.getBean(UserAction.class);userAction.sayok();}结果:
UserAction的sayok~
UserService的hi~

注意事项和细节说明

  1. 如果在IOC容器中查找待装配的组件的类型时,只有唯一的bean与之匹配,则使用该bean装配。
  2. 如待装配的类型对应的 bean 在 IOC 容器中有多个,则使用待装配的属性的属性名作为 id 值再进行查找, 找到就装配,找不到就抛异常
上面说的第二条 使用属性名作为id查询的意思是:
public class UserAction {// 下面的Autowired 表示自动装配@Autowiredprivate UserService userService; // userService就表示属性名public void sayok(){System.out.println("UserAction的sayok~");userService.hi();}
}
上面的代码中, userService就表示属性名, 然后根据这个名字去匹配xml中javabean的id。因为不指定名字在使用注解创建bean时,就是使用类名的首字母为小写作为bean的id。比如说有多个同类型的javabean,一个javabean是配置的id为userService400, 然后javabean中有一个id为userService400的javabean,那就用这个了。
Resource的自动装配
  1. @Resource 的规则说明
    1. @Resource 有两个属性是比较重要的,分是 name 和 type,Spring 将@Resource 注解的name 属性解析为 bean 的名字,而 type 属性则解析为 bean 的类型.所以如果使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略
    2. 如果@Resource 没有指定 name 和 type ,则先使用byName注入策略, 如果匹配不上,再使用 byType 策略, 如果都不成功,就会报错
@Controller
public class UserAction {//@Autowired//@Resource(name = "userService200")    // 使用 bean的id自动装配@Resource(type = UserService.class)     //按照类型实现自动装配private UserService userService;public void sayok(){System.out.println("UserAction的sayok~");userService.hi();}
}
泛型依赖注入
  1. 为了更好的管理有继承和相互依赖的 bean 的自动装配,spring 还提供基于泛型依赖的
    注入机制
  2. 在继承关系复杂情况下,泛型依赖注入就会有很大的优越性

应用实例需求
希望在PhoneService中能够调用PhoneDao。这是通过在上面一层(BaseService和BaseDao层面的依赖注入得到的。)
在这里插入图片描述

public abstract class BaseDao<T> {public abstract void save();
}public class BaseService<T> {@Autowiredprivate BaseDao<T> baseDao;public void save() {baseDao.save();}
}public class Book {
}@Repository
public class BookDao extends BaseDao<Book>{@Overridepublic void save() {System.out.println("BookDao 的 save()..");}
}@Service
public class BookService extends BaseService<Book>{//并没有写属性
}public class Phone {
}@Repository
public class PhoneDao extends BaseDao<Phone>{@Overridepublic void save() {System.out.println("PhoneDao save()");}
}@Service
public class PhoneService extends BaseService<Phone>{}

下面使用注解方式对他们进行配置:

    <context:component-scanbase-package="com.spring.depinjection"/>

下面测试泛型依赖注入

   @Testpublic void setProByDependencyInjection() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans7.xml");;PhoneService phoneService = ioc.getBean(PhoneService.class);phoneService.save();}

说明:在调用phoneService时,此时< T >里的泛型是phone,所以BaseService< T > 及其里面的属性private BaseDao< T > baseDao 都是phone类型的。所以说,他在调用save方法时,首先会去着baseDao的save方法,然后这个baseDao再根据泛型、动态绑定机制去找phoneDao的save方法。

AOP 切面编程

动态代理

● 需求说明

  1. 有 Vehicle(交通工具接口, 有一个 run 方法), 下面有两个实现类 Car 和 Ship
  2. 当运行 Car 对象 的 run 方法和 Ship 对象的 run 方法时,输入如下内容, 注意观察前后有统一的输出.
Vehicle接口:
public interface Vehicle {public void run();public String fly(int height);
}Car.java:
public class Car implements Vehicle{@Overridepublic void run() {System.out.println("小汽车在路上 running....");}@Overridepublic String fly(int height) {System.out.println("小汽车可以飞翔 高度=" + height);return "小汽车可以飞翔 高度=" + height;}
}Ship.java:
public class Ship implements Vehicle{@Overridepublic void run() {System.out.println("大轮船在水上 running....");}@Overridepublic String fly(int height) {System.out.println("轮船可以飞翔 高度=" + height);return "轮船可以飞翔 高度=" + height;}
}
// 提供代理的Java类:
VehicleProxyProvider 该类可以返回一个代理对象.import java.lang.reflect.Proxy;
public class VehicleProxyProvider {//定义一个属性//target_vehicle 表示真正要执行的对象//该对象实现了Vehicle接口private Vehicle target_vehicle;//构造器public VehicleProxyProvider(Vehicle target_vehicle) {this.target_vehicle = target_vehicle;}//编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法//老师解读//1. 这个方法非常重要, 理解有一定难度public Vehicle getProxy() {//得到类加载器ClassLoader classLoader =target_vehicle.getClass().getClassLoader();//得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();//创建InvocationHandler 对象//因为 InvocationHandler 是接口,所以我们可以通过匿名对象的方式来创建该对象/**** public interface InvocationHandler {*  public Object invoke(Object proxy, Method method, Object[] args)*         throws Throwable;* }* invoke 方法是将来执行我们的target_vehicle的方法时,会调用到**/InvocationHandler invocationHandler = new InvocationHandler() {/*** invoke 方法是将来执行我们的target_vehicle的方法时,会调用到* @param o 表示代理对象* @param method 就是通过代理对象调用方法时,的哪个方法 代理对象.run()* @param args : 表示调用 代理对象.run(xx) 传入的参数* @return 表示 代理对象.run(xx) 执行后的结果.* @throws Throwable*/@Overridepublic Object invoke(Object o, Method method, Object[] args)throws Throwable {System.out.println("交通工具开始运行了....");//这里是我们的反射基础 => OOP//method 是?: public abstract void com.hspedu.spring.proxy2.Vehicle.run()//target_vehicle 是? Ship对象//args 是null//这里通过反射+动态绑定机制,就会执行到被代理对象的方法//执行完毕就返回Object result = method.invoke(target_vehicle, args);System.out.println("交通工具停止运行了....");return result;}};/*public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)老师解读1. Proxy.newProxyInstance() 可以返回一个代理对象2. ClassLoader loader: 类的加载器.3. Class<?>[] interfaces 就是将来要代理的对象的接口信息4. InvocationHandler h 调用处理器/对象 有一个非常重要的方法invoke*/Vehicle proxy =(Vehicle)Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);return proxy;}
}
// 无注释版
public class VehicleProxyProvider {//定义一个属性//target_vehicle 表示真正要执行的对象//该对象实现了Vehicle接口private Vehicle target_vehicle;//构造器public VehicleProxyProvider(Vehicle target_vehicle) {this.target_vehicle = target_vehicle;}//编写一个方法,可以返回一个代理对象, 该代理对象可以通过反射机制调用到被代理对象的方法//老师解读//1. 这个方法非常重要, 理解有一定难度public Vehicle getProxy() {//得到类加载器ClassLoader classLoader =target_vehicle.getClass().getClassLoader();//得到要代理的对象/被执行对象 的接口信息,底层是通过接口来完成调用Class<?>[] interfaces = target_vehicle.getClass().getInterfaces();// 匿名内部类InvocationHandler invocationHandler = new InvocationHandler() {@Overridepublic 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;}
}
// 测试使用动态代理public class TestVehicle {@Testpublic void proxyRun() {//创建Ship对象Vehicle vehicle = new Car();//创建VehicleProxyProvider对象, 并且我们传入的要代理的对象VehicleProxyProvider vehicleProxyProvider =new VehicleProxyProvider(vehicle);//获取代理对象, 该对象可以代理执行方法//老师解读//1. porxy 编译类型 Vehicle//2. 运行类型 是代理类型 class com.sun.proxy.$Proxy9Vehicle proxy = vehicleProxyProvider.getProxy();System.out.println("proxy的编译类型是 Vehicle");System.out.println("proxy的运行类型是 " + proxy.getClass());//下面老韩就要给大家解读/debug怎么 执行到 代理对象的 public Object invoke(Object o, Method method, Object[] args)//梳理完毕. proxy的编译类型是 Vehicle, 运行类型是 class com.sun.proxy.$Proxy9//所以当执行run方法时,会执行到 代理对象的invoke//如何体现动态 [1. 被代理的对象 2. 方法]proxy.run();String result = proxy.fly(10000);System.out.println("result=" + result);}

执行结果:
在这里插入图片描述
在这里插入图片描述

执行流程:

调用proxy.run()时,会直接进入 new InvocationHandler这个匿名内部类中. 然后依次执行. 执行到Object result = method.invoke(target_vehicle, args); 时, 会跑到这个target_vehicle对象中执行对应的方法.

在这里插入图片描述

Spring-AOP介绍

  • 底层是 ASPECTJ
  • AOP 的全称(aspect oriented programming) ,面向切面编程

在这里插入图片描述
在这里插入图片描述

切面类就是切面编程,这个类中的方法可以被其他类的任意方法在任意时间调用,灵活度很高,相当于在一个类的某个方法执行过程中某个切面时刻调用方法。

AOP的思路:单独写一个类,然后里面写很多静态方法,然后在别的类里调用这个类里的静态方法的过程就是切面编程

● AOP 实现方式

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

AOP 快速入门

● 说明
  1. 需要引入核心的 aspect 包
  2. 在切面类中声明通知方法
    1. 前置通知:@Before
    2. 返回通知:@AfterReturning
    3. 异常通知:@AfterThrowing
    4. 后置通知:@After
    5. 环绕通知:@Around

我们使用 aop 编程的方式,来实现手写的动态代理案例效果,就以上一个案例为例来讲解
在这里插入图片描述

说明

在这里插入图片描述
SmartAnimalAspect 是一个切面类, 所有的之前的切面方法放到这里去统一管理
SmartAnimalImplement是一个实例方法 运行的类,在这个类运行前,执行切面类中的某个切面方法(这里的f1)。

SmartAnimal 接口:
/*** @Author: GQLiu* @DATE: 2024/1/22 21:14*/
public interface SmartAnimal {public int getSum(int a,int b);public int getSub(int a,int b);
}
切面类:/*** @Author: GQLiu* @DATE: 2024/1/23 20:42* SmartAnimalAspect 作 用 就 是 去 接 管 切 面 编 程 , 此 时 原 来 的* MyProxyProvider 类就可以拿掉了.*/
@Aspect // 表示是一个切面类[底层切面编程的支撑(动态代理+反射+动态绑定)]
@Component  // 将切面类注入SmartAnimalAspect到容器
public class SmartAnimalAspect {// 是写切面方法.// 在方法执行前执行的切面方法// 希望将f1方法切入到SmartAnimalImplement-getSum前执行/*** @Before: 表示前置通知.即在我们的目标对象执行方法前执行.* value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int ,int))"*   指定切入到哪个类的哪个方法 形式是 访问修饰符 返回类型 全类名.方法名(形参列表)*   f1方法可以理解成一个切入方法,这个方法名可以是程序员指定 showBeginLog* @param joinPoint : 在底层执行时, 由AspectJ切面框架会给该切入方法传入JoinPoint连接点对象.*                   通过该参数, 可以获取到相关信息.*/@Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")public void showBeginLog(JoinPoint joinPoint) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));}//返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。@AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")public void showSuccessEndLog(JoinPoint joinPoint) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));}// 异常通知: 将该方法切入到目标方法执行发生异常的catch{}@AfterThrowing(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")public void showExceptionLog(JoinPoint joinPoint) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showExceptionLog()-方法执行出现异常-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));}// 最终通知:将f4方法切入到目标方法执行后(不管是否发生异常都执行)@After(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")public void showFinallyEnd(JoinPoint joinPoint) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showFinallyEnd()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));}
}
实例类,在这个类的某个方法(这里是getSum, getSub)执行前(),执行某个切面方法(如切面类SmartAnimalAspect 里的切面方法f1)
/*** @Author: GQLiu* @DATE: 2024/1/22 21:15*/
@Component  // 使用@Component 当Spring容器启动时, 将SmartDog注入容器中
public class SmartAnimalImplement implements SmartAnimal {@Overridepublic int getSum(int a, int b) {
//        System.out.println("日志-方法名-getSum" + "参数 =" + a + " " + b);return a + b;}@Overridepublic int getSub(int a, int b) {
//        System.out.println("日志-方法名-getSum" + "参数 =" + a + " " + b);return a - b;}
}

细节说明

  1. 关于切面类方法命名可以自己规范一下, 比如 showBeginLog() . showSuccessEndLog()
    showExceptionLog(), showFinallyEndLog()
  2. 切入表达式的更多配置,比如使用模糊配置
    @Before(value=“execution(* com.hspedu.aop.proxy.SmartDog.*(…))”)
  3. 表示所有访问权限,所有包的下所有有类的所方法,都会被执行该前置通知方法
    @Before(value=“execution(* .(…))”)
  4. 当 spring 容器开启了 < !-- 开启基于注解的 AOP 功能 -- > <aop:aspectj-autoproxy/> , 我们获
    取注入的对象, 需要以接口的类型来获取, 因为你注入的对象.getClass() 已经是代理类型
    了!
  5. 当 spring 容器开启了 < !-- 开启基于注解的 AOP 功能 --> <aop:aspectj-autoproxy/> , 我们获
    取注入的对象, 也可以通过 id 来获取, 但是也要转成接口类型.

课后作业
在这里插入图片描述

配置文件不用动<!--扫描com.spring.aop.aspectj这个包查找Spring组件--><context:component-scanbase-package="com.spring.aop.aspectj"/><!--开启基于注解的AOP功能--><aop:aspectj-autoproxy/>
切面类中加上这些:@Before(value = "execution(public void com.spring.aop.aspectj.Phone.work()) || execution(public void com.spring.aop.aspectj.Camera.work())")public void showBeginLog_Phone(JoinPoint joinPoint) {Signature signature = joinPoint.getSignature();System.out.println("切面类shoBeginLog_Phone/Camera-方法执行前-日志-方法名" + signature.getName());}
phone和camera注意要加上Component@Component
public class Phone implements UsbInterface{@Overridepublic void work() {System.out.println("手机Phone执行work。。。");}
}@Component
public class Camera implements UsbInterface{@Overridepublic void work() {System.out.println("相机Camera执行工作。。。");}
}
测试代码@Testpublic void UsbInterfaceTest() {ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans8.xml");String[] beanNames = ioc.getBeanDefinitionNames();for (String beanName : beanNames) {Object bean = ioc.getBean(beanName);System.out.println(beanName + " : " + bean.getClass().toString());}//  UsbInterface camera = (UsbInterface) ioc.getBean(Camera.class);     // 傻叉! 这是代理类型的, 通过Camera类型拿肯定拿不到!// 这时可以通过类名首字母小写, 即通过类名的方式拿.UsbInterface camera = (UsbInterface) ioc.getBean("camera");camera.work();System.out.println("====================");}

AOP-切入表达式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意事项和细节

  1. 切入表达式也可以指向类的方法, 这时切入表达式会对该类/对象生效
  2. 切入表达式也可以指向接口的方法, 这时切入表达式会对实现了接口的类/对象生效
  3. 切入表达式也可以对没有实现接口的类,进行切入(就是对普通的类的某个执行方法也可以进行前后的切入。)

动态代理 jdk 的 Proxy 与 Spring 的 CGlib的区别

AOP-JoinPoint

● 通过 JoinPoint 可以获取到调用方法的签名
● 应用实例需求
说明: 在调用前置通知获取到调用方法的签名, 和其它相关信息

// 常用方法public void beforeMethod(JoinPoint joinPoint){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-返回通知获取结果

在这里插入图片描述

// 修改切面类的方法://返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。@AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",returning = "res")public void showSuccessEndLog(JoinPoint joinPoint, Object res) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));System.out.println("返回通知的结果为" + res);  // 返回通知的结果为12}

AOP-异常通知中获取异常

如何在异常通知方法中获取异常信息?

// 修改后的切面类// 异常通知: 将该方法切入到目标方法执行发生异常的catch{}@AfterThrowing(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",throwing = "throwable")public void showExceptionLog(JoinPoint joinPoint, Throwable throwable) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showExceptionLog()-方法执行出现异常-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));System.out.println("异常通知 -- 异常信息--" + throwable);}

在这里插入图片描述

AOP-环绕通知【了解】

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

如何使用环绕通知完成其它四个通知的功能。

    // 环绕通知@Around(value = "execution(public int getSum(int, int))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {Object result = null;String methodName = joinPoint.getSignature().getName();try {// 1. 相当于完成前置通知@Before完成的事Object[] args = joinPoint.getArgs();List<Object> list = Arrays.asList(args);System.out.println("Aop环绕通知--" + methodName + " 方法开始了, 参数有" + list);// 在环绕通知中一定要调用joinPoint.proceed()来执行目标方法result = joinPoint.proceed();// 2. 相当于返回通知@AfterReturning完成的事情System.out.println("Aop环绕通知" + methodName + "方法结束了--结果是" + result);} catch (Throwable e) {// 3. 相当于异常通知完成的事儿System.out.println("Aop环绕通知" + methodName + "方法抛出异常了--异常对象" + e);} finally {// 4. 相当于最终通知完成的事情System.out.println("Aop后置通知" + methodName + "方法最终结束了。");}return result;/*执行结果:Aop环绕通知--getSum 方法开始了, 参数有[10, 2]12方法内部打印res=12Aop环绕通知getSum方法结束了--结果是12Aop后置通知getSum方法最终结束了。12*/}
// 测试类@Testpublic void smartAnimalImplementTest() {// 得到的spring容器ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("beans8.xml");// 这里需要通过接口类型获取到注入的SmartDog对象SmartAnimal smartAnimal = ioc.getBean(SmartAnimal.class);int sub = smartAnimal.getSum(10, 2);System.out.println(sub);System.out.println("SmartAnmial的运行类型=" + smartAnimal.getClass());   // class com.sun.proxy.$Proxy13}

AOP-切入点表达式重用

对某个切入表达式,如果很多都用到了某个切入表达式,则就用这个切入表达式重用@PointCut()来实现重用这个切入表达式。

@AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",returning = "res")@Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")上面这两个切入表达式的value值都是一样的,都是一个类中的getSum方法。所以这里使用切入表达式重用技术来实现重用。
    // 切入表达式重用@Pointcut(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")public void myPointCut() {}// @Before(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))")//使用切入表达式重用@Before(value = "myPointCut()")public void showBeginLog(JoinPoint joinPoint) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showBeginLog()-方法执行前-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));}//返回通知 :即把f2方法切入到目标独享方法正常执行后再执行。// @AfterReturning(value = "execution(public int com.spring.aop.aspectj.SmartAnimalImplement.getSum(int,int))",//                 returning = "res")@AfterReturning(value = "myPointCut()", returning = "res")public void showSuccessEndLog(JoinPoint joinPoint, Object res) {// 通过连接点对象joinPoint 可以获取方法签名Signature signature = joinPoint.getSignature();System.out.println("切面类showSuccessEndLog()-方法执行后-日志-方法名-" + signature.getName() + " " + Arrays.asList(joinPoint.getArgs()));System.out.println("返回通知的结果为" + res);  // 返回通知的结果为12}

AOP-切面优先级问题

● 切面优先级问题:
如果同一个方法,有多个切面类的不同切面方法在同一个切入点切入,那么执行的优先级如何控制.
● 基本语法:
@order(value=n) 来控制 n 值越小,优先级越高.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面使用SmartAnimalAspect和SmartAnimalAspect3切面类去做,然后根据这两个切面类的优先级去处理先后顺序问题。

注意事项和细节说明

  1. 不能理解成:优先级高的每个消息通知都先执行,这个和方法调用机制(和 Filter 过滤器链式调用类似)
    在这里插入图片描述
  2. 如何理解执行顺序
    在这里插入图片描述

AOP-基于 XML 配置 AOP

● 基本说明:
前面我们是通过注解来配置 aop 的,在 spring 中,我们也可以通过 xml 的方式来配置 AOP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/663151.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity DOTween插件常用方法(二)

文章目录 1.3 动画设置1.4 动画队列 Sequence1.5 动画回调函数1.6 等待函数&#xff08;协程中使用&#xff09; 1.3 动画设置 SetLoops 设置循环动画&#xff1b; 参数&#xff1a; loops&#xff1a;指定循环的次数&#xff0c;设置为 -1 表示无限循环&#xff1b; loopType…

洛谷p1644跳马问题

跳马问题 题目背景 在爱与愁的故事第一弹第三章出来前先练练四道基本的回溯/搜索题吧…… 题目描述 中国象棋半张棋盘如图 1 1 1 所示。马自左下角 ( 0 , 0 ) (0,0) (0,0) 向右上角 ( m , n ) (m,n) (m,n) 跳。规定只能往右跳&#xff0c;不准往左跳。比如图 1 1 1 中所…

Windows篇|连接共享文件夹映射驱动器教程

前言 昨天给小伙伴们分享了如何设置Windows共享文件夹的教程 超简单设置Windows共享文件夹,传输文件无烦恼 A电脑设置完共享文件夹之后,只要在同一局域网下,所有的电子设备都可以连接访问A电脑的共享文件夹里的内容。 这样就省去了U盘传输的麻烦,只要A电脑开着机,文件放入…

基于SpringBoot+Vue的校园资料分享平台(V2.0)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

【GitHub项目推荐--ChatGPT开源项目】【转载】

Auto-GPT Auto-GPT 是一个实验性的开源项目&#xff0c;基于 GPT-4。你给出 Auto-GPT 一个的任务&#xff0c;它不会立即输出答案&#xff0c;而会先自己通过多轮对话来琢磨、验证、决策&#xff0c;从而自己找出一条达成目标的路&#xff0c;整个过程完全不需要人类插手&…

【C++游戏开发-01】推箱子

C游戏开发 文章目录 C游戏开发[TOC](文章目录) 前言一、逻辑分析1.1地图实现1.2人物的移动1.2.1小人移动1.2.2其他移动 1.3墙壁的碰撞1.4箱子的推动1.4.1什么时候推箱子1.4.2什么情况可以推箱子 1.5胜利的判断1.6卡关的处理1.7关卡的切换 二、DEMO代码2.1游戏框架2.2各功能函数…

Python实战:使用DrissionPage库爬取高考网大学信息

上一篇文章&#xff0c;我刚入门 DrissionPage 爬虫库&#xff0c;使用这个库爬取了拉钩网关于 Python 的职位信息。 今天再使用 DrissionPage 爬虫库练习一个案例&#xff0c;爬取高考网大学信息。 本次爬取到2885个大学信息&#xff0c;包含大学名称、所在省、市、大学标签信…

Galah:一款功能强大的LLM驱动型OpenAI Web蜜罐系统

关于Galah Galah是一款功能强大的Web蜜罐&#xff0c;该工具由LLM大语言模型驱动&#xff0c;基于OpenAI API实现其功能。 很多传统的蜜罐系统会模拟一种包含了大量网络应用程序的网络系统&#xff0c;但这种方法非常繁琐&#xff0c;而且有其固有的局限性。Galah则不同&…

【算法详解 | 二分查找】详解二分查找 \ 折半查找高效搜索算法 | 顺序数组最快搜索算法 | 递归循环解决二分查找问题

二分查找 by.Qin3Yu 本文需要读者掌握 顺序表 的操作基础&#xff0c;完整代码将在文章末尾展示。 顺序表相关操作可以参考我的往期博文&#xff1a; 【C数据结构 | 顺序表速通】使用顺序表完成简单的成绩管理系统.by.Qin3Yu 文中所有代码使用 C 举例&#xff0c;且默认已使用…

聊一聊Tomcat的架构和运行流程,尽量通俗易懂一点

1、Tomcat的架构 这里可以看出 A、一个Tomcat就是一个Server&#xff0c;一个Server下会有多个Service&#xff0c; B、Service只负责封装多个Connector和一个Container&#xff08;Service本身不是容器&#xff0c;可以看做只是用来包装Connector和Container的壳&#xff0c…

基于Springboot的社区疫情防控平台

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 一、项目简介 以往的社区疫情防控管理…

交强险投保日期查询接口返回字段说明

API接口是现代互联网应用中重要的组成部分&#xff0c;通过接口的调用可以实现不同系统之间的数据交互和共享。在汽车保险行业中&#xff0c;交强险投保日期查询接口是非常关键的一个接口&#xff0c;本文将详细介绍该接口的返回字段和使用方法。 接口名称&#xff1a;交强险投…

C++入坑基础知识点

当学习了C语言之后&#xff0c;很多的小伙伴都想进一步学习C&#xff0c;但两者有相当一部分的内容都是重叠的&#xff0c;不知道该从哪些方面开始入门C&#xff0c;这篇文章罗列了从C到C必学的入门知识&#xff0c;学完就算是踏入C的大门了。 1. 命名空间 写C的时候&#xff…

找不到d3dcompiler_43.dll,无法继续执行代码的原因分析与解决方法

在运行某些软件或游戏时&#xff0c;可能会遇到系统提示找不到 d3dcompiler_43.dll 文件的情况。这个特定的动态链接库文件 (dll) 是 DirectX 3D 编译器组件的一部分&#xff0c;对于许多现代软件游戏的正常运行起着不可或缺的作用。它的主要功能在于将高级着色语言编写的代码转…

零基础学Python之核心基础知识

1.Python入门简介 &#xff08;1&#xff09;什么是Python Life is short, you need Python&#xff01;人生苦短&#xff0c;我用Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&#xff0c;相比其他语言…

MySQL数据库入门(概念+使用)

目录 1. 数据库的概念 1.1 数据库的存储介质 1.2 主流数据库 2. MySQL的基本使用 2.1 链接数据库 2.2 服务器管理 2.3 数据库&#xff0c;服务器和表关系 2.4 简单MySQL语句 3. MySQL架构 4. SQL分类 5. 存储引擎 本篇完。 1. 数据库的概念 数据库是按照数据结构来…

【CSS】页面自适应屏幕宽度(响应式布局媒体查询-@media、弹性布局、网格布局和相对单位-vh/em/%)

【CSS】页面自适应屏幕宽度&#xff08;响应式布局媒体查询-media、弹性布局、网格布局和相对单位-vh/em/%&#xff09; 一、媒体查询&#xff08;media&#xff09;1、媒体类型2、媒体特征3、媒体查询语法4、示例&#xff08;1&#xff09;示例1&#xff08;2&#xff09;示例…

leetcode热题100.二叉树中的最大路径和

Problem: 124. 二叉树中的最大路径和 文章目录 题目解题方法复杂度Code 题目 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 …

pytorch_car_caring 排坑记录

pytorch_car_caring 排坑记录 任务踩坑回顾简单环境问题代码版本问题症状描述解决方法 cuda问题&#xff08;异步问题&#xff09;症状描述解决方法 任务 因为之前那个MPC代码跑出来的效果不理想&#xff0c;看了一天代码&#xff0c;大概看明白了&#xff0c;但要做改进还要有…

C语言指针学习 之 指针是什么

前言 指针是C语言中一个重要概念&#xff0c;也是C语言的一个重要特色&#xff0c;正确而灵活地运用指针可以使程序简洁、紧凑、高效。每一个学习和使用C语言的人都应当深入的学习和掌握指针&#xff0c;也可以说不掌握指针就没有掌握C语言的精华。 一、什么是指针 想弄清楚什…