Spring (Bean, IoC, AOP, SpringMVC)

Spring

Spring框架是一种分层架构,它包含了一系列的功能,大概由20种模块组成。 这些模块分为:
核心容器(Core Container),
数据访问/集成(Data Access/Integration),
Web,
AOP,
工具(Instrumentation),
消息(Messaging),
测试用例(Test)

在这里插入图片描述


1. 核心容器

包含模块spring-core, spring-beans, spring-context, spring-context-support,spring-expression:
spring-core : Spring框架基本的核心工具类;
spring-beans : 包含访问配置文件、创建和管理bean以及进行IoC/DI操作的相关类. BeanFactory;
spring-context: 构建与Core和Beans之上,继承了Beans的特性,扩展添加了国际化、时间传播、资源加载和对Context的创建和支持。ApplicationContext;
spring-expression: 提供 一个强大的表达式语言用于在运行时查询和操作对象,该语言支持设置/获取属性值,属性的分配,方法的调用,访问数组上下文、容器和索引器、逻辑和算是运算符、命名变量以及从Spring的容器中根据名称检索对象。

Spring源码分析之BeanFactory体系结构
Spring IOC 容器预启动流程源码探析
Spring源码分析之BeanFactoryPostProcessor调用过程详解

Spring(https://spring.io/) 系列目录

1.1 Spring-beans

Spring 官方文档对 bean 的解释是:
In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.
Spring 中,构成应用程序主干并由Spring IoC容器管理的对象称为beanbean是一个由Spring IoC容器实例化、组装和管理对象
spring bean是什么(转)

bean规范如下:
1、所有属性为private
2、提供默认构造方法
3、提供getter和setter
4、实现serializable接口
1.1.1 Bean 的配置

Bean配置信息定义了Bean的实现及依赖关系。

三种Bean的配置方案:
1、在 XML中进行显示配置;
2、使用 JavaConfig进行显示配置;
3、隐式的Bean发现机制和 自动装配

很多场景下通过组件扫描和自动装配实现Spring的自动化更为推荐,但是有时候行不通。比如引用第三方组件,没办法在它的类上添加@Component@Autowired。所以就需要JavaConfig或者XML配置

在这里插入图片描述

Spring Bean详细讲解 什么是Bean?
spring中bean配置和bean注入

1.1.1.1 自动装配

一般使用@Autowired注解自动装配Bean,要将类表示成可用于@Autowired注解自动装配的Bean类;

采用一下注解可实现:
@Component:通用的注解,可标注任意类为 Spring 的Bean。如果一个Bean不知道属于哪一个层(逻辑上的分层),可以该注解。
@Repository:持久层( Dao层)名主要用于数据库相关操作;
@Service:对应服务层,主要设计一些复杂的逻辑(一般会调用Dao层);
@Controller:对应 Spring MVC控制层,主要用户接受用户请求并调用 Service层返回数据给前端页面。

<context:component-scan>:扫描指定包,如果发现有指定注解,那么该类将由Spring进行管理。

1.1.1.2 JavaConfig

当需要将第三方库中的组件装配到应用中时,是没有办法在他的类上添加@Component或者@Autowired的,所以可以采用JavaConfigxml,先来看JavaConfig配置。

一般遵循两个原则:
1、 JavaConfig是配置相关代码,不含任何逻辑代码;
2、通常会将 JavaConfig放到单独的包中。
2个关键注解
1、 @Configuration :用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被 @Bean注解的方法,这些方法将会被 AnnotationConfigApplicationContextAnnotationConfigWebApplicationContext类进行扫描,并用于构建 bean定义,初始化 Spring容器。
2、 @Bean:用于告诉方法,产生一个 Bean对象,然后这个 Bean对象交给 Spring管理。(和 xml配置中的 bean标签的作用是一样的)
public class SgtPeppers implements CompactDisc {
    private String title = "White Horse";
    private String artist = "Talyor Swift";
    @Override
    public void play() {
        System.out.println("Playing " + title + " by " + artist);
    }
}
@Configuration // 这是一个配置类
public class CDPlayerConfig {
    @Bean // 下面的方法会产生一个Bean对象
    public CompactDisc sgtPeppers(){
        return new SgtPeppers();
    }
    @Bean // 参数注入
    public CDPlayer cdplayer(CompactDisc compactDisc){
        return new CDPlayer(compactDisc);
    }
    @Bean // 无参数注入
    public CDPlayer cdplayer(){
        return new CDPlayer(sgtPeppers());
    }
}

<context:component-scan>:扫描指定包,如果发现有指定注解,那么该类将由Spring进行管理。

spring使用JavaConfig进行配置

1.1.1.3 xml 配置
方式:
构造器注入
set方式注入
扩展方式注入


@Component@Bean的区别:
1、都是使用注解定义 Bean的方式
2、一个应用于类,一个应用于方法
3、 @Component用于自动检测和使用类路径扫描自动配置 Bean@Bean用于显式声明单个 Bean,而不是让 Spring自动执行它。
4、 @Bean可以对第三方的类进行配置; @Component不能对第三方的类进行自动配置。
1.1.2 bean 的生命周期

在这里插入图片描述
生命周期:

对象创建:
1、从 Bean的配置中获取 BeanDefinition,进行实例化;
2、设置 Bean的属性;
3、 Bean级生命周期接口( BeanNameAware, BeanFactoryAware, ApplicationContextAware)的方法;
4、 BeanPostProcessorpostProcessorBeforeInitialization()
5、 InitializingBean接口的 afterPropertiesSet()
6、自定义初始化方法 init-method或者 @PostConstruct 标注的方法;
7、 BeanPostProcessorpostProcessorAfterInitialization()
8、 Bean创建完毕。
Bean的销毁:
1、 DiposibleBean接口的 destory()
2、 自定义的 destory方法或者 @PreDestory 标注的方法。

Spring中Bean的生命周期是怎样的? - MOBIN-F的回答 - 知乎
Spring Bean的生命周期(实例讲解)

1.1.2.1 各种接口方法分类
Bean的完整生命周期会经历各种方法调用,这些方法可以划分为以下几类:
1、Bean自身的方法:这个包括了Bean本身调用的方法(如:构造方法,set方法)和通过配置文件中 <bean>init-methoddestroy-method指定的方法
2、Bean级生命周期接口方法:这个包括了 BeanNameAwareBeanFactoryAwareInitializingBeanDiposableBean这些接口的方法
3、容器级生命周期接口方法:这个包括了 BeanPostProcessorInstantiationAwareBeanPostProcessor 这两个接口实现,一般称它们的实现类为“后处理器”。
4、工厂后处理器接口方法:这个包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。

BeanNameAware接口是为了让自身Bean能够感知到,并可以获取到自身在Spring容器中的id属性。
同理,其他的Aware接口也是为了能够感知到自身的一些属性。比如实现了ApplicationContextAware接口的类,能够获取到ApplicationContext,实现了BeanFactoryAware接口的类,能够获取到BeanFactory对象。
Spring中的…Aware接口

1.1.3 bean 作用域与线程安全

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

bean 作用域
1、 singleton:单例, 默认作用域。
2、 prototype:原型,每次创建一个新对象。
3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
5、global-session:全局会话,所有会话共享一个实例。

在这里插入图片描述
bean对象是默认单例的,那么Spring中单例Bean的线程安全问题如何处理?

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。

  1. Bean对象中尽量不使用可变的成员变量;
  2. 类中定义一个ThreadLocal成员变量

Spring 单例Bean和Java 单例模式的区别?

  1. Spring的的单例是基于BeanFactory也就是spring容器,单例Bean在此Spring容器内是单个的;
  2. Java的单例是基于JVM,每个JVM内一个单例。

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


1.2 IoC(Inverse of Control)

控制反转 (Inverse of Control, IoC):
将创建对象的权利交给 IoC容器。
那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件(见上一节Bean的配置)。
依赖注入(Dependency Injection, DI):
就是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它。

在这里插入图片描述

为了不重复造轮子,一些基础知识可以先阅读一下以下三篇文章:IoC : 工厂模式 + 反射 + 配置文件读取Web项目使用IoC的优势IoC的依赖倒置

IoC :
依赖倒置原则六大设计原则之一
工厂模式
配置文件读取
反射

总结来说 IoC 就是:基于依赖倒置原则,结合工厂模式,从配置文件中读取Bean配置信息并通过反射构建Bean;在之后对Bean进行管理。(自己总结的!!!)
将创建对象的权利从程序猿手中拿走,并递给了IoC Container

以上只是对IoC的大致思想进行了总结,具体Spring IoC实现更加复杂。

1.2.1 Spring IoC 体系结构
  1. Bean是一个由Spring IoC容器实例化、组装和管理对象
  2. Spring中,Bean是使用BeanDefinition描述的。

那么BeanDefinition从加载、解析、处理、注册到BeanFactory的过程是怎样的呢?

注册:通过 IoC 容器内部维护的一个Map 来保存得到的 BeanDefinition 的过程;

1.2.1.1 BeanFactory

Spring Bean 的创建是依靠典型的工厂模式,这一系列的 Bean 工厂(也即 IoC 容器)为开发者管理对象间的依赖关系提供了很多便利和基础服务,在 Spring 中有许多的 IoC 容器的实现供用户选择和使用。

BeanFactory作为最顶层的一个接口类,它定义了IoC容器的基本功能规范:
在这里插入图片描述

public interface BeanFactory {    
	//对FactoryBean的转义定义,因为如果使用bean的名字检索FactoryBean得到的对象是工厂生成的对象,    
	//如果需要得到工厂本身,需要转义           
	String FACTORY_BEAN_PREFIX = "&"; 
	//根据bean的名字,获取在IOC容器中得到bean实例    
	Object getBean(String name) throws BeansException;    
	//根据bean的名字和Class类型来得到bean实例,增加了类型安全验证机制。    
	Object getBean(String name, Class requiredType) throws BeansException;    
	//提供对bean的检索,看看是否在IOC容器有这个名字的bean    
	boolean containsBean(String name);    
	//根据bean名字得到bean实例,并同时判断这个bean是不是单例    
	boolean isSingleton(String name) throws NoSuchBeanDefinitionException;    
	//得到bean实例的Class类型    
	Class getType(String name) throws NoSuchBeanDefinitionException;    
	//得到bean的别名,如果根据别名检索,那么其原名也会被检索出来    
	String[] getAliases(String name);    
}
BeanFactory 有三个子接口:
ListableBeanFactory:可列表的工厂接口(表示Bean的集合)
HierarchicalBeanFactory :表示Bean之间的继承关系的接口(表示Bean之间的关系)
AutowireCapableBeanFactory:Bean的自动装配规则(表示Bean行为)

BeanFactory之类的接口只对IOC容器的基本行为作了定义,根本不关心Bean是如何加载的。正如我们只关心工厂里得到什么的产品对象,至于工厂是怎么生产这些对象的,这个基本的接口不关心。而要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现:

IoC容器实现:
XmlBeanFactory
ClasspathXmlApplicationContext
ApplicationContext
… (还有很多)
1.2.1.2 BeanDefinition

Spring IoC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的。

在这里插入图片描述

/** bean 的名字为键,BeanDefinition为值,初始容量为256 */
private final Map<String, BeanDefinition> beanDefinitionMap 
    			= new ConcurrentHashMap<String, BeanDefinition>(256);

BeanDefinition存储在一个Map对象中,如果使用的是DefaultListableBeanFactory的话,它就存在一个ConcurrentHashMap对象中。
现在将BeanDefinition是如何一步步走到这个Map对象中的。

1.2.1.3 Bean 的解析

Bean 的解析过程非常复杂。Bean 的解析主要就是对 Spring 配置文件的解析。这个解析过程主要通过下图中的类完成:
在这里插入图片描述

1.2.2 Spring IoC 初始化

IoC容器的初始化包括BeanDefinitionResource定位、载入和注册这三个基本的过程。我们以ApplicationContext为例讲解,ApplicationContext系列容器也许是我们最熟悉的,因为web项目中使用的XmlWebApplicationContext就属于这个继承体系。

BeanDefinition生成过程最简化:
1、把 XML文件加载到内存中以 Document对象的形式存在;
2、完成 Document解析和处理的任务,获取到 BeanDefinition
3、构建对象,并且存放在 BeanFactory(如: DefaultListableBeanFactory)的 ConcurrentHashMap中。
1.2.2.1 DefaultListableBeanFactory 的整个流程
// 根据Xml配置文件创建Resource资源对象,该对象中包含了BeanDefinition的信息
 ClassPathResource resource =new ClassPathResource("application-context.xml");
// 创建DefaultListableBeanFactory
 DefaultListableBeanFactory factory =new DefaultListableBeanFactory();
// 创建XmlBeanDefinitionReader读取器,用于载入BeanDefinition。
// 之所以需要BeanFactory作为参数,是因为会将读取的信息回调配置给factory
 XmlBeanDefinitionReader reader =new XmlBeanDefinitionReader(factory);
// XmlBeanDefinitionReader执行载入BeanDefinition的方法,最后会完成Bean的载入和注册。
// 完成后Bean就成功的放置到IOC容器当中,以后我们就可以从中取得Bean来使用
 reader.loadBeanDefinitions(resource);
1.2.2.2 ApplicationContext 的整个流程
  1. Bean定义资源的载入是从refresh()函数开始,也就说初始化的入口就是refresh();
  2. Bean的配置载入到IoC的方法是loadBeanDefinition
    1. ResourceLoader 来完成资源文件位置的定位( 从类路径,文件系统, URL 等方式来定为资源位置 );
    2. 定位到 Bean 配置文件后,将其抽象成 Resource 来被 IoC 容器处理;
    3. 容器通过 BeanDefinitionReader 来完成Bean配置信息的解析和 Bean 的信息注册;实际的处理过程是委托给 BeanDefinitionParserDelegate 来完成,从而得到 Bean 的定义信息,这些信息在 Spring 中使用 BeanDefinition 对象来表示;
    4. 容器解析得到 BeanDefinition 以后,需要把它在 IoC 容器中注册,这由 IoC 实现 BeanDefinitionRegistry 接口来实现。
  3. 通过 BeanFactoryApplicationContext 来享受到 Spring IoC 的服务。

区别 BeanfactoryFactory Bean

  1. 其中 BeanFactory 指的是 IoC 容器的编程抽象,比如 ApplicationContextXmlBeanFactory 等,这些都是 IoC 容器的具体表现,需要使用什么样的容器由客户决定,但 Spring 为我们提供了丰富的选择;
  2. Factory Bean 只是一个可以在 IoC 而容器中被管理的一个Bean

BeanDefinition从加载、解析、处理、注册到BeanFactory的过程
bean创建过程与九次beanPostProcessor调用时机(原图地址)
Spring IoC原理解读


在这里插入图片描述

1.2.3 Spring IoC容器的依赖注入
1.2.4 Spring IoC容器的lazy-init属性

2. AOP 和 Instrumentation

包含模块spring-aop, spring-aspects, spring-instrument, spring-instrument-tomcat:
spring-aop : 提供了一个AOP联盟标准的 面向切面编程的实现,它允许你定义方法拦截器与切入点,从而将逻辑代码与实现函数进行分离。
spring-aspects : 提供了与AspectJ的集成
spring-instrument : 提供了类工具的支持与classloader的实现,以便在特定的应用服务上使用。
spring-instrument-tomcat : 包含了spring对于Tomcat的代理

2.1 代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

在这里插入图片描述

作用:
中介隔离作用: 在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能: 代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类 增加额外的功能来扩展被代理类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。

设计模式—代理模式

2.1.1 静态代理

在这里插入图片描述

package ProxyTest.StaticProxy;

public interface KindWomen {
    void makeEyesWithMan();
}
package ProxyTest.StaticProxy;

public class JLPan implements KindWomen {
    @Override
    public void makeEyesWithMan() {
        System.out.println(">>>>>>before method.invoke(), 这里代表代理对象在主业务逻辑方法执行前织入的代码");
        kindWomen.makeEyesWithMan();
        System.out.println(">>>>>>>after method.invoke(),这里代表代理对象在主业务逻辑方法执行后织入的代码");
    }
}
package ProxyTest.StaticProxy;
// 代理类
public class WangPo implements KindWomen {
    private KindWomen kindWomen;        // 被代理对象
    public WangPo()
    {
        this.kindWomen = new JLPan();
    }
    public WangPo(KindWomen kindWomen)
    {
        this.kindWomen = kindWomen;
    }
    @Override
    public void makeEyesWithMan() {
        kindWomen.makeEyesWithMan();
    }
}
package ProxyTest.StaticProxy;

public class DaGuanRen {
    public static void main(String args[])
    {
        WangPo wangPo = new WangPo(new JLPan());
        wangPo.makeEyesWithMan();
    }
}
优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可( 解耦合),对于如上的客户端代码,
缺点:
1)代理类和被代理类实现了相同的接口,代理类通过实现被代理类中相同的方法。这样就出现了大量的 代码冗余。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2) 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。以上的代码是只为 JLPan类的访问提供了代理,但是如果还要为其他类如 WuSong类提供代理的话,就需要我们再次添加 WuSong的代理类。
2.1.2 动态代理

