本人本科毕业,21届毕业生,一年工作经验,简历专业技能如下,现根据简历,并根据所学知识复习准备面试。
记录日期:2022.1.9
大部分知识点只做大致介绍,具体内容根据推荐博文链接进行详细复习。
这部分我之前的学习,是通过博客,以及《Spring 5核心原理与30个类手写实战》这本书学习的,这本书前面会介绍设计模式多一点。
当然比较推荐的是《Java设计模式》。
有一篇比较详细的博客链接参考:史上最全设计模式导学目录(完整版)
设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。
设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
正确使用设计模式具有以下优点。
一般主要讲一下23种设计模式。
创建型设计模式,是指在创建对象的同时隐藏创建逻辑,不使⽤ new 直接实例化对象,程序在判断需要创建哪些对象时更灵活。包括下面五种模式:
一般说二四种设计模式的时候,是指23个GoF设计模式+简单工厂模式,简单工厂模式属于创建型模式,待会也可以提一下。
科普一下GOF:GOF是设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software(中译本名为《设计模式——可复用面向对象软件的基础》)的四位作者,他们分为是:Elich Gamma、Richard Helm、Ralph Johnson、以及John Vlissides。这四个人常被称为Gang of Four, 即四人组,简称Gof。 他们总结了23个设计模式。
结构型设计模式,是指通过类和接⼝间的继承和引⽤实现创建复杂结构的对象。包括下面七种模式:
适配器模式(Adapter)
装饰器模式(Decorator)
代理模式(Proxy)
外观模式(Facade)
桥接模式(Bridge)
组合模式(Composite)
享元模式(Flyweight)
行为型设计模式,是指通过类之间不同通信方式实现不同行为。包括下面十一种模式:
策略模式(Strategy)
模板方法模式(Template Method)
观察者模式(Observer)
迭代器模式(Iterator)
责任链模式(Chain of Responsibility)
命令模式(Command)
备忘录模式(Memento)
状态模式(State)
访问者模式(Visitor)
中介者模式(Mediator)
解释器模式(Interpreter)
上面二十三种中重点学习的设计模式我用加粗标出来,大部分是平时有用到的设计模式。
参考文章链接:设计模式(十)适配器模式
在日本,日本的插座电压都110V,而我们国内的手机充电器和笔记本充电器都是220V,这个时候就无法充电了,因为电压不匹配,此时需要一个变压器来将电压升到220V,这样我们就可以在日本插座和我们的电器中间添加一个变压器来适配我们的设备了。
适配器模式(Adapter Design Pattern),它是用来做适配的,它将不兼容的接口转换为可兼容的接口
,让原本由于接口不兼容而不能一起工作的类可以一起工作。
适配器模式有两种实现方式:类适配器
和对象适配器
。
类适配器使用继承关系
来实现;对象适配器使用组合关系
来实现。
对象适配器
是使用组合的方法,在Adapter中会保留一个原对象(Adaptee)的引用,适配器的实现就是讲Target中的方法委派给Adaptee对象来做,用Adaptee中的方法实现Target中的方法。
从上图可以看出,对象适配器主要由目标接口(Target)
、需要适配的类(Adaptee)
、适配器(Adapter)
三部分组成(除去调用接口的客户端Client):
就以刚才日本电压 和 中国电压的区别来编写电源适配器,示例代码如下:
// 日本插座抽象类
public abstract class IJapanSocket {
public abstract Integer japanCharge();
}
// 中国插座抽象类
public abstract class IChineseSocket {
public abstract Integer chineseCharge();
}
// 中国220V插座
class ChineseSocket extends IChineseSocket {
public Integer chineseCharge() {
return 220;
}
}
// 电压适配器
public class SocketAdapter extends IJapanSocket {
ChineseSocket chineseSocket;
public SocketAdapter(ChineseSocket chineseSocket) {
this.chineseSocket = chineseSocket;
}
public Integer charge() {
return chineseSocket.chineseCharge;
}
}
那我们实际使用时,客户端代码如下:
public class Client {
public static void main(String[] args) {
IChineseSocket socket = new ChineseSocket(); // 中国插座实现类
IJapanSocket socketAdapter = new SocketAdapter(socket); // 电源适配器
System.out.println("电源电压是" + socketAdapter.charge() + "V");
}
}
日本插座和中国插座有相似之处,他们都可以作为供电源,区别就在于电压不同,现在我们有一个中国插座的实现类,有日本插座的抽象类也就是接口。我们的电压适配器继承自日本插座抽象类并且保留了中国插座实现类的引用,重写供电的方法,但是是使用的中国插座的供电方法来实现的。在客户端中,我们给适配器传递一个中国插座实现类对象,就可以把它当做日本插座来使用了。
与对象适配器不同的是,类适配器是通过类的继承来实现的。Adpater直接继承了Target和Adaptee中的所有方法,并进行改写,从而实现了Target中的方法。
类适配器的主要部分与对象适配器相似。
这种方式的缺点就是必须实现Target和Adaptee中的方法,由于Java不支持多继承,所以通常将Target设计成接口,Adapter继承自Adaptee然后实现Target接口。
// 日本插座抽象类
interface IJapanSocket {
Integer japanCharge();
}
// 中国插座抽象类
public abstract class IChineseSocket {
public abstract Integer chineseCharge();
}
// 电压适配器
public class SocketAdapter extends IChineseSocket implements IJapanSocket {
public Integer charge() {
return chineseSocket.chineseCharge;
}
public Integer japanCharge() {
return chineseCharge();
}
public Integer chineseCharge() {
return 220;
}
}
那我们实际使用时,客户端代码如下:
public class Client {
public static void main(String[] args) {
IJapanSocket socketAdapter = new SocketAdapter(); // 电源适配器
System.out.println("电源电压是" + socketAdapter.chineseCharge() + "V");
}
}
这两种方法的效果是一样的,只是用的方法不一样。
Java不支持多继承,所以将Duck声明为接口,Adapter继承自中国插座类并且实现了日本插座的方法,但是实现日本插座的方法不再是委派给中国插座类的对象,而是直接调用中国插座类的方法,因为在Adapter中实现了中国插座类的方法,所以可以直接调用。
在实际开发中,一个类如果想要实现某一个接口,就必须要实现接口中的每一个方法,如果目标(Target)角色中的方法众多,而这个类需要的仅仅几个,但是根据接口的实现规则,其余的方法也必须实现,这就造成了很多的不便,代码会很累赘。这个时候就可以考虑使用缺省适配器模式了。
缺省适配器模式,是为一个接口提供缺省实现,这样的类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。
当原接口中定义的方法太多,而其中大部分又不被需要时,这种模式非常实用。由缺省适配器类直接实现目标(Target)角色接口,并为所有方法提供缺省的空实现。用户类就只需要继承适配器类,只实现自己需要实现的方法就行了。
先举一个例子:
在学校里,一般学生会做的事情有:吃饭、睡觉、学习、看书、提问、谈恋爱。
而在我们初一四班里的同学,张三是个学渣,他只会吃饭、睡觉。
那我们可以将学生作为接口,初一四班里的同学作为中间者,而张三属于班级的一员,让张三继承于初一四班里的同学。
示例代码如下:
// 学生接口
public interface Student {
public void eat();
public void sleep();
public void study();
public void read();
public void question();
public void love();
}
// 初一四班学生抽象类
public abstract class Class4Student implements Student {
public void eat(){}
public void sleep(){}
public void study(){}
public void read(){}
public void question(){}
public void love(){}
}
// 张三
public class ZhangSan extends Class4Student {
public void eat(){
System.out.println("张三爱吃东西");
}
public void sleep(){
System.out.println("张三爱睡觉");
}
}
我们看到通过初一四班的同学(缺省适配器),张三不需要再实现自己不需要的方法了。
将目标类和适配者类解耦
,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。增加了类的透明性和复用性
,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。灵活性和扩展性都非常好
,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。类适配器模式还具有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还具有如下优点:
一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
类适配器模式的缺点如下:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
对象适配器模式的缺点如下:
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
SpringMVC
在请求处理流程(见后面的SpringMVC详解,应该会写的
)中涉及HandlerAdapter
处理器适配器。
public interface HandlerAdapter {
boolean supports(Object handler);
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
HandlerAdapter
的实现类有这几种:
使用 HandlerAdapter
的原因分析:如果处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果直接调用 Controller
方法,就得不断使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller
,就得修改原来的代码,这样违背了 OCP
原则。
HandlerAdapter
使用说明:
装饰模式(Decorator Pattern) ,动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。
从上图中可以看出,装饰模式一共有抽象组件角色(Component)
、具体组件角色(ConcreteComponent)
、抽象装饰器(Decorator)
、具体装饰器角色(ConcreteDecorator)
四部分组成:
以做咖啡为例子,一杯咖啡,可以选择加牛奶、加糖、加水、加巧克力,不同的选择在最后拿出来的是一杯不同的咖啡,我们通过代码实现:
// 一种普通的咖啡 抽象类
public abstract class Coffee {
// 咖啡的介绍
protected String description = "genral Coffee";
public String getDescription() {
return description;
}
// 每个子类都有自己的实现方法
public abstract double cost();
}
现在写一个抽象装饰类,规范好具体装饰类需要实现的代码:
// 抽象装饰类
public abstract class AbstractAddDecorator extends Coffee {
public abstract String getDescription();
}
现在来具体实现装饰类:
// 具体的装饰者,加牛奶类
public class MilkCoffee extends AbstractAddDecorator {
// 保留一个被装饰者的引用
Coffee coffee;
public Mocha(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription() + ", Add milk";
}
public double cost() {
return beverage.cost() + 20;
}
}
被装饰者实现类,比如咖啡有分为摩卡、卡布奇诺等…
// 被装饰者实现类,摩卡咖啡
public class Moka extends Coffee {
public Moka() {
description = "Moka";
}
public double cost() {
return 150;
}
}
写一段测试测试代码:
public static void main(String[] args) {
Coffee coffee = new Moka();
System.out.println("咖啡:" + coffee.getDescription());
System.out.println("消费了" + coffee.cost());
Coffee milkMoka = new MilkCoffee(coffee);
System.out.println("咖啡:" + milkMoka.getDescription());
System.out.println("消费了" + milkMoka.cost());
}
}
Java的IO流是践行装饰者模式的典型实践。
我们拿InputStream
举例,来看一下它的类图:
我们来看一下FilterInputStream
:
class FilterInputStream extends InputStream {
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
}
为什么要拿字节流举例?Java还有一种字符流Reader。
因为字符流的体系里面还涉及到其他的设计模式,并不完全契合这小节的主题。
在Spring 中的TransactionAwareCacheDecorator 类我们也可以来尝试理解一下,这个类主要是用来处理事务缓存的,来看一下代码:
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;
public TransactionAwareCacheDecorator(Cache targetCache) {
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public Cache getTargetCache() {
return this.targetCache;
}
}
TransactionAwareCacheDecorator 就是对Cache 的一个包装。
MVC 中的装饰者模式HttpHeadResponseDecorator 类,看一下代码:
public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator {
public HttpHeadResponseDecorator(ServerHttpResponse delegate) {
super(delegate);
}
}
org.apache.ibatis.cache.Cache
接口,他有很多实现类,比如FifoCache 先入先出算法的缓存;LruCache 最近最少使用的缓存;TransactionlCache 事务相关的缓存,都是采用装饰者模式。实现类有:
我们拿一个举例子来看一下,比如FifoCache
:
public class FifoCache implements Cache {
private final Cache delegate;
private final Deque<Object> keyList;
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList();
this.size = 1024;
}
}
参考文章链接:代理模式的使用总结
敖丙的文章:《设计模式系列》- 代理模式
代理模式,为其他对象提供一种代理以控制对这个对象的访问
。
从上图中可以看出,代理模式一共有抽象主题角色(Subject)
、具体主题角色(RealSubject)
、代理主题角色(Proxy)
三部分组成:
代理模式又分为静态代理和动态代理。
静态代理是由开发者创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理是在程序运行时,通过运用反射机制动态的创建而成。
静态代理,由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。
举个例子,一个班的同学都要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长代理学生上交班费,班长就是学生的代理,示例代码如下:
// 创建Person接口
public interface Person {
//上交班费
void giveMoney();
}
编写Student
实现类,示例代码如下:
public class Student implements Person {
public String name;
public Student(String name) {
this.name = name;
}
public void giveMoney() {
System.out.println(name + "上交班费50元");
}
}
再编写StudentsProxy
类,这个类也实现了Person接口,但是还另外持有一个学生类对象。由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费行为,示例代码如下:
// 学生代理类,也实现了Person接口,保存一个学生实体,这样可以代理学生产生行为
public class StudentsProxy implements Person {
//被代理的学生
Student student;
public StudentsProxy(Person student) {
// 只代理学生对象
if(student instanceof Student) {
this.stu = (Student) stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
stu.giveMoney();
}
}
编写测试代码如下:
public static void main(String[] args) {
//被代理的学生张三
Person zhangsan = new Student("张三");
//生成代理对象,并将张三传给代理对象
Person banzhang = new StudentsProxy(zhangsan);
//班长代理上交班费
banzhang.giveMoney();
}
这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了,这就是代理模式。
代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。
就拿这个例子来说,假如班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式实现:
public class StudentsProxy implements Person{
//被代理的学生
Student student;
public StudentsProxy(Person student) {
// 只代理学生对象
if(student instanceof Student) {
this.stu = (Student) stu;
}
}
//代理上交班费,调用被代理学生的上交班费行为
public void giveMoney() {
System.out.println(student.name + "最近学习有进步!");
student.giveMoney();
}
}
只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。
动态代理,是指在运行时,动态生成代理类
。即代理类的字节码将在运行时生成并载入当前的ClassLoader。
动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。
动态代理在程序运⾏时通过反射
创建具体的代理类,代理类和被代理类的关系在运⾏前是不确定的。动态代理的适⽤性更强,主要分为 JDK 动态代理和 CGLib 动态代理。
比如说如果我们想要给每一个代理方法都加上一个前置处理方法,如下:
public void giveMoney() {
//调用被代理方法前加入处理方法
beforeMethod();
student.giveMoney();
}
这里只有一个giveMoney方法,就写一次beforeMethod方法,但是如果除了giveMonney还有很多其他的方法,那就需要写很多次beforeMethod方法,麻烦。所以建议使用动态代理实现。
Jdk的动态代理是基于接口的。现在想要为RealSubject这个类创建一个动态代理对象,Jdk主要会做一下工作:
Jdk
通过java.lang.reflect.Proxy
包来支持动态代理,在Java中要创建一个代理对象,必须调用Proxy
类的静态方法newProxyInstance()
,该方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
其中对于上面3个参数做一下说明:
InvocationHandler
接口是proxy
代理实例的调用处理程序实现的一个接口,每一个proxy
代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke
方法。
InvocationHandler
和Proxy
的关系,就如Runnable
和Thread
的关系。InvocationHandler
接口中只有一个方法invoke
,它的作用就跟Runnable
中的run
方法类似,定义了代理对象在执行真实对象的方法时所希望执行的动作。其主要代码如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
其中对于上面3个参数做一下说明:
InvocationHandler
接口的调用处理器对象,然后将它作为创建代理类实例的参数。(抑或在调用newProxyInstance方法时使用匿名内部类。)这样就得到了代理对象。还是以之前的例子来说明:
// 创建Person接口
public interface Person {
//上交班费
void giveMoney();
}
我们来实现学生类:
// 学生类 实现Person接口
public class Student implements Person {
public String name;
public Student(String name) {
this.name = name;
}
@Override
public void giveMoney() {
try {
//假设数钱花了一秒时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "上交班费50元");
}
}
我们通过定义一个检测方法执行时间的工具类,在任何方法执行前先调用start
方法,执行后调用finsh
方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具,代码如下:
// 线程运行时间监测工具类
public class MonitorUtil {
private static ThreadLocal<Long> tl = new ThreadLocal<>();
public static void start() {
tl.set(System.currentTimeMillis());
}
//结束时打印耗时
public static void finish(String methodName) {
long finishTime = System.currentTimeMillis();
System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
}
}
这个时候我们准备实现JDK动态代理的流程,一个代理类实现接口,需要完成委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
我们来创建StuInvocationHandler
类,实现InvocationHandler
接口,这个类中持有一个被代理对象的实例target
。InvocationHandler
中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke
方法。在invoke
方法中执行被代理对象target
的相应方法。在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。
这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理对象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表动态代理对象
* method:代表正在执行的方法
* args:代表调用目标方法时传入的实参
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理执行" +method.getName() + "方法");
//代理过程中插入监测方法,计算该方法耗时
MonitorUtil.start();
if (Objects.equals(method.getName(), "giveMoney")) { // 匹配一下对应方法
Object result = method.invoke(target, args);
}
MonitorUtil.finish(method.getName());
return result;
}
}
我们测试一下代码:
public static void main(String[] args) {
//创建一个实例对象,这个对象是被代理的对象
Person zhangsan = new Student("张三");
//创建一个与代理对象相关联的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
//创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理执行上交班费的方法
stuProxy.giveMoney();
}
创建了一个需要被代理的学生张三,将zhangsan
对象传给了stuHandler
中,我们在创建代理对象stuProxy
时,将stuHandler
作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke
方法,也就是说,最后执行的是StuInvocationHandler
中的invoke
方法。
我们利用Proxy
类的newProxyInstance
方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException {
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone(); // 对接口数组克隆
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
Class<?> cl = getProxyClass0(loader, intfs); // 这里产生了代理类
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
// private static final Class>[] constructorParams = { InvocationHandler.class };
final Constructor<?> cons = cl.getConstructor(constructorParams); // 获取InvocationHandler构造器
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h}); // 使用构造器返回实例
} catch (IllegalAccessException|InstantiationException e) {
// throw ex
} catch (InvocationTargetException e) {
// throw ex
} catch (NoSuchMethodException e) {
// throw ex
}
}
**Jdk为我们的生成了一个叫$Proxy0
(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。**通过对这个生成的代理类源码的查看,我们很容易能看出,动态代理实现的具体过程。
我们可以对InvocationHandler
看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。
生成的代理类:$Proxy0 extends Proxy implements Person
,我们看到代理类继承了Proxy
类,所以也就决定了java动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。
生成动态代理的方法很多,不止jdk自带的动态代理这一种,还有CGLIB,Javassist或者ASM。
Jdk的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用jdk代理,这就要用到CGLIB代理了。
CGLIB是针对类来实现的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理
。
需要两个jar包:cglib-nodep-2.2.jar、asm.jar
写一个UserDao,有一个用来保存用户的方法,代码如下:
// 目标对象,没有实现任何接口
public class UserDao {
public void save() {
System.out.println("----已经保存数据!----");
}
}
编写CGLIB代理工厂,第一种方式,代码如下:
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory implements MethodInterceptor {
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
return en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
}
或者第二种方式,代码如下:
/**
* Cglib子类代理工厂
* 对UserDao在内存中动态构建一个子类对象
*/
public class ProxyFactory {
//维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
//给目标对象创建一个代理对象
public Object getProxyInstance() {
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(new MethodInterceptor() {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开始事务...");
//执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事务...");
return returnValue;
}
});
//4.创建子类(代理对象)
return en.create();
}
}
我们测试一下代码:
public class App {
@Test
public void test(){
//目标对象
UserDao target = new UserDao();
//代理对象
UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.save();
}
}
**jdk动态代理 **是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
**cglib动态代理 **是利用ASM开源包,对被代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
ASM: 一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final。
JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口。
CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理。
简单来说,如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理 ;如果目标对象实现了接口,且强制使用cglib代理,则会使用cglib代理。
参考博客链接:Spring5 AOP 默认使用Cglib? 从现象到源码深度分析
在 Spring 5.x 中默认使用的是JDK动态代理
,在SpringBoot2.x 版本中通过AopAutoConfiguration
来自动装配 AOP,会默认使用 Cglib 来实现。
参考博客链接:设计模式(七)门面模式(Facade Pattern 外观模式)
门面模式(Facade Pattern)又称为外观模式,外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
。
从上图中我们可以看出门面模式一共有两种角色:
假如我们每天上班回来很累,我们需要打开电视、打开电脑、打开热水器,我们需要一样一样打开它们,代码示例如下:
// 电视类
public class TV {
public void open(){
System.out.println("打开电视看电影!");
}
}
// 电脑类
public class Computer {
public void open(){
System.out.println("打开电脑放音乐!");
}
}
// 热水器类
public class Heater {
public void open(){
System.out.println("打开热水器烧水!");
}
}
我们打开的时候就需要这样操作:
public static void main(String[] args){
Computer computer = new Computer();
Heater heater = new Heater();
TV tv = new TV();
computer.open();
heater.open();
tv.open();
}
但是如果我们把他们一个统一的开关,来调度所有开关,代码如下:
public class Facade {
private Computer computer;
private Heater heater;
private TV tv;
public Facade() {
computer = new Computer();
heater = new Heater();
tv = new TV();
}
public void open() {
computer.open();
heater.open();
tv.open();
}
}
这样在主函数类只需要使用门面类就可以了。
我们打开的时候就需要这样操作:
public static void main(String[] args) {
Facade facade = new Facade();
facade.open();
}
日志系统应该是最常见的门面模式的应用了,Slf4j 就是一个非常经典的门面。
参考博客链接:设计模式(八)桥梁模式(Bridge)
现需要提供大、中、小3种型号的画笔,能够绘制5种不同颜色,如果使用蜡笔,我们需要准备3 * 5 = 15支蜡笔,也就是说必须准备15个具体的蜡笔类。
而如果使用毛笔的话,只需要3种型号的毛笔,外加5个颜料盒,用3 + 5 = 8个类就可以实现15支蜡笔的功能。实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。
桥梁模式的用意是“将抽象化与实现化脱耦,使得二者可以独立地变化。”
那么什么是脱耦呢?我们先来看一下什么是耦合。两个类之间的关系分为两种,一种是强关联
一种是弱关联
,强关联是在编译时期就已经确定的,无法在运行时期动态的改变的关联;弱关联是可以动态地确定并且可以在运行时期动态改变的关联。显然,Java中继承是强关联而聚合是弱关联。耦合就是两个实体的行为的某种强关联,脱耦就是指将他们之间的强关联解除,但是在桥梁模式中是指将它们之间的强关联改换成弱关联。所以桥梁模式的精髓就是尽量使用聚合/组合来实现弱关联。
这是具有一般性的桥梁模式的类图,我们可以看到桥梁模式一共有四部分组成:
如果将Abstraction
和Implementor
看成两个岸边的话,那么聚合关系就像桥一样将他们连接起来,这就是这个模式为什么叫桥梁模式的原因。
拿上面三个毛笔
和 五个颜料盒
举例子画出类图:
首先编写画笔类代码举例:
// 抽象画笔类
public abstract class BrushPenAbstraction {
// 保留对颜色的引用
protected ImplementorColor imp;
// 每种笔都有自己的实现
public abstract void operationDraw();
public void setImplementor(ImplementorColor imp) {
this.imp = imp;
}
}
// 粗毛笔的实现
public class BigBrushPenRefinedAbstraction extends BrushPenAbstraction{
public void operationDraw() {
System.out.println("Big and "+imp.bepaint()+" drawing!");
}
}
// 细毛笔的实现
public class SmallBrushPenRefinedAbstraction extends BrushPenAbstraction{
public void operationDraw() {
System.out.println("Small and "+imp.bepaint()+" drawing!");
}
}
// 中等粗细毛笔的实现
public class MiddleBrushPenRefinedAbstraction extends BrushPenAbstraction{
public void operationDraw() {
System.out.println("Middle and "+imp.bepaint()+" drawing!");
}
}
再编写颜色类代码举例:
// 颜色的接口
public abstract class ImplementorColor {
public abstract String bepaint();
}
// 红颜色
public class OncreteImplementorRed extends ImplementorColor {
public String bepaint() {
return "red";
}
}
// 蓝颜色
public class OncreteImplementorBule extends ImplementorColor {
public String bepaint() {
return "bule";
}
}
...
我们再来编写测试代码如下:
public static void main(String[] args) {
BrushPenAbstraction brushPen = new BigBrushPenRefinedAbstraction();
ImplementorColor col = new OncreteImplementorRed();
// 设置颜色
brushPen.setImplementor(col);
// 画画
brushPen.operationDraw();
}
java.sql.Driver
是一个接口
public interface Driver {
Connection connect(String url, java.util.Properties info) throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
下面可以有MySQL的Driver,Oracle的Driver,这些就可以当做实现接口类。那么我们现在来看看MySQL中的Driver类:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
特别简短的代码,其实只调用了DriverManager
中的registerDriver()
方法来注册驱动。当驱动注册完成后,我们就会开始调用DriverManager
中的getConnection()
方法了。
public class DriverManager {
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
}
可以看到需要返回的是Connection
对象。在Java中通过Connection
提供给各个数据库一样的操作接口,这里的Connection可以看作抽象类。
可以说我们用来操作不同数据库的方法都是相同的,不过MySQL
有自己的ConnectionImpl
类,同样Oracle
也有对应的实现类。这里Driver
和Connection
之间是通过DriverManager
类进行桥接的,不是像我们上面说的那样用组合关系来进行桥接。
组合模式(CompositePattern)又称为整体-部分模式,是指组合多个对象形成树形结构以表示“整体-部分”的关系的层次结构。组合模式对叶子节点和容器节点的处理具有一致性
。
我们可以看到组合模式一共有三部分组成:
组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
比方说我们一个大型公司,公司里分为不同的部门,不同的部门下有不同的组,对于每一个团体我们都能抽象成一个团队抽象。
首先我们来编写团队的抽象类,示例代码如下:
// 抽象构建角色 - 团队抽象类
public interface Unit {
void add(Unit unit);
void remove(Unit unit);
Unit getChild(int i);
void sendMessage(String message);
}
再来编写公司的实现类,示例代码如下:
// 容器构件角色 - 公司
@Data
@NoArgsConstructor
public class Subsidiary implements Unit {
private List<Unit> units = new ArrayList<>();
private String name;
public Subsidiary(String name) {
this.name = name;
}
@Override
public void add(Unit unit) {
units.add(unit);
}
@Override
public void remove(Unit unit) {
units.remove(unit);
}
@Override
public Unit getChild(int i) {
return units.get(i);
}
@Override
public void sendMessage(String message) {
System.out.println(name + "收到通知");
System.out.println(name + "下发通知");
units.forEach(child -> child.sendMessage(message));
}
}
再来实现以下不同的组,比如说开发组、产品组、测试组等,示例代码如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DevGroup implements Unit {
private String name;
@Override
public void add(Unit unit) {
System.out.println("开发组没有子节点");
}
@Override
public void remove(Unit unit) {
System.out.println("开发组没有子节点");
}
@Override
public Unit getChild(int i) {
System.out.println("开发组没有子节点");
return null;
}
@Override
public void sendMessage(String message) {
System.out.println(name + "收到通知");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestGroup implements Unit {
private String name;
@Override
public void add(Unit unit) {
System.out.println("测试组没有子节点");
}
@Override
public void remove(Unit unit) {
System.out.println("测试组没有子节点");
}
@Override
public Unit getChild(int i) {
System.out.println("测试组没有子节点");
return null;
}
@Override
public void sendMessage(String message) {
System.out.println(name + "收到通知");
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductGroup implements Unit {
private String name;
@Override
public void add(Unit unit) {
System.out.println(name + "没有子节点");
}
@Override
public void remove(Unit unit) {
System.out.println(name + "没有子节点");
}
@Override
public Unit getChild(int i) {
System.out.println(name + "没有子节点");
return null;
}
@Override
public void sendMessage(String message) {
System.out.println(name + "收到通知");
}
}
最后,我们通过编写测试代码理解:
public class Client {
public static void main(String[] args) {
// 总公司
Unit head = new Subsidiary("总公司");
Unit headDev = new DevGroup("总公司开发组");
Unit headTest = new TestGroup("总公司测试组");
Unit headProduct = new ProductGroup("总公司产品组");
head.add(headDev);
head.add(headTest);
head.add(headProduct);
// 北京分公司
Unit bj = new Subsidiary("北京分公司");
Unit bjDev = new DevGroup("北京分公司开发组");
Unit bjTest = new TestGroup("北京分公司测试组");
Unit bjProduct = new ProductGroup("北京分公司产品组");
bj.add(bjDev);
bj.add(bjTest);
bj.add(bjProduct);
head.add(bj);
// 北京朝阳部门
Unit bjcy = new Subsidiary("北京分公司朝阳分部");
Unit bjcyDev = new DevGroup("北京分公司朝阳分部开发组");
Unit bjcyTest = new TestGroup("北京分公司朝阳分部测试组");
Unit bjcyProduct = new ProductGroup("北京分公司朝阳分部产品组");
bjcy.add(bjcyDev);
bjcy.add(bjcyTest);
bjcy.add(bjcyProduct);
bj.add(bjcy);
// 北京西城部门
Unit bjxc = new Subsidiary("北京分公司西城分部");
Unit bjxcDev = new DevGroup("北京分公司西城分部开发组");
Unit bjxcTest = new TestGroup("北京分公司西城分部测试组");
Unit bjxcProduct = new ProductGroup("北京分公司西城分部产品组");
bjxc.add(bjxcDev);
bjxc.add(bjxcTest);
bjxc.add(bjxcProduct);
bj.add(bjxc);
// 上海分公司
Unit sh = new Subsidiary("上海分公司");
Unit shDev = new DevGroup("上海分公司开发组");
Unit shTest = new TestGroup("上海分公司测试组");
Unit shProduct = new ProductGroup("上海分公司产品组");
sh.add(shDev);
sh.add(shTest);
sh.add(shProduct);
head.add(sh);
// 上海浦东部门
Unit shpd = new Subsidiary("上海分公司浦东分部");
Unit shpdDev = new DevGroup("上海分公司浦东分部开发组");
Unit shpdTest = new TestGroup("上海分公司浦东分部测试组");
Unit shpdProduct = new ProductGroup("上海分公司浦东分部产品组");
shpd.add(shpdDev);
shpd.add(shpdTest);
shpd.add(shpdProduct);
sh.add(shpd);
// 上海徐汇部门
Unit shxh = new Subsidiary("上海分公司徐汇分部");
Unit shxhDev = new DevGroup("上海分公司徐汇分部开发组");
Unit shxhTest = new TestGroup("上海分公司徐汇分部测试组");
Unit shxhProduct = new ProductGroup("上海分公司徐汇分部产品组");
shxh.add(shxhDev);
shxh.add(shxhTest);
shxh.add(shxhProduct);
sh.add(shxh);
head.sendMessage("五一放假");
}
}
Jdk中的组合模式
享元模式(Flyweight Pattern) 也叫蝇量模式,又称为轻量级模式,是指运用共享技术有效地支持大量细粒度的对象
。运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。
享元模式中存在以下两种状态
:
举个例子来说明:围棋理论上有361个空位可以放棋子,棋子颜色就是棋子的内部状态,而各个棋子之间的差别就是位置的不同,所以棋子坐标就是棋子的外部状态。
也就是说,**享元模式的本质是分离与共享 : 分离变与不变,并且共享不变。**把一个对象的状态分成内部状态和外部状态,内部状态即是不变的,外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的。
在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)
(用于存储具有相同内部状态的享元对象)。在享元模式中,共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为 细粒度对象。
享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
享元模式的主要角色有如下。
在上面的图中看到,其结构图通常包含可以共享
的部分和不可以共享
的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:单纯享元模式
和复合享元模式
,下面分别对它们进行简单介绍。
这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类。
这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享。
用户访问网站,网站只是一个抽象概念,它的具体实现是内部状态,用户是外部状态,示例代码如下:
// 用户类
@Data
public class User {
private String name;
public User(String name) {
this.name = name;
}
}
// 网页抽象
public abstract class WebSite {
public abstract void use(User user);
}
// 具体网页实现
public class ConcreteWebSite extends WebSite {
private String type = "";
public ConcreteWebSite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站的发布形式为:" + type + ",使用者是" + user.getName());
}
}
我们来创建享元工厂类:
// 享元工厂
public class WebSiteFactory {
private HashMap<String, WebSite> pool = new HashMap<>();
public WebSite getWebSiteCategory(String type) {
if(!pool.containsKey(type)) {
pool.put(type, new ConcreteWebSite(type));
}
return pool.get(type);
}
public int getWebSiteCount() {
return pool.size();
}
}
来进行测试:
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite webSite1 = factory.getWebSiteCategory("新闻");
webSite1.use(new User("tom"));
System.out.println("webSite1地址" + webSite1.hashCode());
WebSite webSite2 = factory.getWebSiteCategory("博客");
webSite2.use(new User("jack"));
System.out.println("webSite2地址" + webSite2.hashCode());
WebSite webSite3 = factory.getWebSiteCategory("博客");
webSite3.use(new User("smith"));
System.out.println("webSite3地址" + webSite3.hashCode());
System.out.println("网站共" + factory.getWebSiteCount()+"类");
}
享元模式是通过减少内存中对象的数量来节省内存空间的,是运用共享技术有效地支持大量 细粒度对象的复用,系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。所以以下几种情形适合采用享元模式:
池化技术,String常量池、数据库连接池、缓冲池、线程池、包装类中的缓存等等都是享元模式的应用。
举一个例子,就拿Integer的缓存来说:
// 首先我们看一下Integer这个类,在使用它的时候非常非常的频繁,那我们看一下Integer有一个方法,叫valueOf
public final class Integer extends Number implements Comparable<Integer>
/**
看一下里面的实现,首先一个if判断,如果大于IntegerCache的最小值,并且小于等于IntegerCache的最大值,
我们直接从Cache里面获取,否则返回一个New出来的Integer对象,经常有一些Integer的判断,通过Integer的各种构造,
然后把构造出来的数字做等等判断,让你们判断这个结果是true还是false。
这里面就要对IntegerCache了解,
这段逻辑也非常清晰,也就是说如果走到return IntegerCache.cache[i + (-IntegerCache.low)];这里,这里并不是
new出来的IntegerCache对象,所以数字没有进入到if里面的时候,都是new出来的Integer对象,他们肯定不是同一个对象,
所以这个数字如果不在这个范围,使用==的时候一定是false。
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
/**
IntegerCache这个类是一个private的静态内部类,最小值是-128,而high在静态块里边,声明了为127,也就是说如果我们
小于等于-128,大于等于127的话,不会在Cache里边,而这个high的值也是可以修改的,
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
我们看一下这一行,这里面从JVM的参数里面获取IntegerCache的最大值,然后再进行一些判断,非常容易理解,然后同理Long
类型里面也是有Cache的
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
再来看一下Long里的缓存:
// 这里面也是有LongCache,我们再看一下他的valueOf方法
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
看一下Tomcat提供的common连接池。
/**
Tomcat提供的common连接池,我们打开这个类GenericObjectPool,
这个很明显就是一个连接池,我们先看一下GenericObjectPoolConfig,连接池里默认的一些配置,也就是说如果这些数字
如果我们不配置的话,也会有,那他的实现很简单,我们随便找一个,GenericKeyedObjectPool,有两个版本,一个是pool2,
一个pool1,我们就看pool2的,那我们注意这个连接池,肯定有拿,肯定有放,我可以从这个连接池里界一个对象出来,
借过来我使用,使用完之后我还要放回去,我们看方法borrowObject()
*/
@Override
public T borrowObject(final K key) throws Exception {
return borrowObject(key, getMaxWaitMillis()); // 这里面又调用这个borrowObject(key, getMaxWaitMillis())方法
}
// 上面回调的 borrowObject(key, getMaxWaitMillis())方法,实现如下
/**
这里面通过ObjectDeque对象,一个双端队列,让他来保持对象池的对象,在最上面把这个连接池声明为一个null,
PooledObject p = null;我们看一下下面怎么用,然后在try里面对他实际的使用,通过Object的双端队列,
来保存连接对象,当然这里还会调用一些factory的,factory.activateObject(key, p);存活着的对象,factory里面
有很多的方法,public interface KeyedPooledObjectFactory,我们来看一下方法,有存活的对象activateObject,
有销毁的对象destroyObject,创建对象destroyObject,钝化对象passivateObject,校验对象validateObject,那很简单,
关注对象的几个状态,首先用这个池对象工厂来创建对象,那将不用的池对象进行钝化,对要使用的对象进行激活,
并且还要对池对象进行激活,把有问题的池对象进行销毁,
*/
public T borrowObject(final K key, final long borrowMaxWaitMillis) throws Exception {
assertOpen(); // 断言是否开启
PooledObject<T> p = null;
// Get local copy of current config so it is consistent for entire
// method execution
final boolean blockWhenExhausted = getBlockWhenExhausted();
boolean create;
final long waitTime = System.currentTimeMillis();
final ObjectDeque<T> objectDeque = register(key);
try {
while (p == null) {
create = false;
p = objectDeque.getIdleObjects().pollFirst();
if (p == null) {
p = create(key);
if (p != null) {
create = true;
}
}
if (blockWhenExhausted) {
if (p == null) {
if (borrowMaxWaitMillis < 0) {
p = objectDeque.getIdleObjects().takeFirst();
} else {
p = objectDeque.getIdleObjects().pollFirst(
borrowMaxWaitMillis, TimeUnit.MILLISECONDS);
}
}
if (p == null) {
throw new NoSuchElementException(
"Timeout waiting for idle object");
}
} else {
if (p == null) {
throw new NoSuchElementException("Pool exhausted");
}
}
if (!p.allocate()) {
p = null;
}
if (p != null) {
try {
factory.activateObject(key, p);
} catch (final Exception e) {
try {
destroy(key, p, true);
} catch (final Exception e1) {
// Ignore - activation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
"Unable to activate object");
nsee.initCause(e);
throw nsee;
}
}
if (p != null && (getTestOnBorrow() || create && getTestOnCreate())) {
boolean validate = false;
Throwable validationThrowable = null;
try {
validate = factory.validateObject(key, p);
} catch (final Throwable t) {
PoolUtils.checkRethrow(t);
validationThrowable = t;
}
if (!validate) {
try {
destroy(key, p, true);
destroyedByBorrowValidationCount.incrementAndGet();
} catch (final Exception e) {
// Ignore - validation failure is more important
}
p = null;
if (create) {
final NoSuchElementException nsee = new NoSuchElementException(
"Unable to validate object");
nsee.initCause(validationThrowable);
throw nsee;
}
}
}
}
}
} finally {
deregister(key);
}
updateStatsBorrow(p, System.currentTimeMillis() - waitTime);
return p.getObject();
}
/**
我们再来看一个方法returnObject,把一个对象返回。
*/
@Override
public void returnObject(final K key, final T obj) {
final ObjectDeque<T> objectDeque = poolMap.get(key);
final PooledObject<T> p = objectDeque.getAllObjects().get(new IdentityWrapper<>(obj));
if (p == null) {
throw new IllegalStateException(
"Returned object not currently part of this pool");
}
synchronized(p) {
final PooledObjectState state = p.getState();
if (state != PooledObjectState.ALLOCATED) {
throw new IllegalStateException(
"Object has already been returned to this pool or is invalid");
}
p.markReturning(); // Keep from being marked abandoned (once GKOP does this)
}
final long activeTime = p.getActiveTimeMillis();
try {
if (getTestOnReturn()) {
if (!factory.validateObject(key, p)) {
try {
destroy(key, p, true);
} catch (final Exception e) {
swallowException(e);
}
if (objectDeque.idleObjects.hasTakeWaiters()) {
try {
addObject(key);
} catch (final Exception e) {
swallowException(e);
}
}
return;
}
}
try {
factory.passivateObject(key, p);
} catch (final Exception e1) {
swallowException(e1);
try {
destroy(key, p, true);
} catch (final Exception e) {
swallowException(e);
}
if (objectDeque.idleObjects.hasTakeWaiters()) {
try {
addObject(key);
} catch (final Exception e) {
swallowException(e);
}
}
return;
}
if (!p.deallocate()) {
throw new IllegalStateException(
"Object has already been returned to this pool");
}
final int maxIdle = getMaxIdlePerKey();
final LinkedBlockingDeque<PooledObject<T>> idleObjects =
objectDeque.getIdleObjects();
if (isClosed() || maxIdle > -1 && maxIdle <= idleObjects.size()) {
try {
destroy(key, p, true);
} catch (final Exception e) {
swallowException(e);
}
} else {
if (getLifo()) {
idleObjects.addFirst(p);
} else {
idleObjects.addLast(p);
}
if (isClosed()) {
// Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear(key);
}
}
} finally {
if (hasBorrowWaiters()) {
reuseCapacity();
}
updateStatsReturn(activeTime);
}
}
// 有一个poolMap,里面传了一个key,打开看一下
/**
poolMap它是一个ConcurrentHashMap,也就是说他折中了HashMap和Hashtable,使用ConcurrentHashMap,
来做这个连接池的Map,非常容易理解,也就是说在这个双端队列上一层,又包装了一层Map,也就是说呢poolMap,
是连接池的对象池,那这些都可以认为是享元模式的一个应用,非常容易理解,希望通过这个过程,对以后有类似的
场景,我们就要考虑是否可以使用享元模式
*/
private final Map<K,ObjectDeque<T>> poolMap =
new ConcurrentHashMap<>();