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
容器管理的对象称为bean
。bean
是一个由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
的,所以可以采用JavaConfig
和xml
,先来看JavaConfig
配置。
-
一般遵循两个原则:
-
1、
JavaConfig
是配置相关代码,不含任何逻辑代码; -
2、通常会将
JavaConfig
放到单独的包中。
2个关键注解:
-
1、
@Configuration
:用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean
注解的方法,这些方法将会被AnnotationConfigApplicationContext
或AnnotationConfigWebApplicationContext
类进行扫描,并用于构建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进行管理。
1.1.1.3 xml 配置
-
方式:
- 构造器注入
- set方式注入
- 扩展方式注入
-
1、都是使用注解定义
Bean
的方式 - 2、一个应用于类,一个应用于方法
-
3、
@Component
用于自动检测和使用类路径扫描自动配置Bean
;@Bean
用于显式声明单个Bean
,而不是让Spring
自动执行它。 -
4、
@Bean
可以对第三方的类进行配置;@Component
不能对第三方的类进行自动配置。
@Component
和
@Bean
的区别:
1.1.2 bean 的生命周期
生命周期:
-
对象创建:
-
1、从
Bean
的配置中获取BeanDefinition
,进行实例化; -
2、设置
Bean
的属性; -
3、
Bean
级生命周期接口(BeanNameAware
,BeanFactoryAware
,ApplicationContextAware
)的方法; -
4、
BeanPostProcessor
的postProcessorBeforeInitialization()
; -
5、
InitializingBean
接口的afterPropertiesSet()
; -
6、自定义初始化方法
init-method
或者@PostConstruct
标注的方法; -
7、
BeanPostProcessor
的postProcessorAfterInitialization()
; -
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-method
和destroy-method
指定的方法 -
2、Bean级生命周期接口方法:这个包括了
BeanNameAware
、BeanFactoryAware
、InitializingBean
和DiposableBean
这些接口的方法 -
3、容器级生命周期接口方法:这个包括了
BeanPostProcessor
和InstantiationAwareBeanPostProcessor
这两个接口实现,一般称它们的实现类为“后处理器”。 -
4、工厂后处理器接口方法:这个包括了
AspectJWeavingEnabler
,ConfigurationClassPostProcessor
,CustomAutowireConfigurer
等等非常有用的工厂后处理器接口的方法。工厂后处理器也是容器级的。在应用上下文装配配置文件之后立即调用。
BeanNameAware
接口是为了让自身Bean能够感知到,并可以获取到自身在Spring
容器中的id
属性。
同理,其他的Aware
接口也是为了能够感知到自身的一些属性。比如实现了ApplicationContextAware
接口的类,能够获取到ApplicationContext
,实现了BeanFactoryAware
接口的类,能够获取到BeanFactory
对象。
Spring中的…Aware接口
1.1.3 bean 作用域与线程安全
Spring
容器中的Bean
是否线程安全,容器本身并没有提供Bean
的线程安全策略,因此可以说Spring
容器中的Bean
本身不具备线程安全的特性,但是具体还是要结合具体scope
的Bean
去研究。
-
bean 作用域
- 1、 singleton:单例, 默认作用域。
- 2、 prototype:原型,每次创建一个新对象。
- 3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。
- 4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。
- 5、global-session:全局会话,所有会话共享一个实例。
bean对象是默认单例的,那么Spring中单例Bean的线程安全问题如何处理?
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
- Bean对象中尽量不使用可变的成员变量;
- 类中定义一个ThreadLocal成员变量
Spring 单例Bean和Java 单例模式的区别?
- Spring的的单例是基于BeanFactory也就是spring容器,单例Bean在此Spring容器内是单个的;
- Java的单例是基于JVM,每个JVM内一个单例。
1.2 IoC(Inverse of Control)
-
控制反转 (Inverse of Control, IoC):
-
将创建对象的权利交给
IoC
容器。 - 那么必然的我们需要创建一个容器,同时需要一种描述来让容器知道需要创建的对象与对象的关系。这个描述最具体表现就是我们可配置的文件(见上一节Bean的配置)。 依赖注入(Dependency Injection, DI):
- 就是指对象是被动接受依赖类而不是自己主动去找,换句话说就是指对象不是从容器中查找它依赖的类,而是在容器实例化对象的时候主动将它依赖的类注入给它。
为了不重复造轮子,一些基础知识可以先阅读一下以下三篇文章:IoC : 工厂模式 + 反射 + 配置文件读取、Web项目使用IoC的优势、IoC的依赖倒置。
总结来说 IoC 就是:基于依赖倒置原则,结合工厂模式,从配置文件中读取
Bean
配置信息并通过反射构建Bean;在之后对Bean
进行管理。(自己总结的!!!)
将创建对象的权利从程序猿手中拿走,并递给了IoC Container
。
以上只是对IoC的大致思想进行了总结,具体Spring IoC实现更加复杂。
1.2.1 Spring IoC 体系结构
Bean
是一个由Spring IoC
容器实例化、组装和管理的对象。- 在
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);
}
-
ListableBeanFactory
:可列表的工厂接口(表示Bean的集合) -
HierarchicalBeanFactory
:表示Bean之间的继承关系的接口(表示Bean之间的关系) -
AutowireCapableBeanFactory
:Bean的自动装配规则(表示Bean行为)
BeanFactory
有三个子接口:
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
容器的初始化包括BeanDefinition
的Resource
定位、载入和注册这三个基本的过程。我们以ApplicationContext
为例讲解,ApplicationContext
系列容器也许是我们最熟悉的,因为web项目中使用的XmlWebApplicationContext
就属于这个继承体系。
-
1、把
XML
文件加载到内存中以Document
对象的形式存在; -
2、完成
Document
解析和处理的任务,获取到BeanDefinition
; -
3、构建对象,并且存放在
BeanFactory
(如:DefaultListableBeanFactory
)的ConcurrentHashMap
中。
BeanDefinition
生成过程最简化:
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 的整个流程
Bean
定义资源的载入是从refresh()
函数开始,也就说初始化的入口就是refresh()
;- 将
Bean
的配置载入到IoC
的方法是loadBeanDefinition
:ResourceLoader
来完成资源文件位置的定位( 从类路径,文件系统, URL 等方式来定为资源位置 );- 定位到
Bean
配置文件后,将其抽象成Resource
来被IoC
容器处理; - 容器通过
BeanDefinitionReader
来完成Bean
配置信息的解析和Bean
的信息注册;实际的处理过程是委托给BeanDefinitionParserDelegate
来完成,从而得到Bean
的定义信息,这些信息在Spring
中使用BeanDefinition
对象来表示; - 容器解析得到
BeanDefinition
以后,需要把它在IoC
容器中注册,这由IoC
实现BeanDefinitionRegistry
接口来实现。
- 通过
BeanFactory
和ApplicationContext
来享受到Spring IoC
的服务。
区别
Beanfactory
和Factory Bean
:
- 其中
BeanFactory
指的是IoC
容器的编程抽象,比如ApplicationContext
,XmlBeanFactory
等,这些都是IoC
容器的具体表现,需要使用什么样的容器由客户决定,但Spring
为我们提供了丰富的选择;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
AOP
(Aspect Oriented Programming
)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待 等等。
AOP
可以说是OOP
(Object Oriented Programming
,面向对象编程)的补充和完善。OOP
引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP
允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting
),在OOP
设计中,它导致了大量代码的重复,而不利于各个模块的重用。AOP
像OOP
一样,只是一种编程范式,AOP
并没有规定说,实现AOP
协议的代码,要用什么方式去实现。
spring AOP 及实现方式
Comparing Spring AOP and AspectJ
-
1、如果要代理的对象,实现了某个接口,那么
Spring AOP
会使用JDK Proxy
,去创建代理对象; -
2、而对于没有实现接口的对象,就无法使用
JDK Proxy
去进行代理了(在上面一节有提到),这时候Spring AOP
会使用CGLIB
,生成一个被代理对象的子类,来作为代理。 -
3、但是不是所有
AOP
的实现都是在 运行时进行织入的,因为这样效率太低了,而且只能针对方法进行AOP
,无法针对构造函数、字段进行AOP
。这种情况就引入了AspectJ
,可以在 编译成class
时就织入,比如,当然AspectJ
还提供了后编译器织入和类加载期织入。
Spring AOP
是AOP的一种实现,并且基于动态代理:
静态代理(代码冗余) – JDK动态代理(需要接口) – CGLIB动态代理(运行时载入) – AspectJ(编译时载入的)
Tip : AOP是一种思想,Spring AOP 和 AspectJ是对应的不同实现。
2.3 Spring AOP 和 AspectJ的异同
特点 | Spring AOP | AspectJ |
---|---|---|
实现 | 纯Java实现 | 使用Java编程语言的扩展来实现 |
单独编译过程 | 不需要 | 需要AspectJ编译器(ajc),除非设置了LTW |
织入方式 | 运行时 | 编译时 |
织入水平 | 只是针对方法 | 字段、方法、构造函数、静态初始化、final Class |
应用领域 | Spring | 所有领域 |
运行速度 | 慢 | 块 |
学习难度 | 容易 | 更复杂 |
2.4 Spring AOP
- AspectJ风格(注解风格)
- 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() {}
anyPublicOperation
如果方法执行连接点代表任何公共方法的执行,则匹配。inTrading
如果交易模块中有方法执行,则匹配。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、
introduction
,introduction
可以为原有的对象增加新的属性和方法。
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
相关的 jar
包 aspectjrt
和 aspectjweaver
。
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
中引入context
、aop
对应的命名空间;配置自动扫描的包,同时使切面类中相关方法中的注解生效,需自动地为匹配到的方法所在的类生成代理对象。
<?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 ============");
}
*/
}
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
图解
体现主要流程的部分源码
- 首先需要在
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>
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"/>
- 执行
DispatcherServlet
的doDispatch()
。
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);
...
}
}
6. Test
spring-test
模块通过Junit
或TestNG
对spring
的组件提供了单元测试和集成测试。