根据对静态代理的介绍,你会发现每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,这样会导致代码冗余。所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理。

Java动态代理又被分为:
JDK动态代理;
CGLIB动态代理。
2.1.2.1 JDK动态代理

动态代理是代理模式Java反射技术结合的产物。

当我们得到一个对象,想动态的为其一些方法每次被调用前后追加一些操作时,我们将会用到java动态代理

Java中动态代理的实现,关键就是这两个东西:
Proxy
InvocationHandler

下面简单说明一下Java如何实现动态代理的。
在这里插入图片描述

package ProxyTest.JDKDynamicProxy;

public interface JDKKindWomen {
    void makeEyesWithMan();
}
package ProxyTest.JDKDynamicProxy;

public class JDKJLPan implements JDKKindWomen{
    @Override
    public void makeEyesWithMan() {
        System.out.println("小潘潘在抛媚眼了。。。");
    }
}
package ProxyTest.JDKDynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKWangPoHandler  implements InvocationHandler {
    private JDKKindWomen kindWomen;
    public  JDKWangPoHandler(){
        kindWomen = new JDKJLPan();
    }
    public  JDKWangPoHandler(JDKKindWomen kindWomen){
        this.kindWomen = kindWomen;
    }
    /**
     *  这个方法不会被我们显示的去调用 
     *  
     * 第一个参数就是 代理者,如果你想对代理者做一些操作可以使用这个参数;
     * 第二个就是 被执行的方法,
     * 第三个是 执行该方法所需的参数。
     * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>>>>>before method.invoke(), 这里代表代理对象在主业务逻辑方法执行前织入的代码");
        method.invoke(kindWomen, args);
        System.out.println(">>>>>>>after method.invoke(),这里代表代理对象在主业务逻辑方法执行后织入的代码");
        return null;
    }
    // 自定义一个构建代理对象的方法
    public static JDKKindWomen createProxy()
    {
        JDKJLPan jlPan = new JDKJLPan();
        JDKWangPoHandler wangPoHandler = new JDKWangPoHandler(jlPan);
        return (JDKKindWomen) Proxy.newProxyInstance(
                JDKJLPan.class.getClassLoader(), // jlPan.getClass().getClassLoader(),//
                JDKJLPan.class.getInterfaces(), //jlPan.getClass().getInterfaces(),
                wangPoHandler
        );
    }
}
package ProxyTest.JDKDynamicProxy;

public class JDKDaGuanRen {
    public static void main(String args[])
    {
        JDKKindWomen kindWomen = JDKWangPoHandler.createProxy();
        kindWomen.makeEyesWithMan();
    }
}
2.1.2.2 CGLIB动态代理
什么是 CGLIB:
CGLib是一个强大的、高性能的代码生成库,它可以在运行期扩展Java类与实现Java接口。 Hibernate支持它来实现 PO(Persistent Object 持久化对象)字节码的动态生成。
其被广泛应用于 AOP框架中,用以提供方法拦截操作。例如 Spring AOP为他们提供方法的 interception(拦截)。
CGLib采用底层的字节码技术 ASM, 可以为一个类创建子类, 在子类中采用方法拦截的技术拦截所有父类方法的调用, 并织入横切逻辑。换句话说, CGLIB通过“继承”可以继承父类所有的公开方法,然后可以重写这些方法,在重写时对这些方法增强,这就是 CGLIB的思想。

根据里氏代换原则(LSP):父类需要出现的地方,子类可以出现,所以CGLib实现的代理也是可以被正常使用。

在这里插入图片描述
pom导入包:

<dependencies>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

package ProxyTest.CglibDynamicProxy;

public class CglibJLPan {
    public void makeEyesWithMan() {
        System.out.println("小潘潘在抛媚眼了。。。");
    }
}
package ProxyTest.CglibDynamicProxy;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibWangPoHandler implements MethodInterceptor {
    /**
     *
     * @param o cglib生成的代理对象
     * @param method 被代理对象的方法
     * @param objects     传入方法的参数
     * @param methodProxy 代理的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println(">>>>>>before method.invoke(), 这里代表代理对象在主业务逻辑方法执行前织入的代码");
        Object obj = methodProxy.invokeSuper(o, objects);
        System.out.println(">>>>>>>after method.invoke(),这里代表代理对象在主业务逻辑方法执行后织入的代码");
        return obj;
    }
	// 自定义一个构建代理对象的方法
    public static CglibJLPan createProxy()
    {
        Enhancer enhancer = new Enhancer();             // 通过CGLIB动态代理获取代理对象的过程
        enhancer.setSuperclass(CglibJLPan.class);       // 设置需要处理的对象父类
        enhancer.setCallback(new CglibWangPoHandler()); // 设置enhancer回调对象
        return (CglibJLPan) enhancer.create();          // 创建并且返回代理对象
    }
}
package ProxyTest.CglibDynamicProxy;

public class CglibDaGuanRen {
    public static void main(String args[]){
        // 创建代理对象(这个方法是自己实现的)
        CglibJLPan jlPan = CglibWangPoHandler.createProxy();
        jlPan.makeEyesWithMan();
    }
}

2.1.3 静态、JDK动态、CGLIB动态三者区别
方法缺陷优势
静态代理代码冗余中介隔离作用; 便于增加功能
JDK动态代理需要接口解决冗余问题 ,不依赖第三方库
CGLIB动态代理运行时载入,使用第三方库不需要接口

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.2 AOP

AOPAspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待 等等。

  • AOP可以说是OOPObject Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
  • AOPOOP一样,只是一种编程范式,AOP并没有规定说,实现AOP协议的代码,要用什么方式去实现。
    spring AOP 及实现方式
    Comparing Spring AOP and AspectJ
    在这里插入图片描述
Spring AOP是AOP的一种实现,并且基于动态代理:
1、如果要代理的对象,实现了某个接口,那么 Spring AOP会使用 JDK Proxy,去创建代理对象;
2、而对于没有实现接口的对象,就无法使用 JDK Proxy去进行代理了(在上面一节有提到),这时候 Spring AOP会使用 CGLIB,生成一个被代理对象的子类,来作为代理。
3、但是不是所有 AOP的实现都是在 运行时进行织入的,因为这样效率太低了,而且只能针对方法进行 AOP,无法针对构造函数、字段进行 AOP。这种情况就引入了 AspectJ,可以在 编译成class时就织入,比如,当然 AspectJ还提供了后编译器织入和类加载期织入。

静态代理(代码冗余) – JDK动态代理(需要接口) – CGLIB动态代理(运行时载入) – AspectJ(编译时载入的)

在这里插入图片描述

Tip : AOP是一种思想,Spring AOP 和 AspectJ是对应的不同实现。

2.3 Spring AOP 和 AspectJ的异同

特点Spring AOPAspectJ
实现纯Java实现使用Java编程语言的扩展来实现
单独编译过程不需要需要AspectJ编译器(ajc),除非设置了LTW
织入方式运行时编译时
织入水平只是针对方法字段、方法、构造函数、静态初始化、final Class
应用领域Spring所有领域
运行速度
学习难度容易更复杂


2.4 Spring AOP

  1. AspectJ风格(注解风格)
  2. xml配置风格(Schema-based AOP Support)

在这里插入图片描述

2.4.1 术语和概念
切面 Aspect:
切点、连接点及其通知所在的那个类叫做切面。
切点 Pointcut:
连接点的集合
连接点 Joinpoint:
SpringAOP中连接点最小单位是方法,每个方法称为一个连接点;连接点可以使用表达式表达,一个表达式可以表达多个连接点。
通知 Advice:
切入的时机和内容。

在这里插入图片描述

在这里插入图片描述

切入点表达式:

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 

@Pointcut("within(com.xyz.myapp.trading..*)")
private void inTrading() {} 

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 
  1. anyPublicOperation 如果方法执行连接点代表任何公共方法的执行,则匹配。
  2. inTrading 如果交易模块中有方法执行,则匹配。
  3. tradingOperation 如果方法执行代表交易模块中的任何公共方法,则匹配。

Advice的类型

1、 before advice, 在 join point 前被执行的 advice. 虽然 before advice 是在 join point 前被执行, 但是它并不能够阻止 join point 的执行, 除非发生了异常(即我们在 before advice 代码中, 不能人为地决定是否继续执行 join point 中的代码)
2、 after return advice, 在一个 join point 正常返回后执行的 advice
3、 after throwing advice, 当一个 join point 抛出异常后执行的 advice
4、 after(final) advice, 无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice.
5、 around advice, 在 join point 前和 joint point 退出后都执行的 advice. 这个是最常用的 advice.
6、 introductionintroduction可以为原有的对象增加新的属性和方法。

在这里插入图片描述

图片:Spring AOP——简单粗暴,小白教学

AOP中的Joinpoint可以有多种类型:构造方法调用,字段的设置和获取,方法的调用,方法的执行,异常的处理执行,类的初始化。
也就是说在AOP的概念中我们可以在上面的这些Joinpoint上织入我们自定义的Advice;
但是Spring中却没有实现上面所有的joinpoint,确切的说,Spring只支持方法执行类型的Joinpoint

Spring AOP应用-- 哔哩哔哩视频
spring官方 : aop-aspectj-support
3幅图让你了解Spring AOP
Spring AOP:Spring 中面向切面编程



2.4.2 Spring AOP的应用

Spring支持XML方式和实现注解的方式(也叫AspectJ方式)的AOP;打不死要使用@Aspect注解,需要引入AspectJ相关的 jaraspectjrtaspectjweaver

2.4.2.1 pom.xml配置

首先在pom.xml中添加相关jar包

    <!-- aspectj 相关jar包-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
      <version>1.9.1</version>
    </dependency>
    <!-- 做实验时候缺了这个包,跑死跑不出来;还报异常
Exception encountered during context initialization - 
cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name '名字' ...
	-->
    <dependency> 
	  <groupId>org.aspectj</groupId>
	  <artifactId>aspectjweaver</artifactId>
	  <version>1.9.6</version>
	</dependency>
2.4.2.2 xml配置文件

Spring的配置文件 mySpringAOP.xml 中引入contextaop对应的命名空间;配置自动扫描的包,同时使切面类中相关方法中的注解生效,需自动地为匹配到的方法所在的类生成代理对象。

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!-- 配置自动扫描的包 -->
    <context:component-scan base-package="mySpringAOP" />
    <!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 -->
    <aop:aspectj-autoproxy/>
	<!--  
	这是 xml 配置方式,这里的bean配置我们使用注解的方式,而不是xml
    <bean id="mathImpl" class="mySpringAOP.ArithmeticCalculatorImpl">
    </bean>
    -->
</beans>
2.4.2.3 计算器实现类

创建简单计算器实现类 ArithmeticCalculatorImpl

package mySpringAOP;
import org.springframework.stereotype.Component;
// 将实现类加入Spring的IOC容器进行管理
@Component("mathImpl") // "ArithmeticCalculator"
public class ArithmeticCalculatorImpl {
    public int add(int i, int j) {
        System.out.println(i+" + "+ j +" = " + (i+j));
        return i+j;
    }
}
2.4.2.4 切面类

现在想在计算器实现类中的每个方法执行前、后、以及发生异常时等打印一些信息,这时候我们构建一个切面类SpringAOPAspect

package mySpringAOP;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component // 类上使用 @Component 注解 把切面类加入到IOC容器中
@Aspect     // 使用 @Aspect 注解 使之成为切面类
public class SpringAOPAspect {
    // 切点(连接点的集合)
    @Pointcut("execution(* mySpringAOP.*.*(..))") // 使用Execution表达式表示连接点的集合
    private void justPointCut(){}
	// 这个execution表达式的含义:mySpringAOP包及所有子包下任何类的任何方法都会使用

    /** 通知:
     * ("justPointCut()") : 通知的位置(目标方法)
     * Before : 通知的时间
     * public void before{ ... } : 通知的内容
    */
    @Before("justPointCut()") // 
    public void before()    {
        System.out.println("============ Before ============");
    }
    @After("justPointCut()")
    public void after()  {
        System.out.println("============ After  ============");
    }
    @AfterReturning("justPointCut()")
    public void afterReturning()	{
        System.out.println("======= AfterReturning ========");
    }
    // 连接点方法执行抛出异常之后调用
    @AfterThrowing("justPointCut()")
    public void afterThrowing()    {
        System.out.println("======= AfterThrowing ========");
    }
    /**
    *  org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for...
    *  环绕监听会对有返回值的方法做处理 !!! 所以像下面这样写会报错。
    @Around("justPointCut()")
    public void Around()    {
        System.out.println("============ Around ============");
    }
    */
}

