栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【狂神说】Spring5笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【狂神说】Spring5笔记

Spring5

Spring中文文档Spring framework 中文文档 - Spring framework 5.1.3.RELEASE Reference | Docs4dev

Spring官方文档Core Technologies (spring.io)
如果笔记有错误欢迎提出!!!!!!!

文章目录
  • Spring5
    • 简介
      • Spring的组成
      • Spring的扩展
    • IOC理论推导
    • HelloSpring
    • IOC注册对象的方式
    • Spring配置说明
    • 依赖注入
      • set注入搭建环境
      • 例子
      • 拓展注入方式
    • bean的作用域
    • bean的自动装配
    • 使用注解实现自动装配
    • 使用注解开发
      • 属性注入
      • 作用域
    • JavaConfig代替配置文件
    • 静态代理模式
      • **静态代理总结:**
    • 动态代理模式
      • 深化理解(模板)
    • AOP
      • 什么是AOP
      • AOP在Spring中的作用
      • 通过 Spring API 实现
      • 自定义类来实现
      • 使用注解实现
    • 整合Mybatis
      • Mybatis回顾
      • Mybatis-Spring
      • 另一种实现方法
    • 声明式事务
      • 回顾事务
      • Spring中的事务管理

简介
  • Spring:春天—> 给软件行业带来了春天

  • 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

  • 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

  • 开发者Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。。。

  • Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

名词解释:

SSH : Struct2 + Spring + Hibernate! (老式

SSM : SpringMvc + Spring + Mybatis!

三个地址:

Spring官方地址:Spring | Home

Spring下载地址(当然我们用不到)repo.spring.io

Spring的github地址Spring (github.com)

Maven的依赖:(不知道的看前面JavaWeb视频)


    org.springframework
    spring-webmvc
    5.3.10



    org.springframework
    spring-jdbc
    5.3.10


    junit
    junit
    4.12
    test

优点?

  • Spring是一个轻量级、 非侵入式的开源免费的框架 (容器) (即可以插入工程而不对原有文件运行产生干扰
  • 控制反转 IoC , 面向切面 Aop (重点)
  • 对事物的支持 , 对框架的支持

总结:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的框架(容器)。

Spring的组成

讲的不清楚。建议百度。/

(7条消息) Spring模块组成_FYHannnnnn的博客-CSDN博客_spring组成

Spring的扩展

原页面已经找不到了,这个页面已经改变了)

Spring Boot

  • 一个快速开发的脚手架

  • 基于SpringBoot可以快速开发单个微服务

  • 约定大于配置!

  • Spring Cloud
    Spring CLoud是基于SpringBoot实现的

  • 因为现在大所述公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用

弊端:发展太久了之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”!

IOC理论推导

控制反转,将程序的主动权交给用户,程序根据用户输入的值自动(被动)选择相应代码去执行。这是Spring框架的核心内容。

这里的用户指的是那些买这个项目的人吧,他们不懂代码,因此让他们直接在配置文件中修改相应的值就可以达到代码实现不同效果。

IOC的两种策略:(来自百度百科

  1. 依赖查找:容器提供回调接口和上下文条件给组件。EJB和Apache Avalon 都使用这种方式。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上(也就是上面所说的 类型1):容器将调用这些回调方法,从而让应用代码获得相关资源。
  2. 依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)

憋了很久,终于弄懂什么是IOC(控制反转) - 戎"码"一生 - 博客园 (cnblogs.com)

IoC原理 - 廖雪峰的官方网站 (liaoxuefeng.com)

IOC是一种编程思想,由主动编程变成了被动接收。在本视频中,狂神通过set方式作为例子。而后面实际使用其实就是使用修改xml配置文件。

利用IOC,我们不用再去程序中根据用户的要求进行改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象是由Spring创建,管理,装配!

在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起使用的其他对象) 。然后,容器在创建 bean 时注入那些依赖项。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化或位置的 bean 本身的逆过程(因此称为 Control Inversion)。

HelloSpring

Spring 提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。

在大多数应用场景中,不需要实例化用户代码即可实例化一个 Spring IoC 容器的一个或多个实例。

如上图所示,Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化,配置和组装应用程序中的对象。

