Spring版权声明

原创
小哥 3年前 (2022-10-27) 阅读数 50 #大杂烩

TOC]

前言

为了更好地深入研究Spring核心技术,在这里组织Spring相关的核心常识、面试知识点,本文将通过通俗易懂的语言和代码来实现,相信能在最短的时间内巩固学习。

本文中的引用:

亲身体验使用

  • spring :Core提供依赖项注入
  • spring mvc : mvc框架
  • spring boot :我没感觉到什么,跟我走mvc它的配置比它感觉的要少得多。
  • spring cloud :围绕微服务的一系列事情。

from 2018/7/27

1.基本概念

  1. JavaBean

JavaBean是一种组件技术 ,就好像你做了一个扳手,这个扳手会用在很多地方,这个扳手还提供了多种功能(你可以用这个扳手、锤子、撬棍等),而这个扳手是一个部件。

JavaBean是一种遵循一种特定的写作方法。Java类 ,它通常具有以下特点:

  • 这个Java类必须具有非参数构造函数。
  • 属性必须私有化。
  • 私有化的属性必须通过public类型的方法向其他程序公开,方法的命名也必须遵循某些命名约定。
  • 此类应该是可序列化的。(例如,可以实现Serializable 接口,用于实现bean坚持不懈)

许多开发人员将JavaBean被认为遵守特定的命名惯例的。POJO。 简而言之,当一个POJO可序列化,具有非参数构造函数,使用getter和setter方法访问该属性时,他是一个JavaBean。

package gacl.javabean.study;

