java进阶--更进一步对java的理解

Spring补充

BeanFactory 和 ApplicationContext

在 spring 容器中,BeanFactory 接口是 IOC 容器要实现的最基础的接口,定义了管理 bean 的最基本的方法,例如获取实例、基本的判断等,如下图:
BeanFactory

BeanFactory 有多个子接口来进一步扩展 bean 相关的功能.

ApplicationContext也间接继承了BeanFactory,如果说BeanFactory是Sping的心脏,那么 ApplicationContext 就是完整的身躯了。它们都可以当做 Spring 的容器,Spring 容器是生成 Bean 实例的工厂,并管理容器中的 Bean。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e8j0C3VW-1644746703890)(E:\Users\asus\AppData\Roaming\Typora\typora-user-images\1644726083931.png)]

区别

1.BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身,是Spring 中比较原始的 Factory,它不支持 AOP、Web 等 Spring 插件.

而 ApplicationContext 不仅包含了 BeanFactory 的所有功能,还支持 Spring 的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。

2.BeanFactory:需要等到获取某个 bean 的时候才会创建该 bean,即延迟初始化.

ApplicationContext:在启动 Spring 容器时就会创建所有的 bean(Web 应用中推荐)

SpringBean 的生命周期

宏观上来讲,springBean 的生命周期可以分为 5 个阶段:

  1. 实例化 Instantiation

  2. 属性赋值 Populate

  3. 初始化 Initialization

  4. 将 bean 对象放入到容器中,使用

  5. 销毁 Destruction

细化来说

1.instantiate bean 对象实例化

2.populate properties 属性赋值

3.1 如果 Bean 实现 BeanNameAware 执行 setBeanName

3.2 如果 Bean 实现 BeanFactoryAware 或者ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext 对象.

初始化

3.3 如果存在类实现 BeanPostProcessor(AOP) ,执行 postProcessBeforeInitialization

3.4 如果 Bean 实现 InitializingBean 执行 afterPropertiesSet 如果配置了自己的初始化方法< bean init-method=“init” >

3.5 如果存在类实现 BeanPostProcessor(AOP) ,执行 postProcessAfterInitialization

4.完整的 bean 创建好,将 bean 对象放入到容器中,可以使用

5.销毁 如果 Bean 实现 DisposableBean 执行 destroy 如果配置了自己的销毁方法< bean destroy-method=“customerDestroy”> 指定销毁方法 customerDestroy.

SpringBean的生命周期

Spring 中的 bean 是线程安全的吗?

不是线程安全的

Spring容器本身并没有提供 Bean 的线程安全策略,因此可以说 Spring 容器中的 Bean 本身不具备线程安全的特性,但是具体还是要结合具体 scope 的 Bean 情况。

Spring 的 bean 作用域(scope)类型

​ 1、singleton:单例,默认作用域。

​ 2、prototype:原型,每次创建一个新对象。

​ 3、request:请求,每次 Http 请求创建一个新对象,适用于

​ …

线程安全这个问题,要从单例与原型 Bean 分别进行说明

原型 Bean:

对于原型 Bean,每次创建一个新对象,也就是线程之间并不存在 Bean 共享,自 然是不会有线程安全的问题。

单例 Bean:

对于单例 Bean,所有线程都共享一个单例实例 Bean,因此是存在资源的竞争。

Bean 又分为 有状态就是有数据存储功能(例如包含成员变量)

无状态就是不会保存数据 如果单例 Bean,是一个无状态 Bean,也就是线程中的操作不会对 Bean 的成员执行查 询以外的操作,那么这个单例 Bean 是线程安全的。比如 Spring mvc 的 Controller、 Service、Dao 等,这些 Bean 大多是无状态的,只关注于方法本身。

但是如果 Bean 是有状态的 那就需要开发人员自己来进行线程安全的保证,最简单的办法就是改变 bean 的作用域把 "singleton"改为’‘protopyte’ 这样每次请求Bean 就相当于是 new Bean() 这样就可以保证线程的安全了。

controller、service 和 dao 层本身并不是线程安全的,只是如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这是自己的线程的工作内存,是安全的。