传统上,配置元数据以简单直观的 XML 格式提供,这是本章大部分内容用来传达 Spring IoC 容器的关键概念和功能的内容。

编写HelloSpring:

新建一个项目Spring_study .检查一下这里的Maven是否是自己的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RxwgoxTW-1636453833297)(Spring5.assets/image-20211005102204929.png)]

然后删掉src将该工程作为父工程,在pom.xml文件中导入上面提到的依赖。

然后在项目中新建一个子工程,Spring-01-hello。

在java中创建包com.song.pojo 在里面创建hello.java类

public class Hello { 
    private String str;
    public String getStr() {//alt+insert快速生成  
        return str;
    }
    public void setStr(String str) {
        this.str = str;
    }
    @Override
    public String toString() {//ctrl+o 快速生成
        return getStr();
    }
}

在resources文件夹中创建一个beans.xml文件

这个文件的模板建议自己在最开始提到的官方文档(中文文档也能找到



    
    
        
    

在test文件夹中的java文件创建一个测试类 test.java

public static void main(String[] args) { //psvm + 回车快速生成
    //获取Spring的上下文对象,这里是已经写死了,不能够再改变了
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //我们的对象已经在Spring中了,我们只需要取出来就可以了
    Hello hello = (Hello) context.getBean("hello");
    System.out.println(hello.toString());
}

输出结果:This is HelloSpring..

运行完之后发现实体类Hello的左侧边出现了绿色的叶子,当出现绿色的叶子说明已经被Spring所托管了。如果没有就点击xml文件上方的黄色提示信息,它会自动帮助改正/

接下来狂神将上个视频中讲解IOC理论时所使用的关于用户自己选择数据库使用的例子用Spring实现:

在java的com.song.pojo包里创建UserDao接口以及UserDaoMysqlImpl类和UserDaoOracleImpl类。

public interface UserDao {
void getUser();
}
public class UserDaoMysqlImpl implements UserDao{
    public void getUser(){
        System.out.println("通过mysql得到数据!");
    }
}

public class UserDaoOracleImpl implements UserDao {
    public void getUser(){
        System.out.println("通过Oracle得到数据!");
    }
}

在java的com.song.service包里创建UserService接口以及UserServiceImpl类。

public interface UserService {
    void getUser();
}
public class UserServiceImpl implements  UserService{
    private  UserDao userdao;
    public void setUserDao(com.song.pojo.UserDao userDao) {
        userdao = userDao;
    }
    public void getUser() {
        userdao.getUser();
    }
}

beans.xml文件




    


测试类test.java

public static void main(String[] args) { //psvm + 回车快速生成
    //可以通过输入 CPX+回车 快速补全 注意补全后的数据类型要自己改为 ApplicationContext
    //当运行这句话时,beans.xml里的 所有类 都已经被构造出来了,就存放在容器中!!!
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
	//在Spring容器(context)中直接取出来
    UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userserviceImpl");
    userServiceImpl.getUser();
}

输出结果:通过mysql得到数据!

我当时有疑惑,为什么要强转为UserServiceImpl,UserService不行吗?我试了一下,也行。。。然后在网上搜到了下面的文章,先贴在这里//

(8条消息) Spring框架getBean()方法返回对象为什么只能转成接口对象,转换成接口的实例会报错?_你好,我们在哪里见过啊!-CSDN博客

IOC注册对象的方式

这节课讲的就是关于上面的bean.xml文件中java类注册时采用何种方式。

  1. 默认通过无参构造函数,如果java类只有有参构造函数会报错。
  2. 如果想通过有参函数来进行构造,有三种方法

在pojo包里创建User类:

public class User {
    private String name;
    public User(String name) {//有参构造函数
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+ name );
    }
}
  • 下标赋值

很简单的就可以理解

index指构造方法的第几个参数 初始以0开始

 
    
 
  • 参数类型创建

这里不能偷懒使用String哦,必须写完整,不然爆红

 
    
 

另外试了一下两个String参数时,他会按照bean里面的顺序挨个给两个String参数赋值。

虽然可以成功赋值,但是感觉不太好用的感觉///


    
    

  • 参数名创建

这里的参数名就是有参构造函数的括号里的参数


    