/**

  • @author gacl
  • Person类是最简单的JavaBean */ public class Person {

    //Person类封装的私有属性 // 姓名 String类型 private String name; // 性别 String类型 private String sex; // 年龄 int类型 private int age;

    /**

    • 无参数施工法 */ public Person() {

    }

    //Person外部提供的用于访问私有属性的类。public方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }

JavaBean在J2EE在开发中,它通常用于封装数据。JavaBean组件,其他程序可以通过反射技术实例化。JavaBean对象,并通过反映那些符合命名约定的方法,您可以了解。JavaBean属性,该属性进而调用其属性来保存数据。

  1. Bean

  • Bean“豆子”的中文意思,Bean意义是可重复使用的Java组件 。组件是一组可以自己在内部管理的一个或多个类,外部世界不知道它们的内部信息以及它们是如何操作的。使用它的对象只能通过接口操作。
  • Bean不需要继承特殊的基类。(BaseClass)或实现特定的接口(Interface)。Bean写作说明书写的是。Bean的容器(Container)能够分析Java类文件及其方法。(Methods)转换为属性(Properties),即把Java类作为Bean类使用。Bean写作规范包括。Bean类构造方法、定义属性和访问方法编写规则。
  • Java Bean是基于Java的组件模型 属性、方法和事件 3构图的一部分。在这个模型中,JavaBean可以修改或与其他组件组合以生成新的组件或完整的程序。这是一个Java类,它被封装为具有特定功能或处理特定业务的对象。因此,它还可以嵌入到JSP页面内的Java代码访问Bean以及它的属性。
  1. 传统Javabean与Spring中的bean的区别

Javabean现在没人再用它了。

springbean可以说是javabean的发展, 但这是完全不同的

不同用途:传统javabean更多地将参数作为值传递,而spring中的bean几乎在任何地方都很有用,任何组件都可以被称为bean。

不同的写作方式:传统javabean作为值对象,每个属性都需要提供getter和setter方法;但spring中的bean只需提供接受设置值注入的属性。setter方法。

不同生命周期:传统javabean作为值对象传递,不接受任何容器来管理其生命周期;spring中的bean有spring管理他们生命周期行为。

一切都可以spring实例化和管理容器。java类可以被调用bean。

原来,服务器处理页返回的值是直接使用的。request对象,后来又添加了javabean要管理对象,所有页面值只要是和即可。javabean函电,你可以用这个类。.GET属性方法来获取该值。javabean不仅可以传递参数,还可以处理数据,这非常类似于将服务器执行的类放在页面上,使对象管理相对不那么混乱(相比之下asp(当页面上的所有内容都完成时)。

spring中的bean,是通过配置文件,javaconfig等。设置,有以下几种spring使用后自动实例化和销毁的对象。让我们只在使用对象时使用它,而不管我们是否创建一个类对象(即spring注射)。它通常用于执行服务器端代码。

  1. POJO

POJO 和JavaBean是我们的两个常见关键词,它们通常令人困惑,POJO全称是Plain Ordinary Java Object / Pure Old Java Object,中文可以翻译成:普通Java类, 有一个角色getter/setter可以调用该方法的类型POJO ,但是JavaBean则比 POJO更复杂的是, Java Bean 是一个可重复使用的组件, Java Bean 从理论上讲,没有严格的规范。 Java 类可以是一个 Bean 。但通常,由于 Java Bean 由容器创建( Tomcat) 的,所以 Java Bean 应具有非参数构造函数,此外,通常 Java Bean 还要实现 Serializable 该接口用于实现 Bean 坚持不懈。 Java Bean 不能跨进程访问

一般在web在为应用程序中的数据库构建映射对象时,我们只能调用它。POJO。 POJO(Plain Old Java Object)这个名字是用来强调它是一个普通的java对象,而不是特殊对象。 2005年11月时,“POJO主要用来指那些用来不遵守特定规定的人Java对象模型、约定或框架,如EJB的Java对象. 理想情况下,一个POJO是一个不受限制的Java对象(Java语言规范)。例如,一个POJO不应该是

  1. 1. 扩展预定的类,如 public class Foo extends javax.servlet.http.HttpServlet { ...

  2. 2. 实现预定的接口,如 public class Bar implements javax.ejb.EntityBean { ...

  3. 3. 包含预定的标签,如 @javax.ejb.Entity public class Baz{ ...

然后,由于技术上的困难和其他原因,许多兼容POJO样式软件产品或框架实际上仍然需要使用预定的标签,例如为了更方便地持久化。

二、Spring核心技术

  1. IOC(控制权的逆转)

1.1 什么是IOC

IoC(Inversion of Control),意思是控制反转,不是一种技术,而是一种设计理念。Ioc意味着 将您设计的对象交给容器控件,而不是对象内的传统直接控件。

如何更好地理解Ioc怎么了?充分理解Ioc关键是要搞清楚“谁控制谁、什么,为什么是逆转(应该有逆转),哪些方面是逆转的”,然后我们来深入分析:

  • 谁控制谁、什么 :传统Java SE程序设计,我们通过直接进入对象内部new为了创建对象,程序主动创建依赖对象;IoC有没有一个特殊的容器来创建这些对象,即。Ioc控制配对的容器 大象的创造;谁控制谁?当然了IoC 容器控制对象吗?也就是说,外部资源获取的主要控制(不仅仅是包括例如文件等的对象)。
  • 为什么是反转,哪些方面是反转的。 :如果有倒置,就会有正向旋转。传统的应用程序是由我们自己在对象中主动控制,直接获得依赖对象,即正向旋转。反转是帮助创建和注入依赖对象的容器。为什么会颠倒过来呢?因为容器帮助我们查找和注入依赖对象,所以对象只被动地接受依赖对象,所以它们是相反的。哪些方面是颠倒的?从属对象的获取被反转。

简单来说

积极的转变:例如,有一个类在类中有方法(不是静态方法),调用类中的方法,在类中创建对象,使用对象调用方法,并创建类对象。new出来对象

反转:对象的创建未完成。new实现的方式,但要给予Spring配置为创建类对象

【必读】IOC深刻理解,请转身: 深入浅出IOC

1.2 IoC能做什么

IoC 它不是一种技术,它只是一种想法,是面向对象编程的一条重要规则,指导我们如何设计松散耦合的更好的程序。传统的应用程序是由我们在类内主动创建的,导致类之间耦合程度高,测试困难;IoC在容器之后,创建和查找依赖对象的控制权交给容器,容器注入组合的对象,因此对象和对象是松散耦合的,这也方便了测试,有利于功能重用,更重要的是使程序的整个架构非常灵活。

其实IoC编程最大的变化不是从代码,而是从思想上,从师傅到换位的变化。该应用程序原本是BOSS,获取任何资源都是活跃的,但在。IoC/DI在思想上,申请变得被动,被动等待。IoC容器来创建和注入它所需的资源。

IoC它很好地体现了面向对象设计的一条规则-- 好莱坞定律:“不要找我们,我们找你”;也就是,IoC容器帮助对象找到相应的依赖对象并注入它,而不是对象主动寻找它。

1.3 IoC和DI

DI—Dependency Injection,即“依赖注入” 组件之间的依赖关系由容器在运行期间确定,即容器动态地向组件注入依赖关系。依赖注入的目的不是为了给软件系统带来更多的功能,而是为了提高组件重用的频率,为系统构建一个灵活的、可扩展的平台。有了依赖注入机制,我们可以通过指定目标所需的资源来完成我们的业务逻辑,而不需要编写任何代码,而不必关心特定资源来自哪里以及由谁实现它们。

理解DI关键是:“ 谁靠谁,为什么要靠谁,谁注射谁,注射什么 那我们就来深入分析一下:

  • 谁取决于谁: 当然,应用程序依赖于IoC容器;
  • 您需要依赖的原因: 应用程序需求IoC容器提供对象所需的外部资源;
  • 谁给谁注射: 很明显是IoC容器注入应用程序中的对象,该应用程序依赖于该对象;
  • 注射了什么: 它是注入对象所需的外部资源(包括对象、资源、常量数据)。

IoC和DI这是什么关系?事实上,它们是从同一个概念的不同角度来描述的。由于控制反转的概念模糊(可能只被理解为一个级别的容器控制对象,很难想到由谁来维护对象关系),所以2004硕士学位Martin Fowler还给出了一个新的名称:“依赖注入”,相对的IoC 而言, “依赖注入” 明确描述 “注入的对象依赖项IoC容器配置相关对象“

对于Spring Ioc这个核心理念,我相信每一次学习Spring人们会有自己的理解。这种概念性认识没有绝对的标准答案,不同的人有不同的看法。 理解了IoC和DI有了概念之后,一切都会变得简单明了。剩下的工作就是在框架中堆积积木。让我们在下一节中看看它。Spring它是如何使用的

1.4 IOC底层原理 (降低类之间的耦合程度)

  • 其基本原则是使用技术。
    • xml配置文件
    • dom4j解决xml
    • 工厂设计模式
    • 反射
  • 原理

//伪代码 //需要实例化的类 public class UserService{ }

public class UserServlet{ //得到UserService的对象 //最初的做法是:new 对象(); 来创建

//经过spring后
UserFactory.getService();   //(下面的两步代码调用)

}

第1步:创建xml配置文件中,配置要创建的对象类。

步骤2:创建工厂类,使用。dom4j解析配置文件+反射

public class Factory { //返回UserService对象的方法。 public static UserService getService() { //1.使用dom4j来解析xml文件
//根据id值userService,得到id值对应的class属性值 String classValue = "class属性值"; //2.使用反射创建类对象。 Class clazz = Class.forName(classValue); //创建类的对象 UserService service = clazz.newInstance(); return service; } }

超详细的原理说明: java-bible/4.principle.md at master · biezhi/java-bible

转存失败 重新上传 取消

1.5 Spring中怎么用

(1)剖面法

我们在Spring以下是获取该对象的方法:

public static void main(String[] args) {
ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");
Lol lol = (Lol) context.getBean("lol");
lol.gank(); }

一起看看Spring如何让它变得有效,在。 applicationContext.xml 配置文件为酱油紫色:

Lol 类是这样的:

public class Lol {

private String name;

public Lol() {
}

public void gank(){
    System.out.println(this.name + "在gank!!");
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

}

上面的代码自然运行 剑圣在gank!!

(2)注解方法

Spring更高级的用法,在。3.0在该版本之后,基于Annotation每次都会为头发配置注入实现。 Xml 看到所有的鸡蛋痛..

首先,它仍然是必要的 xml 配置在中启用批注的方式

这允许您使用注释驱动的依赖注入,下面是一个使用场景。

public class Lol {

@Autowired
private DuangService duangService ;

public void buyDuang(String name, int money) {
    duangService.buy(name, money);
}

}

@Service("duangService") public class DuangService {

public void buy(String name, int money){
    if(money > 0){
        System.out.println(name + "买了" + money + "一角的特效,假装成功!");
    } else{
        System.out.println(name + "没有钱,却想假装是被迫的,这很奇怪。");
    }
}

}

这只是一个简单的例子。刀锋大师想在他狂野的时候买下它。5一角硬币的三杀效应,嗯..虽然不合乎逻辑

此时 DuangService 已经被注射到 Lol 在该对象中,运行代码(这里是一个示例,代码不能运行)的结果是:

德玛买了5一角的特效,假装成功!

  1. DI(依赖项注入)

2.1 什么是依赖项注入

在依赖注入模式中,创建被调用者的工作不再由调用者完成,而创建被调用者实例的工作通常完成。Spring容器完成,然后注入调用者。 创建对象时,将该值设置为类中的属性。

2.2 为什么要使用依赖项注入

为了实现代码/模块之间松散耦合。

2.3 为什么要实现松耦合

上层调用下层,上层依赖下层。当下层变化很大时,上层也会发生变化,这会导致模块复用性的降低,大大增加开发成本。

一般来说,抽象改变的概率很小,所以用户程序依赖于抽象,而实现的细节也取决于抽象。即使实现细节不断变化,只要抽象保持不变,客户端程序就不需要更改。这极大地减少了客户端程序和实现细节之间的耦合。

2.4 IOC和DI区别

  1. IOC控制反转,提供对象创建Spring配置
  2. DI依赖项注入,向类中的属性注入值
  3. 关系,依赖项注入不能单独存在,需要IOC在此基础上完成作业

2.5 一种依赖项注入方法

  1. 使用set方法注入

  2. 使用参数化构件注入

  3. 使用接口注入

说明:Spring框架支持前两种方法。

(1)使用set方法注入

1234567 (2)使用参数构造注入 public class Person { private String pname; public void setPname(String pname) { this.pname = pname; } } (3)注入对象类型属性 * 创建service和dao类,在service中得到dao 具体实施流程 * 在service中把dao作为属性,生成dao的set方法 public class UserService { // 1.定义UserDao类型属性 private UserDao userDao; // 2.生成set方法 public void setUserDao(UserDao userDao) { this.userDao = userDao; } } 1. 调驱关系 12345678 (4)p命名空间注入 ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/ioc-p1.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/ioc-p1.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/ioc-p1.png) ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/ioc-p2.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/ioc-p2.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/ioc-p2.png) (5)注入复杂类型属性 aaa bbb ccc qqq www eee admin admin 3. AOP(面部切面编程) --------------- ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/spring-aop.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/spring-aop.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/spring-aop.png) ### 3.1 什么是AOP AOP(Aspect Oriented Programming )称为面对面编程,扩展功能不是修改源代码实现。它主要用于解决程序开发中的一些系统级问题,如日志、事务、权限等待、Struts2拦截器的设计基于AOP这个想法是一个更经典的例子。 * AOP:面向对象编程,扩展功能不修改源代码实现 * AOP采取 **水平抽出机构** ,取代传统的 **纵向继承** 系统可重复性代码(性能监控、事务管理、安全检查、缓存) **spring底层通过两种方式进行增强。** 1. 第一:Spring传统AOP 纯java实现,在运行期间,目标对象被代理,编织到增强代码中。 3. 第二:AspectJ第三方开源技术,Spring已经整合AspectJ,提供对AspectJ批注支持、开发AOP程序 更轻松(企业主流) ### 3.2 底层原理 ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/aop2.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/aop2.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/aop2.png) 第一种 JDK 自含式动态代理技术 JDK动态代理必须基于接口 角色:使用代理监视目标对象的性能(计算运行时)、安全检查(它们是否有权限)、 原木等 注意:Proxy必须有接口,并且必须将Proxy对象转换为接口类型。 第二种 CGLIB(CodeGenerationLibrary)是一个开放源码项目 Spring使用CGlib 进行AOP代理, hibernate 也是支持CGlib(默认使用 javassist )需要下载cglib 的jar包(Spring 最新版本3.2 已在内部集成cglib , **无需下载cglib的jar** ) **角色:您可以为目标类动态创建子类并代理目标类方法(不需要接口)** 原理:Spring AOP 在底层,它将确定用户是基于接口代理还是基于目标类代理,以及它是否用于接口代理。JDK如果使用目标类代理,则返回代理。Cglib代理。 ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/aop1.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/aop1.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/aop1.png) ### 3.3 AOP操作术语 以下面的表面代码为例: public class User { public void add() {...} public void delete() {...} public void update() {...} public void query() {...} } * **Joinpoint(连接点)(重要)** * 可以在类中增强的方法称为连接点。 * **Pointcut(入口点)(重要)** * 所谓的切入点,就是我们想要的Joinpoint截取的定义 * **Advice(通知/增强)(重要)** * 所谓通知,是指截获Joinpoint在那之后,你所要做的就是通知.通知分为事前通知、事后通知、例外通知、最终通知和环绕通知(功能在该部分完成) * **Aspect(切面)** : * 是入口点和通知(推荐)的组合 * **Introduction(引介)** * 引用是不修改类代码的特殊通知, Introduction您可以动态添加一些方法或Field. * **Target(目标对象)** * 代理的目标对象(需要增强的类) * **Weaving(织入)** * 它是向目标应用增强功能的过程。advice 应用到 target的过程 * **Proxy(代理)** * 一个类被AOP在编织增强之后,将生成一个结果代理类。 ### 3.4 Spring的AOP操作 * 在Spring里面进行Aop操作,使用aspectj实现 * aspectj不是Spring的一部分和Spring一起使用Aop操作 * Spring2.0后来,又增加了一对新的。aspectj的支持 * 使用aspectj实现aop有两种方法 * 基于aspectj的xml配置 * 基于aspectj注解方法 (1)AOP准备操作 1,除了导入基本jar除了包,您还需要导入它。aop相关的jar包: 1. aopalliance-1.0.jar 2. aspectjweaver-1.8.7.jar 3. spring-aspects-5.0.4.RELEASE.jar 4. spring-aop-5.0.4.RELEASE.jar 2、创建Spring岩心轮廓 除了引入约束之外spring-beans此外,还需要引入新的约束条件。spring-aop 12345678 (2使用表达式配置入口点。 1. 切入点:一种实用的增强方法 2. 常见表达方式 execution(<访问修饰符>? <返回类型> <方法名>(<参数>)<异常>) (1)到包裹的内部。add方法,以增强 execution(* cn.blinkit.aop.Book.add(..)) (2)* 是对类里面的所有方法,以增强 execution(* cn.blinkit.aop.Book.*(..)) (3) _._ 是所有的类中的方法,以增强 execution(* *.*(..)) (4)全部匹配save开始的方法 execution(* save*(..)) ### 3.5 使用xml实现AOP **aop配置代码:** Book public class Book { public void add() { System.out.println("add......"); } } MyBook public class MyBook { public void before1() { System.out.println("前置增强......"); } public void after1() { System.out.println("后置增强......"); } //环绕通知 public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //方法之前 System.out.println("方法之前....."); //执行增强的方法 proceedingJoinPoint.proceed(); //方法之后 System.out.println("方法之后......"); } } xml配置 测试代码 public class AOPTest { @Test public void testBook() { ApplicationContext context = new ClassPathXmlApplicationContext("cn/blinkit/aop/spring-aop.xml"); Book book = (Book) context.getBean("book"); book.add(); } } ### 3.6 使用注释来实现AOP 1. 创建对象 (1)创建Book和MyBook **(强化课程)** 对象 2. 在spring岩心轮廓中,开启aop操作 具体操作请参见xml配置文件代码: 3. 使用关于增强类的注释来完成。aop操作 (1)班级以上加分 @Aspect (2)以上方法加 @Before(value = "execution(* cn.blinkit.aop.anno.Book.*(..))") @After(value = "表达式") @Around(value = "表达式") 等... **Book** public class Book { public void add() { System.out.println("add...注解版本..."); } } **MyBook增强类** @Aspect public class MyBook { //使用注释完成方法上方的增强配置。 @Before(value = "execution(* cn.blinkit.aop.anno.Book.*(..))") public void before1() { System.out.println("前置增强...注解版本..."); } @After(value = "execution(* cn.blinkit.aop.anno.Book.*(..))") public void after1() { System.out.println("后置增强...注解版本..."); } //环绕通知 @Around(value = "execution(* cn.blinkit.aop.anno.Book.*(..))") public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //方法之前 System.out.println("方法之前...注解版本..."); //执行增强的方法 proceedingJoinPoint.proceed(); //方法之后 System.out.println("方法之后...注解版本..."); } } **xml配置** ### 3.7 为什么需要代理模式? 假设您需要实现一个计算类。Math完成加法、减法、乘法、除法功能如下: package com.zhangguo.Spring041.aop01; public class Math { //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } } 现在需求发生了变化,要求项目中的所有类在执行方法时输出执行时间。最直接的方法是修改源代码,如下所示: package com.zhangguo.Spring041.aop01; import java.util.Random; public class Math { //加 public int add(int n1,int n2){ //开始时间 long start=System.currentTimeMillis(); lazy(); int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //减 public int sub(int n1,int n2){ //开始时间 long start=System.currentTimeMillis(); lazy(); int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //乘 public int mut(int n1,int n2){ //开始时间 long start=System.currentTimeMillis(); lazy(); int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //除 public int div(int n1,int n2){ //开始时间 long start=System.currentTimeMillis(); lazy(); int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //模拟延时 public void lazy() { try { int n=(int)new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } } 缺点: 1. 工作量特别大。如果项目中有多个类和多个方法,则会多次修改。 2. 与设计原则背道而驰:开合原则(OCP)、开放扩展、封闭修改、修改每种添加功能的方法,不容易维护。 3. 违反设计原则:单一责任(SRP),每个方法除了完成各自的功能外,还需要计算时间消耗和延迟。每种方法导致它改变的原因有很多。 4. 违反设计原则:依赖反转(DIP),抽象不能靠细节,两者都要靠抽象。在.中Test类中,Test与Math都是细节。 解决: * 使用静态代理来解决一些问题(向下看)。...) ### 3.8 静态代理 1,定义抽象主题界面 package com.zhangguo.Spring041.aop02; /** * 接口 * 抽象主题 */ public interface IMath { //加 int add(int n1, int n2); //减 int sub(int n1, int n2); //乘 int mut(int n1, int n2); //除 int div(int n1, int n2); } 2、主题类、算术类、抽象接口的实现 package com.zhangguo.Spring041.aop02; /** * 代理的目标对象。 *真实主题 */ public class Math implements IMath { //加 public int add(int n1,int n2){ int result=n1+n2; System.out.println(n1+"+"+n2+"="+result); return result; } //减 public int sub(int n1,int n2){ int result=n1-n2; System.out.println(n1+"-"+n2+"="+result); return result; } //乘 public int mut(int n1,int n2){ int result=n1*n2; System.out.println(n1+"X"+n2+"="+result); return result; } //除 public int div(int n1,int n2){ int result=n1/n2; System.out.println(n1+"/"+n2+"="+result); return result; } } 3、代理类 package com.zhangguo.Spring041.aop02; import java.util.Random; /** * 静态代理类 */ public class MathProxy implements IMath { //被代理的对象 IMath math=new Math(); //加 public int add(int n1, int n2) { //开始时间 long start=System.currentTimeMillis(); lazy(); int result=math.add(n1, n2); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //减法 public int sub(int n1, int n2) { //开始时间 long start=System.currentTimeMillis(); lazy(); int result=math.sub(n1, n2); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //乘 public int mut(int n1, int n2) { //开始时间 long start=System.currentTimeMillis(); lazy(); int result=math.mut(n1, n2); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //除 public int div(int n1, int n2) { //开始时间 long start=System.currentTimeMillis(); lazy(); int result=math.div(n1, n2); Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //模拟延时 public void lazy() { try { int n=(int)new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } } 4、试运行 package com.zhangguo.Spring041.aop02; public class Test { IMath math=new MathProxy(); @org.junit.Test public void test01() { int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } } 5、小结 通过静态代理,无论上述4个问题: **已解决:** * 解决了“开合原则(OCP)“问题,因为没有修改。Math类,同时扩展MathProxy类。 * 解决了“对反转的依赖(DIP)“问题,通过引入接口。 * 解决了“单一责任(SRP)“问题,Math类不再需要计算耗时和延迟的操作,但在某些方面MathProxy问题仍然存在。 **未解决:** * 如果项目中有多个类,则需要编写多个代理类,工作量大,修改困难,维护困难,无法应对变化。 如果要解决上述问题,可以使用动态代理。 ### 3.9 动态代理,使用JDK内置的Proxy实现 只需要一个代理类,而不是每个类。 修改上一个示例中的代理类。MathProxy如下: package com.zhangguo.Spring041.aop03; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Random; /** * 动态代理类 */ public class DynamicProxy implements InvocationHandler { //被代理的对象 Object targetObject; /** * 表示后获取对象。 * @param object 被代理的对象 * @return 代理后的对象 */ public Object getProxyObject(Object object){ this.targetObject=object; return Proxy.newProxyInstance( targetObject.getClass().getClassLoader(), //类加载器 targetObject.getClass().getInterfaces(), //获取代理对象的所有接口。 this); //InvocationHandler对象 //loader:一个ClassLoader对象,该对象定义哪些ClassLoader对象以生成要加载的代理对象。 //interfaces:一个Interface对象数组表示我将为需要代理的对象提供的接口集。如果我为它提供了一组接口,那么代理对象就会声明实现该接口。(多态),这样我就可以调用这组接口中的方法。 //h:一个InvocationHandler对象,该对象指示当我的动态代理对象调用该方法时将关联哪个对象。InvocationHandler对象,间接通过invoke来执行 } /** * 当用户调用对象中的每个方法时,它由以下方法执行,该方法必须位于接口中。 * proxy 被代理后的对象 * method 要执行的方法信息(反射) * args 执行该方法所需的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //编织内容,开始时间 long start=System.currentTimeMillis(); lazy(); //使用反射调用目标对象上的方法并传入参数。 Object result=method.invoke(targetObject, args); //编织的内容,结束的时间 Long span= System.currentTimeMillis()-start; System.out.println("共用时:"+span); return result; } //模拟延时 public void lazy() { try { int n=(int)new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } } 试运行: package com.zhangguo.Spring041.aop03; public class Test { //实例化MathProxy代理对象 //通过getProxyObject方法表示后获取对象。 IMath math=(IMath)new DynamicProxy().getProxyObject(new Math()); @org.junit.Test public void test01() { int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } IMessage message=(IMessage) new DynamicProxy().getProxyObject(new Message()); @org.junit.Test public void test02() { message.message(); } } 小结: JDK内置的Proxy动态代理可以在运行时动态生成字节码,而无需为每个类编写代理类。一个接口主要用在中间。InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下: 使用内置的Proxy实施动态代理有一个问题: **被代理的类必须实现接口,而动态代理不实现接口就无法完成。** 如果项目中的某些类没有实现接口,则不应该故意提取一些没有实例意义的接口来实现动态代理。cglib才能解决这个问题。 ### 3.10 动态代理,使用cglib实现 CGLIB(Code Generation Library)是一个开放源码项目,是一款功能强大、高性能、高品质的Code生成可在运行期间扩展的类库。Java类与实现Java界面,用外行人的话说cglib您可以在运行时动态生成字节码。 1、引用cglib,通过maven 2、使用cglib要完成动态代理,一般原则是:cglib继承代理类、覆盖方法、编织通知、动态生成字节码并运行它,因为它是继承的。final没有办法动态代理类。具体实现如下: package com.zhangguo.Spring041.aop04; import java.lang.reflect.Method; import java.util.Random; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /* * 动态代理类 * 实现了方法拦截器接口。 */ public class DynamicProxy implements MethodInterceptor { // 代理对象 Object targetObject; //Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance. //Uses the no-arg constructor of the superclass. //动态生成一个新类,该类使用父类的非参数构造方法创建指定特定回调的代理实例。 public Object getProxyObject(Object object) { this.targetObject = object; //动态代码生成器增强器 Enhancer enhancer=new Enhancer(); //回调方法 enhancer.setCallback(this); //设置生成类的父类类型。 enhancer.setSuperclass(targetObject.getClass()); //动态生成字节码并返回代理对象。 return enhancer.create(); } // 拦截方法 public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 编织横切内容,开始时间 before long start = System.currentTimeMillis(); lazy(); // 调用方法 Object result = methodProxy.invoke(targetObject, args); // 编织的横切内容,结束的时间 Long span = System.currentTimeMillis() - start; System.out.println("共用时:" + span); return result; } // 模拟延时 public void lazy() { try { int n = (int) new Random().nextInt(500); Thread.sleep(n); } catch (InterruptedException e) { e.printStackTrace(); } } } 试运行: package com.zhangguo.Spring041.aop04; public class Test { //实例化DynamicProxy代理对象 //通过getProxyObject方法表示后获取对象。 Math math=(Math)new DynamicProxy().getProxyObject(new Math()); @org.junit.Test public void test01() { int n1=100,n2=5; math.add(n1, n2); math.sub(n1, n2); math.mut(n1, n2); math.div(n1, n2); } //表示的另一个对象。,不再需要再次编辑代理代码。 Message message=(Message) new DynamicProxy().getProxyObject(new Message()); @org.junit.Test public void test02() { message.message(); } } **小结** 使用cglib可以实现动态代理,即使被代理的类没有实现接口,被代理的类也不能final类。 4. Spring Ioc容器 ---------------- ### 4.1 Bean作用域 Spring 如果您使用以下五个作用域,该框架将支持它们。 web-aware ApplicationContext 其中有三个是可用的。 作用域 描述 singleton 在spring IoC只有一个集装箱Bean实例,Bean以单一实例的形式存在,默认 prototype 来自容器的每个调用Bean返回一个新实例,即每次调用。getBean()相当于执行newXxxBean() request 每次HTTP请求创建新的Bean,该范围仅适用于WebApplicationContext环境 session 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅限WebApplicationContext环境 global-session 一般用于Portlet应用程序环境,更改为仅用于WebApplicationContext环境 ### 4.2 Bean 生命周期 在传统的Java应用中,bean生命周期很简单。使用Java关键字new进行bean实例化,然后bean它是可以使用的。一次bean不再使用,Java自动垃圾收集。 相比之下,Spring容器中的bean生命周期就显得相对复杂多了。正确理解Spring bean生命周期非常重要,因为你或许要利用Spring提供的扩展点是定制的。bean在创作过程中。下图显示了bean装载到Spring应用上下文中的一个典型生命周期过程。 ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/bean-life.png) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/bean-life.png)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/bean-life.png) 上图bean在Spring容器从创建到销毁经历了几个阶段,每个阶段都可以成为目标。Spring如何管理bean个性化定制 **如您所见,在bean在你准备好之前,bean该工厂执行了几个启动步骤。我们对上面的数字进行了详细描述:** 1. Spring 对 Bean 实例化; * 相当于程序。new Xx() 2. Spring 将值和 Bean 参比注射入 Bean 在相应的属性中; 3. **如果Bean实现了 BeanNameAware 接口** ,Spring 将 Bean 的 ID 传递给setBeanName()方法 * 实现BeanNameAware清主要是为了过关Bean要获得的引用的Bean的ID总体而言,生意很少进入。Bean的ID的 4. **如果Bean实现了BeanFactoryAware接口** ,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。 * 实现BeanFactoryAware 主要目的是为了获得Spring容器,如Bean通过Spring集装箱放行事件等。 5. **如果Bean实现了ApplicationContextAwaer接口** ,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,将bean传入对它所在的应用程序上下文的引用。 * 作用与BeanFactory类似的是获得Spring集装箱,不同的是Spring集装箱在呼唤。setApplicationContext方法,当它采用自己时setApplicationContext 传入的参数,而Spring集装箱在呼唤。setBeanDactory之前需要程序员指定(注入)setBeanDactory里的参数BeanFactory 6. **如果Bean实现了BeanPostProcess接口** ,Spring将会调用他们的postProcessBeforeInitialization(预初始化)方法 * 作用是在Bean实例创建成功后,会进行增强,例如Bean进行修改以添加函数 7. **如果Bean实现了InitializingBean接口** ,Spring将会调用他们的afterPropertiesSet方法、角色和配置文件中。Bean使用init-method声明初始化的作用是相同的,都是在。Bean在完全属性设置成功后执行的初始化方法。 8. **如果Bean实现了BeanPostProcess接口** ,Spring将会调用他们的postProcessAfterInitialization(初始化后)方法 * 作用与6同样的,6是在Bean它在初始化之前执行,这是在Bean初始化后,计时不同。 9. 经过上述工作,Bean将保留在应用程序上下文中供应用程序使用,直到应用程序上下文被销毁 10. **如果Bean实现了DispostbleBean接口** ,Spring它将被称为。destory方法、角色和配置文件中。Bean使用destory-method属性的作用是相同的,都是在。Bean销毁实例前执行的方法。 ### 4.3 BeanFactory 和ApplicationContext(Bean工厂和应用程序环境) Bean 工厂(com.springframework.beans.factory.BeanFactory)是Spring 框架的核心接口,它提供了高级IoC 配置机制。 应用程序上下文(com.springframework.context.ApplicationContext)建立在BeanFactory 基于。 我们几乎在所有应用程序中都直接使用它。ApplicationContext 而不是潜在的BeanFactory。 ![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/beanfactory.jpg) ![](https://www.itfans123.com/wp-content/uploads/2022/10/post-2814-635a85050b083.gif) 转存失败 重新上传 取消 [![](https://github.com/frank-lam/2019_campus_apply/raw/master/notes/pics/beanfactory.jpg)](https://github.com/frank-lam/2019_campus_apply/blob/master/notes/pics/beanfactory.jpg) ApplicationContext 初始化的属性和BeanFactory这其中有一个显著的区别: * BeanFactory在初始化容器时,它不会实例化。Bean直到第一次访问Bean 仅实例目标Bean; * 而ApplicationContext 在初始化应用程序上下文时,所有单个实例都被实例化。Bean 。 ApplicationContext ctx = new ClassPathXmlApplicationContext("com/baobaotao/context/beans.xml"); ApplicationContext ctx = new FileSystemXmlApplicationContext("com/baobaotao/context/beans.xml"); ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{"conf/beans1.xml","conf/beans2.xml"});
版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门