目录
Spring
BeanFactory和ApplicationContext
BeanFactory
接口是 IOC 容器要实现的最基础的接口,定义了管理 bean 的最基本的方法,例如获取实例、类型、基本的判断等
ApplicationContext
也间接继承了BeanFactory接口,在此基础上扩展了功能
- BeanFactory是Spring基础设施,面向Spring框架本身的,在使用对象时才创建
- ApplicationContext是面向应用的,增加了功能(支持AOP和事务),适用与Web应用程序,在服务器启动时就创建
ApplicationContext的具体实现类有ClassPathXmlApplicationContext
:它可以加载类路径的配置文件,要求配置文件必须在类路径下,如果不在则加载不了
FactoryBean
实现了FactoryBean的Bean是一类叫做factory的bean。
Spring会在使用getBean()获取bean时,调用其通过getObject()重写的方法来创建bean
SpringBean的生命周期
spring Bean的作用域:scope的值
- singleton:单例(默认),对象在工厂初始化时创建
- prototype:原型(多例),对象在工厂初始化后创建即获取对象时创建
- request:在Web环境下,同一次请求创建一个实例
- session:在Web环境下,同一次会话创建一个实例
- globalSession
Spring中的Bean可能经过5个阶段
- 实例化:创建一个原始的对象,例如new对象,通过反射机制实现的(框架可以读到类名)
- 属性赋值:为对象的属性进行赋值 在UserService中注入UserDao userDao
- 初始化
- 如果实现了
BeanNameAware
、BeanFactoryAware
、ApplicationContextAware
这些接口,就要去执行这些接口的函数,来初始化我们的对象例如接口,或配置了自定义的初始化方法< bean init-method="init">
- 对类进行功能的提升:如果此类需要事务的增强(aop),就要在此处为bean添加功能
- 如果实现了
- 将完整 bean 对象创建好,放入到容器中,可以使用
- 销毁 如果 Bean 实现 DisposableBean 执行 destroy
Spring中的bean是线程安全的吗?
要看Spring 的 bean 作用域(scope)类型
- singleton(单例):多个线程共享一个对象,不一定是线程安全的
- prototype(原型):每次使用时都会创建一个对象,是线程安全的
有状态 / 无状态
- 有状态bean(可存储数据):线程不安全
- 无状态bean(不会保存数据):线程安全
servlet是线程安全的吗? 不是
servlet对象是在服务器启动时由服务器创建,只会有一个,所以是单例的
Bean循环依赖
就是 A 对象的创建依赖了 B 对象,B 对象的创建依赖了 A 对象
public class Admin{
@Autowired
Menu menu; //此时menu对象还可能没有被创建
}
public class Menu{
@Autowired
Admin admin;
}
如果不考虑 Spring,问题不存在。但是,在 Spring 中循环依赖就是一个问题了,为什么?
因为spring在创建对象时要为属性自动注入值,注入时就需要查找所依赖的对象,如果此时依赖的对象还可能没有被创建,就会出现问题
解决方法
spring提供了一个三级缓存机制
每一个缓存可以理解为一个map容器,把不同的对象做一个临时存储
- 一级缓存,用于保存实例化、注入、初始化完成的 bean 实例
- 二级缓存,用于保存实例化完成的原始对象(不需要注入值)
- 三级缓存,假如这个类有需要增强的功能,就要把这个把成品对象继续放在三级缓存中去增强功能。(用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象)
解决过程
- A,B 循环依赖,先初始化 A,先暴露一个半成品 A(二级缓存)
- 再去初始化依赖的 B,初始化 B 时如果发现 B 依赖 A,也就是循环依赖,就注入半成品 A,之后初始化完毕 B
- 再回到 A 的初始化过程时就解决了循环依赖
在这里只需要一个 Map能缓存半成品 A 就行了,也就是二级缓存就够了,但是这个二级缓存存的是 Bean对象,如果这个对象存在代理,那应该注入的是代理,而不是 Bean,此时二级缓存无法及缓存 Bean,又缓存代理,因此三级缓存做到了缓存工厂 ,也就是生成代理,这我的理解:总结起来:二级缓存就能解决缓存依赖,三级缓存解决的是代理
Servlet 的过滤器与 Spring 拦截器区别
过滤器实现依赖于tomcat,请求会先到达过滤器,然后进入servlet
拦截器是spring框架内部封装的,请求先到达servlet,根据映射地址,去匹配拦截器,最终找到controller控制器
底层的实现原理不同
- 过滤器 是基于函数回调的
- 拦截器 则是基于 Java 的反射机制(动态代理)实现的
使用范围不同
- 过滤器 Filter 的使用要依赖于 Tomcat 等容器,导致它只能在 web 程序中使用
触发时机不同
- 过滤器 是在请求进入容器后,但在进入 servlet 之前进行预处理
- 拦截器 是在请求进入 servlet 后,在进入 Controller 之前进行预处理的
拦截的请求范围不同
- 过滤器几乎可以对所有进入容器的请求起作用
- 拦截器只会对 Controller 中请求或访问 static 目录下的资源请求起作用
spring中用到了哪些设计模式
- 单例模式:spring提供了一个获取对象的全局访问点BeanFactory,这个BeanFactory就是单例的
- 简单工厂:根据传入一个唯一的标识来获得Bean对象,这个标识可以是类型或者名称
- 动态代理:spring的AOP;在织入切面也就是增强方法时,AOP容器会为目标对象创建动态的创建一个代理 对象
- 工厂方法:实现了FactoryBean接口的bean是一类叫做factory的bean;其特点是,spring会在使用getBean()调 用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个 bean.getOjbect()方法的返回值
建模语言UML
类图:以可视化图形方式来表示类的结构和类与类的关系,方便理解
类
- +:公有的 Public
- -:私有的 Private
- #:受保护的 Protected
- ~:朋友 Friendly
接口
在 UML 中,接口使用一个带有名称的小圆圈来进行表示。图形类接口的 UML 表示
类图
简化对软件系统的理解,它是系统分析与设计阶段的重要产物,也是系统编码与测试的重要模型依据
类和类之间的关系
依赖关系
在一个类中某个方法把另一个类当参数使用,临时性的
关联关系
在一个类中把另一个类当做属性
聚合关系
强关联关系,是整体和部分之间的关系
组合关系
更强烈的关联关系,一旦整体对象不存在,部分对象也将不存在
泛化关系
类继承类
实现关系
类实现接口
面向对象的设计原则
时提高一个软件系统的可维护性和可复用性
开闭原则
对扩展开放,对修改关闭
在程序需要扩展的时候,不去修改原有的代码,实现一个热插拔效果(USB接口)。
比如将功能抽象,使用抽象类和接口,具体的实现可以扩展子类,提高复用性和可维护性
里氏替换原则
在任何父类出现的地方都可以用它的子类来替换,且不影响功能(多态)
子类可以扩展父类的功能,但不去改变父类原有的功能;也就是子类继承父类时,可以新添加功能,尽力不去重写父类方法。
如果重写父类的方法,继承体系的可用性就会变差,特别是多态使用频繁时程序出错概率大
依赖倒置
高展模块不应该依赖底层模块,两者都应该依赖其抽象;
抽象不应该依赖细节,细节应该依赖抽象
比如组装电脑需要cpu、内存、硬盘等配件;我们在实现电脑类的时候,依赖是的抽象(CPU),而不是具体的类(intelcpu);
- 在顶层不适合定义太多的功能,底层实现有可能用不到
- 在中间设计接口,抽象类去扩展功能,底层按自己的需要去实现即可
单一职责
一个类只负责一个功能领域中的相应职责
便于理解,提高代码可读性
接口隔离原则
使用多个专门的接口,一个接口只做一件事,而不使用单一的总接口,客户端不应该被迫依赖它不使用的方法
迪米特原则
一个对象应当对其他对象尽可能少的了解,降低耦合
使用代理的思想,不直接去对其他类进行访问;例如明星、经纪人、公司粉丝关系;
组合/聚合复用原则
优先使用组合,使系统更灵话,其次才考虑继承,达到复用的目的
电脑类,cpu类,内存类,硬盘类,电脑需要包含其他三个,就把三个类加入到自己的属性中,而不是继承
Java设计模式
前辈们在长期开发过程中,为解决某一类问题,总结出的一种较为优化的代码设计结构,提高程序代码的复用性、扩展性、稳定性
设计模式分类
- 创建型模式:主要描述如何创建对象(单例、原型、工厂方法、抽象工厂、建造者)
- 结构型模式:按照某种布局建造复杂的结构(代理、适配器、桥接、装饰、外观、享元、组合)
- 行为模式:多对象协作完成任务(模板方法、职责链、观察者、中介者、访问者、迭代器、解释器、备忘录、策略、命令、状态)
单例模式
在程序中只允许有一个对象
- 节省内存资源
- 保证数据内容的一致性
要创建对象
- 将构造方法私有化
- 只能在本类中创建,这样我们就可以控制数量
- 向外界提供一个公共的访问方法
饿汉式单例
在类加载时就会创建单例对象,不会出现线程安全问题
public class Singleton {
//创建 Singleton 的一个对象
private static Singleton instance = new Singleton();
//让构造函数为 private
private Singleton(){}
//获取唯一可用的对象
public static Singleton getInstance(){
return instance;
}
}
懒汉式单例
在类加载的时候不会去创建对象,在第一次访问时才去创建,避免资源浪费
懒汉式单例有线程安全问题,要加锁处理;
对于直接在方法加锁,所有使用该对象的时候都需要加锁获取,而大多数判断结果都是这个对象已经生成,直接那去用就行,所以给方法加锁极大影响效率
volatile + 双重检索:先通过不加锁判断是否存在实例,如果有就直接拿;如果没有,加锁去生成实例,但是拿到锁后不能直接生成对象,需要在判断一次,这个成员又是被volatile修饰的具有可见性和有序性。就能保证仅第一个线程生成对象
可见性:保证第一个线程生成实例后,后面的线程能够可见,再第二重检索判断的时候就不会再生成实例
有序性:new 这个单例实例需要三条指令。1 申请内存空间 2 调用构造方法 3 把对象的地址赋给引用变量。。但是操作系统的指令重排会 将23反过来,把半成品对象赋给了引用;其他的新来的线程可能拿到这个半成品对象;所以应该禁止指令重排
public class Singleton {
//因为大多数都是判断完发现不为null直接返回对象的,直接给方法加锁影响性能;双重检索锁的区域小
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
//先判断是否有实例,有就不用加锁直接获取该实例;没有再加锁去生成实例保证唯一
if (singleton == null) {
//加锁让一个线程进入
synchronized (Singleton.class) {
//第二次检索
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
工厂方法模式
批量使用对象,将创建与使用分离
一类工厂只负责一类产品;
三种角色
- 抽象产品:接口/抽象类 定义
- 具体产品:实现接口/抽象类的具体实现类
- 工厂:负责生产对象
以抽象表示具体(多态)
/**
* 抽象汽车接口
*/
public interface Car {
void show();
}
/**
* 具体汽车类
*/
public class DZCar implements Car{
@Override
public void show() {
System.out.println("DZCar");
}
}
/**
* 具体汽车类
*/
public class FTCar implements Car{
@Override
public void show() {
System.out.println("FTCar");
}
}
/**
* 汽车工厂:复制生产汽车对象
*/
public class CarFactory {
public static Car createCar(String className) {
if(className==null){
return null;
}
try {
return (Car) Class.forName(className).newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
}
}
public class Test {
public static void main(String[] args) {
DZCar dzCar = (DZCar) CarFactory.createCar("com.ff.advance.test.DZCar");
dzCar.show();
FTCar ftCar = (FTCar) CarFactory.createCar("com.ff.advance.test.FTCar");
ftCar.show();
}
}
---------------------------------------
DZCar
FTCar
抽象工厂模式
围绕一个超级工厂创建其他工厂,超级工厂是其他工厂的工厂
产品族
:一个品牌下的所有产品类型(华为品牌下的电脑、手机)
产品等级
:多个品牌下的同种产品(华为、小米品牌下的手机)
- 定义电脑工产品口和手机产品接口
- 超级工厂接口聚合这两个接口
- 定义华为工厂和小米工厂的实现类分别实现超级工厂接口
- 客户端通过工厂接口创建各品牌的工厂(华为工厂),通过华为工厂类创建自己的产品
拓展产品族:添加一个Pad产品种类,要写一个Pad接口,让超级工厂集成他,然后再创建华为Pad和小米Pad两个具体产品类。很不友好
拓展产品等级:添加一个苹果工厂,再加苹果电脑和苹果手机实现类。友好
代理模式
当用户不能或不想直接去访问目标对象,可以通过一个中间代理商在用户和目标之间起到了一个中间作用
例如我去买车,依照迪米特原则,我应该更少的直接去了解汽车厂,而是去找4S店,由中介来了解双方的信息,做到牵线的作用
- 防止直接去访问目标对象,保护了目标对象
- 可以对目标的功能进行扩展
- 将用户和目标对象分离,降低耦合度
结构
- 抽象主题类:通过接口定义真实主题的规范 (汽车接口)
- 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。(奥迪汽车)
- 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能(4S店)
静态代理
一般使用于关系是固定的,代理某类事务就必须实现其接口
如果要代理多个目标对象,就需要实现更多的接口,后期维护比较麻烦,违背开闭原则
/**
Dao接口,定义保存功能
*/
public interface BaseDao {
void save();
}
---------------------------------------------------
/**
实际功能实现类
*/
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();
}
}
--------------------------------------------------
before
UserDaoImpl:save()
after
动态代理
动态代理中,代理类并不是在 Java 代码中实现,而是在运行时期生成,相比静态代理,动态代理可以很方便的对委托类的方法进行统一处理,如添加方法调用次数、添加日志功能等等
jdk 代理
实现原理:java反射机制,动态获取目标类中代理的方法,动态生成代理对象,jdk代理要求目标类必须实现其接口,而代理类不需要实现那些接口,代理扩展性好
- 必须定义一个功能接口(抽象主题类)
- 写一个真实主题类(被代理的类)
- 创建一个动态代理类,生成这个类要传入被代理的类的实例,实现
InvocationHandler接口
,并重写该invoke()
,这个invoke方法会获取我们要执行的方法,并对其进行增强 - 生成代理对象,然后执行一个方法的时候就去执行invoke方法
/**
* 抽象主题类
*/
public interface Car {
void show();
}
----------------------------------------------
/**
* 真实主题类(被代理的类)
*/
public class BZCar implements Car {
@Override
public void show() {
System.out.println("BZCar show");
}
}
----------------------------------------------
/**
* 代理类
*/
public class DtProxy implements InvocationHandler {
//被代理类实例
Object object;
public DtProxy(Object object) {
this.object = object;
}
/**
* 通过传入被代理对象 需要执行的方法,在此方法上进行增强
* @param method 获取需要代理的方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("pre show");
Object invoke = method.invoke(object);
System.out.println("over show");
return invoke;
}
}
---------------------------------------------
public class Test {
public static void main(String[] args) {
BZCar bzCar = new BZCar();
//获取我们写的代理对象
InvocationHandler dtProxy = new DtProxy(bzCar);
//真正生成动态代理对象
Car car = (Car) Proxy.newProxyInstance(DtProxy.class.getClassLoader(),bzCar.getClass().getInterfaces(), dtProxy);
//实际执行的是代理类的invoke方法
car.show();
}
}
Cglib 代理
使用动态字节码技术
可以在运行中为目标类动态的生成一个子类,进行方法拦截,从而增强功能
不能代理final修饰的类,目标类可以不实现任何接口
其他设计模式
原型模式
使用复制(克隆)的方式来创建对象,省去了对象创建的一系列流程,提高了效率
模板方法模式
基于继承进行代码复用
做几件事情的步骤差不多,只有细节有所不同;在一个方法中定义一个骨架,将一些步骤由子类实现;可以在不改变算法结构的情况下重新定义某些步骤。
AQS提供模板方法,具体的实现由子类继承重写
责任链模式
请求要访问资源要经过多个对象组成的责任链
Sentinel内部创建了一个责任链,也就是不同的Slot对象,每个Slot对象负责不同的功能,只有通过这些对象的校验,才能真正访问资源
观察者模式
对象之间有依赖关系,一个对象的状态改变,它的所有依赖者都会收到通知
Spring中的ApplicationListener
事件监听,实现ApplicationContext的事件处理
注解
Java 注解(Annotation)又称 Java 标注,为类,方法,属性,包进行标注
可以被编译到字节码文件中 ,运行时可以通过反射机制获取到注解标签.
内置的注解
java中已经定义好的注解标记
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告
元注解 (注解的注解)
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入 class 文件中,或者是在 运行时可以通过反射访问
- SOURCE:在源文件中有效(即源文件保留)
- CLASS:在 class 文件中有效(即 class 保留)
- RUNTIME:在运行时有效(即运行时保留)
- @Target - 标记这个注解应该是哪种 Java 成员
- ElementType.TYPE 可以应用于类的任何元素
- ElementType.CONSTRUCTOR 可以应用于构造函数
- ElementType.FIELD 可以应用于字段或属性
- ElementType.LOCAL_VARIABLE 可以应用于局部变量
- ElementType.METHOD 可以应用于方法级注释
- ElementType.PACKAGE 可以应用于包声明
- ElementType.PARAMETER 可以应用于方法的参数
自定义注解标签
@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;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
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();
for (Field field : fields) {
//获得此属性上 名为NotNull 的注解标签
NotNull notNull = field.getAnnotation(NotNull.class);
if (notNull != null) {
//通过属性名获取到属性的get方法
Method m = user.getClass().getMethod("get" + getMethodName(field.getName()));
//获得属性的值
Object obj=m.invoke(user);
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);
}
}
---------------------------------------------------------
name长度不能小于3
对象克隆
复制对象,将一个对象的数据复制到另一个对象中去
实现方式
1.在 Java 语言中,通过覆盖 Object 类的 clone()方法可以实现浅克隆。
2.在 spring 框架中提供 BeanUtils.copyProperties(source,target)
浅克隆
如果一个对象中关联了其他的引用变量, 浅克隆时,只会将关联的对象的引用地址复制出来,并没有创建一个新的对象.
深克隆
如果一个对象中关联了其他的引用变量, 深克隆时,将此对象中所关联的对象也会进行克隆操作,也就是会创建一个新的关联对象
如何实现深克隆
-
多层次克隆 在关联的类中继续克隆
public class Address implements Cloneable{ String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } @Override protected Address clone() throws CloneNotSupportedException { return (Address)super.clone(); } } public class Person implements Cloneable{ int num; String name; Address address; public Person() { } public Person(int num, String name) { this.num = num; this.name = name; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override protected Person clone() throws CloneNotSupportedException { Person person = (Person)super.clone(); person.address = (Address)address.clone(); //深度复制 联同person中关联的对象也一同克隆. return person; } @Override public String toString() { return "Person{" + "num=" + num + ", name='" + name + '\'' + ", address=" + address + '}'; } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { Address address = new Address(); address.setAddress("汉中"); Person p1 = new Person(100,"jim"); p1.setAddress(address); Person p2 =p1.clone(); p2.setName("tom"); address.setAddress("西安"); System.out.println(p1); System.out.println(p2); } } ----------------------------------------- Person{num=100, name='jim', address=Address{address='西安'}} Person{num=100, name='tom', address=Address{address='汉中'}}
-
使用序列化和反序列化的方式实现(反序列化也是生成对象的一种方式)
public class Address implements Serializable { String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } } public class Person implements Serializable { int num; String name; Address address; public Person() { } public Person(int num, String name) { this.num = num; this.name = name; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } /** * 自定义克隆方法 * @return */ public Person myclone() { Person person = null; try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); person = (Person) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return person; } @Override public String toString() { return "Person{" + "num=" + num + ", name='" + name + '\'' + ", address=" + address + '}'; } } public class Test { public static void main(String[] args) throws CloneNotSupportedException { Address address = new Address(); address.setAddress("汉中"); Person p1 = new Person(100,"jim"); p1.setAddress(address); Person p2 =p1.myclone(); p2.setName("tom"); address.setAddress("西安"); System.out.println(p1); System.out.println(p2); } } ------------------------------------------------------------------ Person{num=100, name='jim', address=Address{address='西安'}} Person{num=100, name='tom', address=Address{address='汉中'}}