Bean 循环依赖

JAVA中循环依赖场景

很简单,就是A对象依赖了B对象,B对象依赖了A对象

Bean循环依赖

//A依赖了B
class A{
	public B b;
}

//B依赖了A
class B{
    public A a;
}

那么循环依赖是个问题吗?

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。

A a = new A();
B b = new B();

a.b = b;
b.a = a;

这样,A.B 就依赖上了

但是,在 Spring 中循环依赖就是一个问题了,为什么?

因为,在 Spring 中,一个对象并不是简单 new 出来了,而是会经过一系列的Bean 的生命周期,就是因为 Bean 的生命周期所以才会出现循环依赖问题。当然,在 Spring 中,出现循环依赖的场景很多,有的场景 Spring 自动帮我们解决了,而有的场景则需要程序员来解决。

产生循环依赖的问题,主要是:A 创建时–>需要 B----s 去创建—>需要 A,从而产生了循环。

循环依赖例子

spring 内部有三级缓存:

singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的 bean 实例

earlySingletonObjects 二级缓存,用于保存实例化完成的 bean 实例

singletonFactories 三级缓存,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。

A,B 循环依赖,先初始化 A,先暴露一个半成品 A,再去初始化依赖的 B,初始化 B 时如果发现 B 依赖 A,也就是循环依赖,就注入半成品 A,之后初始化完毕 B,再回到 A 的初始化过程时就解决了循环依赖,在这里只需要一个 Map 能缓存半成品 A 就行了,也就是二级缓存就够了,但是这个二级缓存存的是 Bean 对象,如果这个对象存在代理,那应该注入的是代理,而不是 Bean,此时二级缓存无法及缓存 Bean,又缓存代理,因此三级缓存做到了缓存 工厂 ,也就是生成代理,这我的理解:总结起来:二级缓存就能解决缓存依赖,三级缓存解决的是代理。

循环依赖例子

Servlet的过滤器与Spring拦截器区别

实现原理不同

​ 过滤器和拦截器底层实现方式大不相同,过滤器是基于函数回调的,拦截器则是基于java的反射机制(动态代理)实现的。

使用范围不同

​ 我们看到的过滤器实现的是 javax.servlet.Filter 接口,而这个接口是在 Servlet 规范中定义的,也就是说过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用。

触发时机不同

过滤器 Filter 是在请求进入容器后,但在进入 servlet 之前进行预处理,请求结束是在 servlet 处理完以后。

拦截器 Interceptor 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。

拦截的请求范围不同

过滤器几乎可以对所有进入容器的请求起作用,而拦截器只会对 Controller 中请求或访问 static 目录下的资源请求起作用。

建模语言

建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言,1997 年被国际对象管理组织(OMG)采纳为面向对象的建模语言的国际标准.

建模语言能为软件开发的所有阶段提供模型化和可视化支持。而且融入了软件工程领域的新思想、新方法和新技术,使软件设计人员沟通更简明,进一步缩短了设计时间,减少开发成本 。

是指具有相同属性、方法和关系的对象的抽象,它封装了数据和行为,是面向对象程序设计(OOP)的基础,具有封装性、继承性和多态性等三大特征。在UML中类使用包含类名、属性和操作且带有分割线的矩形来表示。

(1)类名(Name)是一个字符串,例如,Student。

(2)属性(Attribute)是指类的特性,即类的成员变量。UML 按以下格式表示:

​ [可见性]属性名:类型[=默认值]

​ 例如:-name:String

​ 注意:“可见性”表示该属性对类外的元素是否可见,包括公有(Public)、私有(Private)、受保护(Protected)和朋友(Friendly)4 种,在类图中分别用符号+、-、#、~表示。

(3) 操作(Operations)是类的任意一个实例对象都可以使用的行为,是类的成员方法。UML 按以下格式表示:

​ [可见性]名称(参数列表)[:返回类型]

​ 例如:+display():void。

学生类的 UML 表示。

在这里插入图片描述

接口

接口(Interface)是一种特殊的类,它具有类的结构但不可被实例化,只可以被子类实现。它包含抽象操作,但不包含属性。它描述了类或组件对外可见的动作。在 UML 中,接口使用一个带有名称的小圆圈来进行表示。