在这里插入图片描述

一个很不错的AspectJ的Execution表达式说明

2.4.2.5 main方法
package mySpringAOP;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class mySpringAOPTest {
    public static void main(String args[])	{
    	// 读取配置文件,管理所有的bean
        ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:mySpringAOP.xml");
        // 生成Bean对应的对象
        ArithmeticCalculatorImpl ac = (ArithmeticCalculatorImpl)ctx.getBean("mathImpl");
        // 调用方法
        ac.add(3,5);
    }
}

执行结果如下图所示:
在这里插入图片描述
没有抛出异常,所以@AfterThrowing("justPointCut()") 注解的代码没有执行。

出现Exception encountered during context initialization - cancelling refresh attempt问题

2.4.3 Spring AOP的实现

Source Code Reading…

2.5 AspectJ

Writing…



3. 消息(Messaging)

spring framework 4 包含了spring-messaging模块,其中使用了来自于spring integration项目的关键抽象,如Message, MessageChannel, MessageHandler等,他们可以作为基于消息的应用服务的基础。该模块还包含了一组可将消息映射到方法的注解,类似于spring-mvc的编程模型。

4. 数据访问/集成(Data Access/ Integration)

包含spring-jdbc, spring-tx, spring-orm, spring-oxm, spring-jms:
spring-jdbc: 提供了JDBC抽象层,消除了冗长的JDBC编码和解析数据库厂商特有的错误代码.

