spring进阶

spring进阶

1.BeanFactory和ApplicationContext

BeanFactory接口是IOC容器要实现的最基本的接口,定义了管理bean的最基本的方法,例如获取实例、基本的判断等。BeanFactory有多个子接口来进一步扩展bean相关的功能。

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

关系图

区别
  • BeanFactory是Spring种比较原始的Factory,它不支持AOP、Web等Spring插件 。而ApplicationContext不仅包含了BeanFactory的所有功能,还支持Spring的各种插件,还以一种面向框架的方式工作以及对上下文进行分层和实现继承。

  • BeanFactory是Spring框架的基础设施,面向Spring本身;而ApplicationContext面向使用Spring的开发者,相比BeanFactory提供了更多面向实际应用的功能,集合所有场合都可以直接使用。

  • 实现ApplicationContext接口的方式会在Spring启动是初始化所有的单例bean,也可以为bean设置lazy-init属性为true。而实现BeanFactory接口的方式不会在启动时创建单例bean,而是第一次getBean()时创建。

    在容器启动时,可以发现spring中存在的配置错误。

  • BeanFactory是不支持国际化功能的,因为BeanFactory没有扩展Spring中MessageResource接口;而由于ApplicationContext扩展了MessageResource接口,因而具有消息处理的能力。

  • ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource,而BeanFactory是没有扩展ResourceLoader。底层资源的访问

2.Spring Bean生命周期

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

  1. 实例化Instantiation

    创建对象

  2. 属性赋值Populate

    给属性注入指

  3. 初始化Initialization

    初始化bean,根据配置为bean添加额外的功能

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

  5. 销毁Destruction

细化

  1. 实例化一个Bean–也就是我们常说的new;

  2. 按照Spring上下文对实例化的Bean进行配置–也就是IOC注入;

  3. (1)如果这个Bean以及实现了BeanNameAware接口,会调用它实现的setBeanName(String)方法,此处传递的就是Spring配置文件中Bean的id值。

    (2)如果这个Bean已经实现了BeanFactoryAware接口,会调用它实现的setBeanFactory,传递的是Spring工厂自身

    (3)如果这个Bean已经实现了ApplicationContextAware接口,会调用setApplicationContext方法,传入Spring上下文(这个方式可以实现2的内容,比2更好,因为ApplicationContext是BeanFactory的子接口,有更多的实现方法)。

    (4)如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessBeforeInitialization方法;(BeanPostProcessor经常被用作是Bean内容的更改,并且由于这个是在Bean初始化结束时调用那个的方法,也可以被应用内存或缓存技术)。

    (5)如果Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法。

    (6)如果这个Bean关联了BeanPostProcessor接口,将会调用postProcessAfterInitialization方法。

    以上工作完成后就可以应用这个Bean了,这个Bean是一个Singleton(单例模式)的,所以一般情况下我们调用同一个id的Bean会是在内容地址相同的实例。

  4. 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用那个其实现的destory()方法。

  5. 最后,如果这个Bean的Spring配置中配置了destory-metnod属性,会自动调用其配置的销毁方法。

img

3.Spring中的Bean是线程安全的吗?

不是线程安全的。

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

Spring的Bean作用域(scope)类型

默认是单例

作用域字符描述
单例singleton整个应用中只会创建一个实例
原型prototype每次注入时都新建一个实例
会话session为每个会话创建一个实例
请求request为每个请求创建一个实例

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

  • 原型Bean

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

  • 单例Bean

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

Bean又分为

  • 有状态就是有数据存储功能(例如包含成员变量)
  • 无状态就是不会保存数据

如果一个单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc的Controller、Service、Dao等只关注方法本身,如果只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制变量,这个是自己线程的工作内存,是安全的。

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

4.Bean循环依赖

怎么造成的?

A对象依赖了B对象,B对象依赖了A对象。

在这里插入图片描述

在这里插入图片描述

发生在哪?
  • 构造器的循环依赖。构造器的循环依赖问题无法解决,只能抛出异常。
  • field属性的循环依赖。
是个问题吗?

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

但是再Spring中循环依赖就是问题了。

因为,在Spring中,一个对象并不实简单new出来了,而是会经过一系列的Bean生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。

Spring内部三级缓存:
  1. 一级缓存

    singletonObjects,用于保存实例化、注入、初始化完成的Bean实例。

  2. 二级缓存

    earlySingletonObjects,用于保存实例化完成的Bean实例。

  3. 三级缓存

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

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

在这里插入图片描述

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

循环依赖

5.面向对象设计原则

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

6.Java设计模式

**概念:**设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。

**目的:**为了提高代码的可重用性、代码的可读性和代码的可靠性。

**本质:**面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

优点:

  • 可以提高程序员的思维能力、编程能力和设计能力。
  • 使程序设计更加标准化、代码编制更加工程化,使开发效率大大提高,从而缩短软件的开发周期。
  • 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性 强。

Java设计模式类型

根据模式是用来完成什么工作来划分:

  • 创建型模式:用于描述“怎样创建对象”,它的主要特点是”将对象的创建与使用分离“。提供了单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。
  • 结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。
  • 行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎么样分配职责。提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。
常用设计模式
单例模式(创新型模式)

**定义:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

特点:

  • 单例类只有一个实例对象;
  • 该单例对象必须由单例类自行创建;
  • 单例类对外提供一个访问该单例的全局访问点;

两种实现形式:

  • 懒汉式单例

    该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法

    时才去创建这个单例 。

    实例

    线程不安全,不可用
    public class Singleton {
    
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
    }
    
    //可用
    public class Singleton {
    
        private static Singleton instance = null;
    
        private Singleton() {
        }
    
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    
    }
    
  • 饿汉式单例

    该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之

    前单例已经存在了。

    //可用
    public class Singleton {
    
        private final static Singleton INSTANCE = new Singleton();
        
        private Singleton(){}
    
        public static Singleton getInstance(){
            return INSTANCE;
        }
    
    }
    