图形类接口的 UML 表示。

接口

类图

类图(ClassDiagram)是用来显示系统中的类、接口、协作以及它们之间的静态结构和关系的一种静态模型。它主要用于描述软件系统的结构化设计,帮助人们简化对软件系统的理解,它是系统分析与设计阶段的重要产物,也是系统编码与测试的重要模型依据。

类图中的类可以通过某种编程 语言直接实现。类图在软件系统开发的整个生命周期都是有效的,它是面向对象系统的建模中最常见的图。下图所示是“计算长方形和圆形的周长与面积”的类图,图形接口有计算面积和周长的抽象方法,长方形和圆形实现这两个方法供访问类调用。

类图

类之间的关系

在软件系统中,类不是孤立存在的,类与类之间存在各种关系。根据类与类之间的耦合度从弱到强排列,UML 中的类图有以下几种关系:依赖关系、关联关系、 聚合关系、组合关系、泛化关系和实现关系。其中泛化和实现的耦合度相等,它们是最强的。

依赖关系

依赖(Dependency)关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。

在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是人与手机的关系图,人通过手机的语音传送方法打电话。

依赖关系

关联关系

关联(Association)关系是对象之间的一种引用关系,用于表示一类对象与另 一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。

关联可以是双向的,也可以是单向的。在 UML 类图中,双向的关联可以用带两个箭头或者没有箭头的实线来表示,单向的关联用带一个箭头的实线来表示, 箭头从使用类指向被关联的类。也可以在关联线的两端标注角色名,代表两种不同的角色。

在代码中通常将一个类的对象作为另一个类的成员变量来实现关联关系。

下图所示是老师和学生的关系图,每个老师可以教多个学生,每个学生也可向多个老师学,他们是双向关联。

关联关系

聚合关系

聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。

聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的

一部分, 但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。

在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图。

组合关系

组合(Composition)关系也是关联关系的一种,也表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系,是 cxmtains-a 关系。

在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。

在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图。

组合关系

泛化关系

泛化(Generalization)关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是 is-a 的关系。

在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。例如, Student 类和 Teacher 类都是 Person 类的子类,其类图下图所示。

泛化关系

实现关系

实现(Realization)关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。

在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如,汽车和船实现了交通工具,其类图如下图所示。

实现关系

面向对象设计原则

在面向对象的设计过程中,首先需要考虑的是如何同时提高一个软件系统的可维护性和可复用性。这时,遵从面向对象的设计原则,可以在进行设计方案时减少错误设计的产生,从不同的角度提升一个软件结构的设计水平。

设计原则一句话归纳目的
开闭原则对扩展开放,对修改关闭降低维护带来的新风险
依赖倒置原则高层不应该依赖低层,要面向接口编程更便于理解,提高代码可读性
单一职责原则一个类只干一件事,实现类要单一便于理解,提高代码可读性
接口隔离原则一个接口只干一件事,接口要精简单一功能解耦,高聚合,低耦合
迪米特法则不该知道的不知道,一个类应该保持对其他对象最少的了解,降低耦合度知识和朋友说话,不和陌生人说话,减少代码的臃肿
里氏替换原则不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义防止继承泛滥
合成复用原则尽量使用组合或者聚合关系实现代码复用,少使用继承降低代码耦合

单一职责原则 S

一个类应该只有一个发生变化的原因。一个类只负责一个功能领域中的相应职责, 即一个类不要负责太多“杂乱”的工作。

优点: 低耦合,高内聚。

开放封闭原则O

一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭

为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。

开放封闭原则

优点:

适应性和灵活性

稳定性和延续性

可复用性与可维护性

里氏替换原则 L

所有引用基类的地方必须能透明地使用其子类的对象(多态)

接口隔离原则 I

1、客户端不应该依赖它不需要的接口。
2、类间的依赖关系应该建立在最小的接口上。

依赖倒置原则 D

1、上层模块不应该依赖底层模块,它们都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。

迪米特原则

一个对象应当对其他对象尽可能少的了解,降低耦合。

迪米特原则