看到弹幕在讨论是否能通过context中一个class对象只有一个对应的实例得出Spring默认使用的是单例模式,然后去网上搜了一下。

单例bean与原型bean的区别:

如果一个bean被声明为单例的时候,在处理多次请求的时候在spring容器里只实例化出一个bean,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。

但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。

Spring为什么默认是单例模式?

为了提高性能,少创建实例,垃圾回收,缓存快速获取

单例bean的优势?

  • 减少新生成实例的消耗
  • 减少jvm垃圾回收
  • 单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快

单例bean的劣势?

单例的bean一个很大的劣势就是他不能做到线程安全,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。

转自:面试题:Spring为什么默认bean为单例? - 简书 (jianshu.com)

知识有点多看不懂的一个大佬的博客:

(8条消息) Spring中Bean的单例、多例_longzhutengyue的博客-CSDN博客

Spring配置说明
  • 别名:

在beans.xml文件中加入这句代码,然后就会在注册对象的时候可以通过xml中的别名来当作真名字使用。


  • Bean的配置

id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean 对象所对应的全限定名:包名+类型
name:也是别名,而且同时name可以同时取多个别名,很随意 逗号,分号,空格都可以起到分割不同别名的作用。


    

  • import导入

这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个(都在同一个resources文件目录下

假设,现在项目中有多个人开发,这三个人负责不同类的开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!使用的时候直接使用总配置就可以了。



依赖注入

(9条消息) Spring的五种依赖注入方式_shadow_zed的博客-CSDN博客_spring 注入

  • 构造器注入

上面IOC创建对象已经讲过了

  • Set方式注入【重点】
set注入搭建环境

在一个新的子工程里创建实体类,记得生成对应的get和set以及tostring方法

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List hobbies;
    private Map card;
    private Set games;
    private Properties info;
}
public class Address {
    private String address;
    public String getAddress() {
    return address;
}
//记得使用alt+insert来快捷生成所需要的getset和tosting

然后beans.xml 中进行student类的注册(先对name进行赋值


    

编写测试类( test/java/test.java )

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getName());
    }
}

测试成功就说明本测试环境完成

例子

beans.xml文件补全:


    


    
    
    
    
    
    
        
            《红楼梦》
            《三国演义》
            《西游记》
            《水浒传》
        
    
    
    
        
            games
            code
        
    
    
    
        
            
            
        
    
    
    
        
            L
            C
        
    
    
    
        
            song
            
            20190106
        
    

然后运行测试类 调用toSting()方法,可以做到全部输出。

拓展注入方式

p命名空间注入:使用这个注入方式需要在约束条件中加入一条约束代码

然后注册时可以比较方便…代码比较简短(emmm)

xmlns:p="http://www.springframework.org/schema/p"


    