工厂模式(创新型模式)

**定义:**定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。

按照实际业务场景划分,工厂模式有3中不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式

  • 简单工厂

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

    简单工厂模式中创建实例的方法通常为静态方法

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

    • 简单工厂:是核心,负责实现创建所有的实例的内部逻辑。工厂类的创建产品类的方法可用被外界直接调用,创建所需的产品对象。
    • 抽象产品:是简单工厂创建的所有对象的父类。负责描述所有实例共有的公共接口。
    • 具体产品:是创建目标。

    实例

    //Phone类:手机标准规范类(AbstractProduct)
    public interface Phone {
        void make();
    }
    
    //MiPhone类:制造小米手机(Product1)
    public class MiPhone implements Phone {
        public MiPhone() {
            this.make();
        }
        @Override
        public void make() {
            // TODO Auto-generated method stub
            System.out.println("make xiaomi phone!");
        }
    }
    
    
    //IPhone类:制造苹果手机(Product2)
    public class IPhone implements Phone {
        public IPhone() {
            this.make();
        }
        @Override
        public void make() {
            // TODO Auto-generated method stub
            System.out.println("make iphone!");
        }
    }
    
    
    //PhoneFactory类:手机代工厂(Factory)
    public class PhoneFactory {
        public Phone makePhone(String phoneType) {
            if(phoneType.equalsIgnoreCase("MiPhone")){
                return new MiPhone();
            }
            else if(phoneType.equalsIgnoreCase("iPhone")) {
                return new IPhone();
            }
            return null;
        }
    }
    
    //演示:
    public class Demo {
       public static void main(String[] arg) {
           PhoneFactory factory = new PhoneFactory();
           Phone miPhone = factory.makePhone("MiPhone");            // make xiaomi phone!
           IPhone iPhone = (IPhone)factory.makePhone("iPhone");    // make iphone!
       }
    }
    
  • 工厂方法

  • 抽象工厂

代理模式(结构型模式)

**特征:**代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。

**概念:**我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。

优点:

  • 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
  • 代理对象可以扩展目标对象的功能;
  • 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;

结构:

  • 抽象主题类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  • 真实主题类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理实现可以分为静态代理动态代理

  • 静态代理

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

  • 动态代理

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

    jdk代理

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

    其步骤如下:

    1. 编写一个委托类的接口。即静态代理的
    2. 实现一个真正的委托类,即静态代理的
    3. 创建一个动态代理类,实现InvocationHandler接口,并重写该invoke方法。
    4. 在测试类中,生成动态代理的对象。

    Cglib代理

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

    子类代理实现方法:

    1. 引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-xxx.jar即可。
    2. 引入功能包后,就可以在内存中动态构建子类
    3. 代理类不能为final,否则报错
    4. 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

    JDK和CGLib总结:

    1. CGLib创建的动态代理对象比JDK创建的动态代理使用的是Java反射技术实现,生成类的过程比较高效,对象的性能更高,但是CGLib创建代理对象时所花费的时间比JDK多。所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之使用JDK方式要更为合适一些。
    2. JDK动态代理只能对实现了接口的类生成代理,而不能针对类
    3. CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用字节码实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final
    4. JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口。
    5. CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承,但是针对接口编程的环境下推荐使用JDK的代理

    动态代理总结:虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持接口代理的桎梏,因为它的设计注定了这个遗憾。

7.注解

什么是注解?

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

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

内置的注解
  • @Override

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

  • @Deprecated

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

  • @SuppressWarnings

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

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

  • @Retention**(重点)**

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

    定义了该注解被保留的时间长短:某些注解仅出现在源代码中,而被编译器丢弃;而另一些在class被编译在class文件中;编译在 class 文件中的注解可能会被虚拟机忽略,而另一些在 class 被装载时将被读取。

    使用这个 meta-Annotation 可以对注解的“生命周期”限制。**作用:**标识需要在什么级别保存该注释信息,用于描述注解的声明周期。

    取值有:

    • SOURCE:在源文件中有效(即源文件保留)
    • CLASS:在class文件中有效(即class保留)
    • RUNTIME:在运行时有效(即运行时保留)
  • @Documented

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

  • @Target**(重点)**

    用于描述注解的使用范围。

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

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

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

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

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

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

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

  • @Inherited

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

  • @SafeVarargs

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

  • @FunctionalInterface

    Java8开始支持,标识一个匿名函数或函数式接口。

  • @Repeatable

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

自定义注释

  1. 创建一个Annotaion

    public @interface NotNull { 
        String message() default ""; 
    }
    
  2. 使用元注释修饰

    @Target(ElementType.FIELD) 
    @Retention(RetentionPolicy.RUNTIME) 
    public @interface NotNull { 
        String message() default ""; 
    }
    
  3. 使用注释

    @NotNull 
    private String name;
    
  4. 测试

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, Exception { 
        User user = new User(); 
        //user.setName("jim");
        Field[] fields = user.getClass().getDeclaredFields(); 
        for (Field field : fields) { 
            NotNull notNull = field.getAnnotation(NotNull.class); 
            if (notNull != null) { 
                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()); 
                } 
            } 
        } 
    } 
    /** 
    * 把一个字符串的第一个字母大写
    */ 
    private static String getMethodName(String fildeName) throws Exception{
        byte[] items = fildeName.getBytes(); 
        items[0] = (byte) ((char) items[0] - 'a' + 'A'); 
        return new String(items);
    }
    
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值