组合/聚合复用原则

优先使用组合,使系统更灵话,其次才考虑继承,达到复用的目的。

一般而言,如果两个类之间是"Has-A"关系应使用组合或聚合,如果是"Is-A"关系可使用继承。

总结

七大原则虽说是原则,但它们并不是强制性的,更多的是建议。遵照这些原则固然能帮助我们更好的规范我们的系统设计和代码习惯,但并不是所有的场景都适用,就例如接口隔离原则,在现实系统开发中,我们很难完全遵守一个模块 一个接口的设计,否则业务多了就会出现代码设计过度的情况,让整个系统变得过于庞大,增加了系统的复杂度,甚至影响自己的项目进度。

Java设计模式(java design patterns)

设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。

https://www.runoob.com/design-pattern/design-pattern-tutorial.html

学习设计模式的意义

设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。正确使用设计模式具有以下的优点:

​ 1、可以提高程序员的思维能力、编程能力和设计能力

​ 2、使程序设计更加标准化、代码编制更加工程化,使开发效率大大提高,从而缩短软件的开发周期。

​ 3、使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

java设计模式类型

根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。

创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使 用分离”。提供了单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。

结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。

行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。

常用的设计模式

单例模式

在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。例如,Windows中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。

单例模式有3个特点:

​ 1、单例类只有一个实例对象

​ 2、该单例对象必须由单例类自行创建

​ 3、单例类对外提供一个访问该单例的全局访问点

单例模式的结构

单例类:包含一个实例且能自行创建这个实例的类。

访问类:使用单例的类。其结构如图所示。

单例模式

单例模式通常有两种实现形式

1、懒汉式

该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例 ,这种方式有可能会产生线程安全问题,需要加锁解决

单例类

public class Singleton{
    //定义静态的
    private ststic Singleton instance = null;
    
    //让构造函数为 private,这样该类就不会被实例化
    private Singleton(){}
    
    //向外界提供获取实例的方法  加锁 synchronized 才能保证单例
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

测试类

public class Test {
	 public static void main(String[] args) {
		 
	      //获取唯一可用的对象
		 Singleton object1 = Singleton.getInstance();
		 Singleton object2 = Singleton.getInstance();
		 Singleton object3 = Singleton.getInstance();
		 System.out.println(object1);
		 System.out.println(object2);
		 System.out.println(object3);
	}
}

2、饿汉式

该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。饿汉式不会出现线程安全问题。

单例类

public class Singleton{
    //创建 Singleton 的一个对象
    private static Singleton instance = new Singleton();
    
    //让构造函数为private
    private Singleton(){};
    
    //获取唯一可用的对象
    public static Singleton getInstance(){
        return instance;
    }
}

测试类

public class Test {
	 public static void main(String[] args) {
		 
	      //获取唯一可用的对象
		 Singleton object1 = Singleton.getInstance();
		 Singleton object2 = Singleton.getInstance();
		 Singleton object3 = Singleton.getInstance();
		 System.out.println(object1);
		 System.out.println(object2);
		 System.out.println(object3);
	}
}

工厂模式

工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。

按实际业务场景划分,工厂模式有 2 种不同的实现方式,分别是简单工厂模式、抽象工厂模式。

简单工厂

我们吧被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。

在简单工厂模式中创建实例的方法通常为静态方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。

简单工厂模式的主要角色如下:

简单工厂(SimpleFactory):是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

/*
 * 工厂,负责生产对象
 */
public class SimpleFactory{
    //工厂中负责制造对象的方法
	   public Product createProduct(String className){
		   if(className == null){
			   return null;
		   }else{
				try {
					return (Product) Class.forName(className).newInstance();//反射机制
				} catch (InstantiationException e) {
					e.printStackTrace();
					return null;
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					return null;
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
					return null;
				}
		   }      
	   }
}

抽象产品(Product):是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。

//抽象产品
public interface Product {

    void show();

}

具体产品(ConcreteProduct):是简单工厂模式的创建目标。

//具体产品1
public class ProductA implements Product {

    @Override
    public void show() {
        System.out.println("具体产品1显示...");
    }

}

//具体产品2
public class ProductB implements Product {