5. Web

包含spring-web, spring-webmvc, spring-websocket, spring-webmvc-portlet:
spring-web提供了基于面向web集成的特性,如多文件上传功能、通过servlet listener初始化IoC容器与面向web的ApplicationContext,它还包含了HTTP客户端与Spring远程支持的web相关的部分.
spring-webmvc(又名web-servlet)包含了Spring对于Web应用的MVC与REST实现,Spring MVC框架提供了领域模型代码和Web表单之间的分离,并集成了Spring框架的所有其他特性.
spring-webmvc-portlet(又名web-portlet)提供了基于Portlet环境使用MVC的实现.

SpringMVC

图解

在这里插入图片描述

体现主要流程的部分源码
  1. 首先需要在web.xml配置DispatcherServlet,这是SpringMVC的核心,用于分发不同的任务。
    <!--配置springmvc DispatcherServlet-->
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <!--配置dispatcher.xml作为mvc的配置文件-->
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:dispatcher-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
  1. dispatcher-servlet.xml配置

<!--此文件负责整个mvc中的配置-->

    <!--启用spring的一些annotation -->
    <context:annotation-config/>

    <!-- 配置注解驱动 可以将request参数与绑定到controller参数上 -->
    <mvc:annotation-driven/>

    <!--静态资源映射-->
    <!--本项目把静态资源放在了webapp的statics目录下,资源映射如下-->
    <mvc:resources mapping="/css/**" location="WEB-INF/statics/css"/>
    <mvc:resources mapping="/js/**" location="WEB-INF/statics/js/"/>
    <mvc:resources mapping="/image/**" location="WEB-INF/statics/images/"/>
    <mvc:default-servlet-handler />
    <!--这句要加上,要不然可能会访问不到静态资源,具体作用自行百度-->

    <!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP-->
    <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
    <bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/views/"/><!--设置JSP文件的目录位置-->
        <property name="suffix" value=".jsp"/>
        <property name="exposeContextBeansAsAttributes" value="true"/>
    </bean>

    <!-- 自动扫描装配 -->
    <context:component-scan base-package="example.controller"/>
  1. 执行DispatcherServletdoDispatch()
public class DispatcherServlet extends FrameworkServlet{
	...
	// 调用 Dispatch 方法
	doService(HttpServletRequest request, HttpServletResponse response)
	{
		...
		this.doDispatch(request, response);
		...
	}
	...
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response)
	{
		...
		// 获取handler的映射
		mappedHandler = this.getHandler(processedRequest);
		...
		// 获取映handler适配器,不同的Controller实现对应的适配器不同
		HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
		...
		// 执行适配器的handle方法(本质上是Controller中的对应方法)
		mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
		...
		// View视图解析,页面渲染
		this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
		...
	}

}

SpringMVC源码解析
springMvc 启动流程

6. Test

spring-test模块通过JunitTestNGspring的组件提供了单元测试和集成测试。

Spring架构

Tomcat 和 servlet

Tomcat的工作机制

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值