c命名空间注入:使用这个注入方式也需要在约束条件中加入一条约束代码…(和p的约束代码差不多

通过构造器注入,所以我们必须在实体类中加入有参及无参构造

xmlns:c="http://www.springframework.org/schema/c"
public class Address {
    private String address;

    public Address() {
    }
    public Address(String address) {
        this.address = address;
    }
}


    
bean的作用域

浅析Spring中bean的作用域|spring|websocket|session|xml_网易订阅 (163.com)

singleton(单例):在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype(多例):每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application:限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

websocket:Scopes a single bean definition to the lifecycle将单个bean定义作用于WebSocket的生命周期。在Web感知的SpringApplicationContext上下文中有效。

  • 单例模式

这里所说的单例,和设计模式中所提到的单例模式不同。设计模式中的单例,是强制一个类有且只有一个对象,我们如果不通过特殊的手段,将无法为这个单例类创建多个对象。而 Spring 中的单例作用域不同,这里的单例指的是在一个 Spring 容器中,只会缓存 bean 的唯一对象,所有通过容器获取这个 bean 的方式,最终拿到的都是同一个对象。但是在不同的 Spring 容器中,每一个 Spring 容器都可以拥有单例 bean 的一个实例对象,也就是说,这里的单例限定在一个 Spring 容器中,而不是整个应用程序。并且我们依然可以通过 new 的方式去自己创建 bean 。

  • 原型模式

每次从容器中get都会产生一个新对象

关于这两个模式的选择使用scope属性来确定,Spring默认为单例模式

 
 

再然后就是两种模式下都同时get一个同样的实例类,单例模式get到的是同一个对象。

  • request、session、application这些都是只能在web开发中才能使用到的
bean的自动装配

当Bean的属性很少的时候,我们对它进行配置的时候就使用很少的或者元素进行装配,但是随着工程体积的增大,Bean也可能变得复杂,这时候配置文件也会变得复杂,和 就会变得很多,写起来就会很费劲,这个时候利用自动装配就方便很多

自动装备就是自动寻找容器中的类

例如人拥有猫和狗,人可以让猫叫。

首先测试咱不使用自动装配,使用手动装配的效果。

public class Dog {
    public void shout(){
        System.out.println("wang!");
    }
}
public class Cat {
    public void shout(){
        System.out.println("miao!");
    }
}
public class People {
    private Cat cat;
    private Dog dog;//省略get,set
}

测试类test:

public static void main(String[] args) {
    ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml");
    People people = (People) Context.getBean("people");
    people.getCat().shout();
    people.getDog().shout();
}

beans.xml 手动装配:

在下面的代码中我们使用property 手动装配了人所拥有的cat类和dog类




    
    

beans.xml自动装配:

  • 在下面的代码中我们使用 <… autowire=“byName”> 自动装配人所拥有的cat类和dog类

在本例子中就是在people类中找到setDog(Dog dog)方法然后得到括号里面的参数dog,在beans.xml中寻找到id=dog的bean,然后自动匹配。

要求找到的bean_id必须为dog,多一个字母也不行。(




  • 在下面的代码中我们使用 <… autowire=“byType”> 自动装配人所拥有的cat类和dog类

在本例子中,就是在beans.xml中寻找和自己对象属性类型相同的 bean

这种方法不要求bean_id 必须为dog了,但是只适用于存在一种dog的情况。你只能养一条狗了。。。




使用注解实现自动装配

继续使用上面的子工程。

使用注解需要在beans.xml中导入comtext约束,增加注解支持。(一共在原beans.xml的基础上加了四条语句。



	
    


  • @Autowired

这个注解是属于Spring的,测试的时候配置文件:




使用Autowired我们就可以不用编写set方法了,People类中使用@Autowired注解

public class People {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   //节省篇幅,省略了get方法,测试的时候自己加上
}

运行测试test可以发现,尽管没有对bean进行装配,但是运行成功。

自己更改配置文件后,知道首先按照byType进行自动装配,如果装配失败则进行byName自动装配。

扩展:

@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。

在启动Spring后,注入容器的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,

所以我们有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)。

  • @Autowired+@Qualifier

Spring 注解 @Qualifier 详细解析 - 知乎 (zhihu.com)

@Autowired加上@Qualifier 直接确定了会根据byName的方式自动装配。@Qualifier不能单独使用。

测试的时候,修改beans.xml文件:






然后进行测试,发现报错

再修改People类 value值匹配beans.xml里的id。(如果没有匹配的值,就会直接爆红,运行都运行不了

public class People {
    @Autowired
    @Qualifier(value = "cat2")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    //节省篇幅,省略了get方法,测试的时候自己加上
}

然后再进行测试,成功运行。

  • @Resource

这个注解属于j2EE,JSR-250标准注解,推荐使用它来代替Spring专有的@Autowired注解

但是!!!我的jdk11没有这个注解!!!!

@Resource装配顺序:(–来源网上

(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常

(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配。

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。

使用注解开发 属性注入

在Spring4之后,要使用注解开发,必须要保证aop的包导入了。(依赖使导入Spring-webmvc就直接全部导入了

(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。
(2)< context:component-scan base-package=“XX.XX”/> :除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。

< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!

创建一个子工程,然后将applicationContext.xml (就是原来的beans.xml)基础的配置一下。



    
    
    

在com.song.pojo包下创建User.java类

//@Component 组件
//加上后等价于  
//注意首字母小写
@Component
public class User {
    //等价于  
    //也可以加在set方法上面,两者同时存在时,优先使用set方法上面的
    @Value("szg")
    public String name;
}

写一个测试方法

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean("user");//注意这里时小写user
    System.out.println(user.name);
}

运行成功。

@Component 注解的衍生

@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

这几个衍生注解在效果都是一样的,都是代表将某个类注册到Spring中,装配Bean。

当然,需要都在配置文件的context:component-scan指定范围内

  • dao层 【@Repository】
  • service层 【@Service】
  • controller层 【@Controller】
作用域

通过scope注解中value来定义作用域,详解可以看bean的作用域

将该@Scope("singleton") 加在Component下面就行。

加上注解就不用再去配置文件中配置作用域了

@Component
@Scope("singleton")
public class User {
    @Value("szg")
    public String name;
    public void setName(String name) {
        this.name = name;
    }
}

xml更加万能,适用于任何场合维护简单。

注解更加方便,但是维护起来很麻烦

两者比较合理的分配:

  1. xml用来管理bean;
  2. 注解只负责完成属性的注入;
  3. 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的扫描支持
JavaConfig代替配置文件

JavaConfig是Spring的一个子项目。它基于Java代码和Annotation注解来描述Bean之间的依赖绑定关系。

使用JavaConfig就可以实现不需要beans配置文件,就可以将对象注册到容器中。

  • 创建一个新的子工程,然后创建com.song.pojo包

@Component 与后面的@ComponentScan 相配套使用、两者一起出现

这个注解也会注册一个Bean,id属性为实体类的小写

@Component
public class User {
    public String name;
    @Value("szg")
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                '}';
    }
}
  • 创建com.song.config包,写了一个Song_Config类

@Configuration将该文件标记为配置类(代替xml配置文件

@Bean作为Spring的XML配置文件中的

这个方法的名字就相当于bean中的id属性

这个方法的返回值,就相当于bean标签中的class属性

(ps:可以使用@Bean("BeanName") 来手动指定Bean的名字)

@ComponentScan("com.song.pojo")相当于之前配置文件的

@import 相当于之前的 xml配置文件里的 标签

@Configuration
@ComponentScan("com.song.pojo")
@import("Song_Config2.class")
public class Song_Config {
    @Bean
    public User getUser(){
        return new User();
    }
}
  1. @Bean 注解默认作用域为单例singleton 作用域,可通过@Scope(”prototype“)设置为原型作用域
  2. 既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
  3. 但是扫描之后,会出现两个Bean对象,一个是getUser一个是user。一个是bean创建的,一个是Component创建的。
  • 创建一个测试类:

这里因为我们用的是java配置类去做,所以通过AnnotationConfigApplicationContext类进行解析并注册到Bean的注册表,通过java配置类的class对象加载。

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(Song_Config.class);
    User user1 = (User) context.getBean("getUser");
    User user2 = (User) context.getBean("user");
    System.out.println(user1);
    System.out.println(user2);
    System.out.println(user1==user2);//两个不一样的user对象
    System.out.println(user1.hashCode());
    System.out.println(user2.hashCode());
}
静态代理模式

要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

例子:房东想要出租房子,但是没时间,因此扔给了中介,中介需要帮助房东实现出租房子的任务。

在这里中介就是静态代理了房东来做出租房子这件事情。

租房子的接口:

public interface Rent {
    public void rent();
}

房东:

public class Host implements Rent{
    public void rent() {
        System.out.println("房东出租房子!");
    }
}

中介:

public class Proxy implements Rent{
    private Host host;
    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }
    public void rent() {//代理房东实现出租
         System.out.println("中介发放出租房源");
        host.rent();
        //可以在函数里面加一些附加的内容
         System.out.println("房东记得给中介费!");
    }
}

房子被出租:

public class Test {
    public static void main(String[] args) {
        Host host = new Host();
        //代理,中介帮房东出租房子,并且代理角色也可以加上一些附有的操作(比如签合同、收费等)!
        Proxy proxy = new Proxy(host);
        //不用面对房东,直接找中介租房即可!
        proxy.rent();
    }
}

再例如可以在上面的代理类中对于每种操作都进行输出(printf)来达到了不通过修改房东的源代码而实现了输出关于房子出租过程的作用。

静态代理总结:

1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

  • 每次我要新加入一个实现接口的对象的话,都要重新创建一个代理对象,效率太低
  • 同时,一旦接口增加方法,目标对象与代理对象都要维护.
动态代理模式

建议先看前面的注解和反射的视频再继续向下看。

动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们手动写好的
动态代理分为俩大类:基于接口的动态代理,基于类的动态代理

  • ​ 基于接口—-JDK动态代理
  • ​ 基于类:cglib
  • ​ 现在用的比较多:java字节码实现:javassist
  • ​ 我们使用JDK的原生代码来实现,其余的道理都是一样的!

首先我们需要了解俩个类:Proxy:代理,InvocationHandler:调用处理程序



(13条消息) java中getClass()方法简介_expect521的博客-CSDN博客_getclass

(13条消息) 动态代理模式newProxyInstance及invoke方法参数详解_mRambo的博客-CSDN博客

主要是理解 InvocationHandler newProxyInstance .

//生成代理类
public Object getProxy(){
    return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                                  rent.getClass().getInterfaces(),this);
}

关于代码:

租房子的接口 和 房东对象 都是和前面一样

动态代理类:

public class AutoProxy implements InvocationHandler {
    private  Rent rent;
    public void setRent(Rent rent){
        this.rent=rent;
    }
    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    //格式几乎是死的 注意狂神写的时候第一个参数写的this.getClass().getClassLoader()
    public Object getProxy(){
        //三个参数 :代理类的类加载器   代理类的接口列表   调度方法调用的调用处理程序(就是自己
         return  Proxy.newProxyInstance(rent.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }
    // 处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //核心:本质利用反射实现!
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    public void fare(){
        System.out.println("记得给中介费!");
    }  
}
public class Client {
    //一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
    //由代码动态的生成 接口相应的 代理类
    public static void main(String[] args) {
        //真实角色 对应接口的实现类
        Host host = new Host();
        //动态代理 :代理实例的调用处理程序
        AutoProxy ap = new AutoProxy();
        //多态  因为host实现了rent的接口
        ap.setRent(host); //将真实角色放置进去
        // 动态生成对应的代理类! 记得强转一下
        Rent proxy = (Rent)ap.getProxy();
        proxy.rent();
    }
}
深化理解(模板)

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}


//真实对象
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }

    public void query() {
        System.out.println("查询了一个用户");
    }
}

//模板  直接可以拿过来用!
public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){ //JDK 动态代理的局限性,只支持接口
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    // proxy : 生成的代理类
    // method : 代理类的调用处理程序的方法对象.
    // 被代理对象 target 调用其自身方法时都会执行 invoke
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());//返回调用的函数方法的名称
        Object result = method.invoke(target, args);
        return result;
    }
    public void log(String methodName){
        System.out.println("执行了"+methodName+"方法");
    }
}
public class Test {
    public static void main(String[] args) {
        //每次只需要更改真实对象  而不用每次都要写一个静态代理的代理类
        UserServiceImpl userService = new UserServiceImpl();
        //代理对象的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService); //设置要代理的对象
        UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
        proxy.delete();
        proxy.query();
    }
}