    @Override
    public void show() {
        System.out.println("具体产品2显示...");
    }

}

测试类

public class Test {
	 public static void main(String[] args) {
	      SimpleFactory simpleFactory = new SimpleFactory();

		 Product productA = simpleFactory.createProduct("com.ffyc.javaforward.javadesign.simplefactory.ProductA");

		 Product productB = simpleFactory.createProduct("com.ffyc.javaforward.javadesign.simplefactory.ProductB");
	 }
}

其结构图如下图所示。

简单工厂

代理模式

在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找保姆、找工作,买东西等都可以通过找中介完成。

代理模式的主要优点有:

1、代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;

2、代理对象可以扩展目标对象的功能;

3、代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度。

模式的结构

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。

  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。

  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

其结构图如图所示

代理模式

静态代理

静态代理模式的特点,代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。但是也有缺点,每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。其次,代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。

抽象主题类

/*
  Dao接口,定义保存功能
 */
public interface BaseDao {
	
	void save();
	
}

真实主题类

public class RoleDaoImpl implements BaseDao{

    @Override
    public void save() {
        System.out.println("保存角色");
    }
}

/*
  实际功能实现类
 */
public class UserDaoImpl implements BaseDao {

	    @Override
	    public void save() {
	        System.out.println("UserDaoImpl:save() 保存用户");
	    }

}

代理类

/*
 * 静态(只能代理一种)代理类
 */
public class StaticDaoProxy implements BaseDao {

	//接收所有实现BaseDao接口的实现类对象
	private BaseDao baseDao;

	
	// 将被代理者的实例传进动态代理类的构造函数中
	public StaticDaoProxy(BaseDao baseDao) {
        this.baseDao = baseDao;
    }

    //代理他们实现功能,可以在调用前,调用后额外添加功能.
	@Override
	public void save() {
		System.out.println("before");//额外的扩展功能
		baseDao.save();//调用的是真实目标中的方法
		System.out.println("after");//额外的扩展功能
	}
}

测试类

public class Test {

	 public static void main(String[] args) {
          //把实际执行者交给代理对象管理即可
		  StaticDaoProxy subject = new StaticDaoProxy(new UserDaoImpl());
	      subject.save();//对于用户来讲,访问的是代理中的方法
     }
}

动态代理

动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等,动态代理分为 jdk 动态代理和 cglib 动态代理

jdk代理

动态代理是实现方式,是通过反射来实现的,借助 Java 自带的 java.lang.reflect.Proxy,通过固定的规则生成。

其步骤如下:

  1. 编写一个委托类的接口,即静态代理的

  2. 实现一个真正的委托类,即静态代理的

  3. 创建一个动态代理类,实现 InvocationHandler 接口,并重写该 invoke 方法

  4. 在测试类中,生成动态代理的对象。

委托类

/*
  Dao接口,定义保存功能
 */
public interface BaseDao {
	
	void save();
	
}

真正委托类

/*
  实际功能实现类
 */
public class UserDaoImpl implements BaseDao {

	    @Override
	    public void save() {
	        System.out.println("UserDaoImpl:save()");
	    }

}

代理类

/*
 * 动态代理类
 *   jdk代理  底层实现使用反射机制
 */
public class DynamicDaoProxy implements InvocationHandler {

	// 被代理类的实例  目标
	private Object object;// BaseDao ---> Object(任意的)

	// 将被代理者的实例传进动态代理类的构造函数中
	public DynamicDaoProxy(Object object) {
		this.object = object;
	}