动态代理的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可【核心】
AOP 什么是AOP

代理模式核心是AOP思想:(改人源代码相当于刨祖坟(–来自弹幕

因此当需要对源代码的业务实现进行修改时,需要面向切片编程

给源代码套个娃,即再写一个套娃的代理类,从而对源代码的业务进行修改.

AOP 实现机制 - 简书 (jianshu.com)

  • AOP(Aspect Oriented Programming):可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。

  • AOP是c(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP在Spring中的作用

提供声明式事务;允许用户自定义切面

以下名词需要了解下: (狂神的理解-)

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

  • 目标(Target):被通知对象。

  • 代理(Proxy):向目标对象应用通知之后创建的对象。

  • 切入点(PointCut):切面通知 执行的 “地点”的定义。

  • 连接点(JointPoint):与切入点匹配的执行点。


理解几种增强方式!

Spring中的5种Aop常见应用方式 - 知乎 (zhihu.com)

通过 Spring API 实现
  • 首先创建一个新的子工程 然后导入AOP的依赖(不知道为啥不导入id为spring-aop的包)

    org.aspectj
    aspectjweaver
    1.9.7

  • 在java文件夹中创建com.song.service包,在包里创建接口和实现类
public interface UserService {
    public void add();
    public void delete();
    public void update();
}
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加用户");
    }
    @Override
    public void delete() {
        System.out.println("删除用户");
    }
    @Override
    public void update() {
        System.out.println("更新用户");
    }
}
  • 然后去写我们的增强类:创建com.song.log包 , 我们编写两个java文件 , 一个前置增强 一个后置增强
public class BeforeLog implements MethodBeforeAdvice {
    //method : 要执行的目标对象的方法
    //args : 被调用的方法的参数
    //target : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println( target.getClass().getName() + "的" + method.getName() + "方法被执行");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法,"+"返回值为:"+returnValue);
    }
}
  • 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

创建Spring基本配置文件然后编辑

    
    
    

     
    
    
    




        

        
        
    

创建Test测试文件

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//        写接口
        UserService userservice = (UserService) context.getBean("userservice");
        userservice.add();
    }
}

结果:

com.song.service.UserServiceImpl的add方法被执行
增加用户
执行了com.song.service.UserServiceImpl的add方法,返回值为:null

因此在没有改变UserService的代码的基础上,增加了日志!

自定义类来实现
  • 创建一个com.song.config的包然后写我们自己的一个切入类。
public class DiyPointcut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 去spring中配置
    
    
    
    
        
        
            
            
            
        
    

然后测试即可

使用注解实现
  • 在com.song.config的包中,我们写一个自己的一个注解切入
//如果没有Aspect的就是导入依赖生效的范围不对,去掉pom.xml文件里的scope标签然后刷新Maven就行了。
//定义切面类
@Aspect
public class AnnotationPointCut {
    //如果导入了junit依赖会有junit包下的Before注解  要看清楚 不要导错了
    @Before("execution (* com.song.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("====方法执行前====");
    }
    @After("execution(* com.song.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("----方法执行后------");
    }
    @Around("execution(* com.song.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("环绕前");
        //签名:返回参数的类型 和 类的执行了的方法名称
        System.out.println("获得签名:"+jp.getSignature());
        System.out.println("环绕后");
    }
}
  • Spring配置文件