	/*
	 * 覆盖InvocationHandler接口中的invoke()方法
	 *    Object proxy 表示代理对象
	 *    Method method 代理对象中的方法
	 *	  Object[] args  表示代理方法中的参数
	 * 更重要的是,动态代理模式可以使得我们在不改变原来已有的代码结构
	 * 的情况下,对原来的“真实方法”进行扩展、增强其功能,并且可以达到
	 * 控制被代理对象的行为,下面的before、after就是我们可以进行特殊
	 * 代码切入的扩展点了。
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before");
		Object result = method.invoke(object, args);//用反射机制获取到目标对象中的方法
		System.out.println("after");
		return result;
	}
}

测试类

public class Test {

	 public static void main(String[] args) {
		 //我们要代理的真实对象    Service层是需要添加事务
		 UserDaoImpl userDaoImpl = new UserDaoImpl();
		 //我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
		 InvocationHandler dynamicProxy = new DynamicDaoProxy(userDaoImpl);
		 /*
		  * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
		  * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
		  * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
		  * 第三个参数dynamicProxy, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
		  */
		 //动态生成的代理对象
		 BaseDao baseDao = (BaseDao) Proxy.newProxyInstance(dynamicProxy.getClass().getClassLoader(), userDaoImpl.getClass().getInterfaces(), dynamicProxy);
          //  mybatis  接口代理  访问 接口中的方法
		 baseDao.save();
     }
}

**动态代理总结:**虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注 定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。

Cglib 代理

JDK 实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要 CGLib 了。CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦 截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final 修饰的类进行代理。JDK 动态代理与 CGLib 动态代理均是实现 Spring AOP 的基础。

Cglib 子类代理实现方法:

​ 1.需要引入 cglib 的 jar 文件,但是 Spring 的核心包中已经包括了 Cglib 功能,所以直接引入 spring-core-xxx.jar 即可.

​ 2.引入功能包后,就可以在内存中动态构建子类

​ 3.代理的类不能为 final,否则报错

​ 4.目标对象的方法如果为 final/static,那么就不会被拦截,即不会执行目标对象额 外的业务方法.

CGLIB 创建的动态代理对象比JDK 创建的动态代理对象的性能更高,但是 CGLIB创建代理对象时所花费的时间却比 JDK 多得多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。

具体主题类

//具体主题
public class UserDaoImpl {
	
	public void save() {
	        System.out.println("UserDaoImpl:save()");
	    }

}

动态代理类

/*
 * 动态代理类
 */
public class CGLibProxy implements MethodInterceptor {
    
	    private Enhancer enhancer = new Enhancer();
	    
	    public Object getProxy(Class<?> clazz){  
	        enhancer.setSuperclass(clazz);  
	        enhancer.setCallback(this);  
	        return enhancer.create();  
	    }  
	    /*
	     * 拦截所有目标类方法的调用 
	     * 参数: 
	     * obj  目标实例对象 
	     * method 目标方法的反射对象 
	     * args 方法的参数 
	     * proxy 代理类的实例 
	     */  
	    @Override
		public Object intercept(Object obj, Method method, Object[] args,
								MethodProxy proxy) throws Throwable {
	        //代理类调用父类的方法  
	        System.out.println("开始事务");  
	        Object obj1 = proxy.invokeSuper(obj, args);  
	        System.out.println("关闭事务");  
	        return obj1;  
	    }
}

测试类

public class Test {

	 public static void main(String[] args) {
		  CGLibProxy proxy = new CGLibProxy();  
		  UserDaoImpl userDaoImpl = (UserDaoImpl) proxy.getProxy(UserDaoImpl.class);
		  userDaoImpl.save();
	}
}

注解

什么是注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java语言中的类、方法、变量、参数和包等都可以被标注。和Javadoc不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。java虚拟机可以保留标注内容,在运行时可以获取到标注内容。当然它也支持自定义Java标注。

内置的注解

@Override -检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

@Deprecated -标记过时方法。如果使用该方法,会报编译警告。

@SuppressWarnings -指示编译器去忽略注解中声明的警告。

作用在其他注解的注解(或者说 元注解)是:

@Retention -标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在 运行时可以通过反射访问。

@Documented -标记这些注解是否包含在用户文档中。

@Target - 标记这个注解应该是哪种 Java 成员。

@Inherited -标记这个注解是继承于哪个注解类(默认注解并没有继承于任何子类)

@SafeVarargs -Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。

@FunctionalInterface -Java 8 开始支持,标识一个匿名函数或函数式接口。

@Repeatable -Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

重点

@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方.

ElementType.TYPE 可以应用于类的任何元素。

ElementType.CONSTRUCTOR 可以应用于构造函数。

ElementType.FIELD 可以应用于字段或属性。

ElementType.LOCAL_VARIABLE 可以应用于局部变量。

ElementType.METHOD 可以应用于方法级注释。

ElementType.PACKAGE 可以应用于包声明。

ElementType.PARAMETER 可以应用于方法的参数。

@Retention:@Retention 定义了该注解被保留的时间长短:某些注解仅出现在源代码中,而被编译器丢弃;而另一些却被编译在 class 文件中;编译在 class 文件中的注解可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注意并不影响 class 的执行,因为注解与 class 在使用上是被分离的)。使用这个 meta-Annotation 可以对注解的“生命周期” 限制。作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) 取值(RetentionPoicy)有:

1.SOURCE:在源文件中有效(即源文件保留)

2.CLASS:在 class 文件中有效(即 class 保留)

3.RUNTIME:在运行时有效(即运行时保留)

自定义注解

注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.FIELD)//作用在属性上
@Retention(RetentionPolicy.RUNTIME)//在运行时检测
public @interface NotNull {

	String message() default ""; //注解属性
	
	int length() default 0;
	
	String lengthmessage() default "";
}

使用注解的类

public class User {
	
	private int num;

	@NotNull(message="姓名不能为空",length=3,lengthmessage="长度不能小于3")
	private String name;

	public String getName() {
		return name;
	}

	/**
	 * 是使用在文档注释中的标注,与@NotNull是不同的
	 * @param name
	 */
	public void setName(String name) {
		this.name = name;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public static void main(String[] args) {
		new Date().getYear();
		ArrayList list = new ArrayList();
	}
}

测试类

public class Test {
      //测试注解
	  public static void main(String[] args) throws NoSuchMethodException, SecurityException, Exception {
		  User user = new User();
		    //  user.setName("ji");

		   //通过反射机制获取类的注解信息
		  Field[] fields = user.getClass().getDeclaredFields();//获取user类中所有的属性
		  for (Field field : fields) {
	            NotNull notNull = field.getAnnotation(NotNull.class);//获得此属性上的 名为NotNull的注解标签
	            if (notNull != null) {
	            	 //通过属性名获取找到属性的get方法
	                Method m = user.getClass().getMethod("get" + getMethodName(field.getName()));
	                Object obj=m.invoke(user);//执行get方法  获得属性值
	                if (obj==null) { //值为空  获取注解信息
	                    System.err.println(field.getName() +notNull.message());
	                    throw new NullPointerException(notNull.message());
	                }else{
	                	if(String.valueOf(obj).length()<(notNull.length())){
	                		 System.err.println(field.getName() +notNull.lengthmessage());
	                	}
	                }
	            }
	        }
	   }

		/**
		 * 把一个字符串的第一个字母大写
		 */
	    private static String getMethodName(String fildeName) throws Exception {
	        byte[] items = fildeName.getBytes();
	        items[0] = (byte) ((char) items[0] - 'a' + 'A');
	        return new String(items);
	    }
}

对象克隆

为什么要克隆?

克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠 clone 方法了。那么我把这个对象的临时属性一个一个的赋值给我新 new 的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了 clone 是一个 native 方法,就是快啊,在底层实现的。

误区

Student stu1 = new Student (); 
Student stu2 = stu1 ;

这种形式的代码复制的是引用,即对象在内存中的地址,a 和 b 对象仍然指向了同一个对象。这种只能称为引用复制,两个引用指向的还是同一个对象.

如何实现克隆

克隆有两种方式,浅克隆和深克隆

在 Java 语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括 int、double、byte、boolean、char 等简单数据类型,引用类型包括类、 接口、数组等复杂类型。基本类型的值可以直接复制,引用类型只能复制引用地址。所以浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制。

浅克隆

如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果 原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

实现方式

1、在 Java 语言中,通过覆盖 Object 类的 clone()方法可以实现浅克隆。

2、在 spring 框架中提供 BeanUtils.copyProperties(source,target);

深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

实现方式

1、可以通过覆盖 Object 类的 clone()方法实现

2、也可以通过序列化(Serialization)等方式来实现。

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现 Serializable 接口,否则无法实现序列化操作。

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用 clone 方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值