然后测试即可

结果:

环绕前
获得签名:void com.song.service.UserService.add()
====方法执行前====
增加用户
----方法执行后------
环绕后
null

在环绕增强中,可以执行业务方法,而在前置增强和后置增强中则不可以;这里可以通过环绕增强实现数据库事务的实现,也可以通过环绕增强实现程序运行时间的记录;****

  • 关于

通过aop命名空间的声明自动为spring容器中那些配置**@aspectJ切面的bean创建代理,织入切面**。

当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被隐藏起来了

有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

整合Mybatis

Spring-Mybatis中文文档:mybatis-spring –

Mybatis回顾

创建一个新的子工程在子工程的pom.xml导入依赖:(父工程的依赖有spring-webmvc spring-jdbc 和junit 三种依赖


    
    
        org.mybatis
        mybatis
        3.5.7
    
    
    
        mysql
        mysql-connector-java
        8.0.26
    
    
        org.aspectj
        aspectjweaver
        1.9.7
    
    
    
        org.mybatis
        mybatis-spring
        2.0.6
    
    
    
        org.springframework
        spring-jdbc
        5.3.12
    

    
    
        org.projectlombok
        lombok
        1.18.22
        provided
    
    
        org.junit.jupiter
        junit-jupiter-api
        5.8.1
        compile
    


然后紧接着在配置文件后面加上:


        
            
                src/main/java
                
                    ***.xml
                
                true
            
        
    

然后刷新Maven!!!!

然后连接数据库,(如果找不到database的话)点击idea右上角的搜索然后搜索出来。。。。。。(先去看Mybatis课程

  • 创建com.song.pojo包 来编写一个实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}
  • 在resources资源文件中实现mybatis-config.xml配置文件



    
        
            
            
                
                
                
                
            
        
    
    

        

    

  • 创建com.song.mapper 进行UserMapper接口编写
public interface UserMapper {
    List getUserList();
}
  • 对接口UserMapper在同一个包下 编写UserMapper.xml配置文件