什么是spring
- spring是一个轻量级的IOC和AOP容器框架。是为JAVA应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。
主要模块
Spring AOP : AOP服务
Spring Core : 核心库类,提供IOC服务
Spring Context : 提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等)
Spring DAO : 对JDBC的抽象 ,简化了数据库访问异常的处理
Spring ORM : 对现有的ORM框架的支持
Spring Web : 提供了基本的面向Web的综合特性
Spring MVC : 提供面向Web应用的Model-View-Controller实现
spring IOC
- Inversion of Control 控制反转
- 所谓 IOC ,就是由 Spring IOC 容器来负责对象的生命周期和对象之间的关系。
- Spring 启动时读取应用程序提供的 Bean 配置信息,并在 Spring 容器中生成一份相应的 Bean 配置注册表,然后根据这张注册表实例化 Bean,装配好 Bean 之间的依赖关系,为上层应用提供准备就绪的运行环境。其中 Bean 缓存池为 HashMap 实现。
BeanFactory
- 是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能;
- 定义了允许在运行期间向容器注册单实例 Bean 的方法;对于单实例( singleton)的 Bean 来说,BeanFactory 会缓存 Bean 实例,所以第二次使用 getBean() 获取 Bean 时将直接从IoC 容器的缓存中获取 Bean 实例。
- 在初始化 BeanFactory 时,必须为其提供一种日志框架,比如使用 Log4J, 即在类路径下提供 Log4J 配置文件,这样启动 Spring 容器才不会报错。
ApplicationContext
-
应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能;
ClassPathXmlApplicationContext:默认从类路径加载配置文件
FileSystemXmlApplicationContext:默认从文件系统中装载配置文件
国际化(MessageSource):为应用提供 i18n 国际化消息访问的功能;
访问资源,如URL和文件(ResourceLoader)
载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
消息发送、响应机制(ApplicationEventPublisher)让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。
AOP(拦截器)
-
ClassPathXmlApplicationContext
- 简单的用 ApplicationContext 做测试的话 , 获得 Spring 中定义的 Bean 实例(对象) 可以用:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
- 如果是两个以上 , 可以使用字符串数组 :
ApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"applicationContext.xml","SpringTest.xml"});
- 或者可以使用通配符:
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:/*.xml");
对于 ClassPathXmlApplicationContext 的使用:
- 简单的用 ApplicationContext 做测试的话 , 获得 Spring 中定义的 Bean 实例(对象) 可以用:
classpath: 前缀是可加可不加的 , 默认就是指项目的 classpath 路径下面。
如果要使用绝对路径 , 需要加上 file: , 前缀表示这是绝对路径。
spingboot源码指的classpath
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/",
"classpath:/resources/",
"classpath:/static/",
"classpath:/public/" };
-
对于 FileSystemXmlApplicationContext 的使用:
- 没有盘符的是项目工作路径 , 即项目的根目录。
- 有盘符表示的是文件绝对路径 ,file: 可加可不加。
- 如果要使用 classpath 路径 , 需要前缀 classpath:。
ApplicationEventPublisher
ApplicationContext 通过 ApplicationEvent 类和 ApplicationListener 接口进行事件处理。 如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式。
事件定义
public class UserRegisterEvent extends ApplicationEvent{
public UserRegisterEvent(String name) { //name即source
super(name);
}
}
- 事件发布者
@Service
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void register(String name) {
System.out.println("用户:" + name + " 已注册!");
applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
}
}
- 事件订阅者
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {
@Override
public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
System.out.println("邮件服务接到通知,给 " + userRegisterEvent.getSource() + " 发送邮件...");
}
}
-
WebApplication 体系架构
-
WebApplicationContext 是专门为 Web 应用准备的,它允许从相对于 Web 根目录的路径中装载配置文件完成初始化工作。从 WebApplicationContext 中可以获得ServletContext 的引用,整个 Web 应用上下文对象将作为属性放置到 ServletContext 中,以便 Web 应用环境可以访问 Spring 应用上下文。
-
BeanFactory和ApplicationContext
两者装载bean的区别:
BeanFactory:
BeanFactory在启动的时候不会去实例化Bean,中有从容器中拿Bean的时候才会去实例化;
ApplicationContext:
ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化;
各自优点:
延迟实例化的优点:(BeanFactory)
应用启动的时候占用资源很少;对资源要求较高的应用,比较有优势;不延迟实例化的优点: (ApplicationContext)
所有的Bean在启动的时候都加载,系统运行的速度快;
在启动的时候所有的Bean都加载了,我们就能在系统启动的时候,尽早的发现系统中的配置问题
建议web应用,在启动的时候就把所有的Bean都加载了。(把费时的操作放到系统启动中完成)
Spring Bean 作用域(用于判断bean的个数)
-
singleton(单例)、prototype(原型)、request、session 和 global session
-
singleton:单例模式(多线程下不安全,此为spring默认的生命周期)
- singleton:单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:
<bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>
- singleton:单例模式,Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:
-
prototype:原型模式(每次使用时创建)
- 每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例,每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的bean使用prototype作用域,而对无状态的bean使用singleton作用域。
-
Request:一次 request 一个实例
- request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。
<bean id="loginAction" class="com.cnblogs.Login" scope="request"/>
- request:在一次 Http 请求中,容器会返回该 Bean 的同一实例。而对不同的 Http 请求则会产生新的 Bean,而且该 bean 仅在当前 Http Request 内有效,当前 Http 请求结束,该 bean实例也将会被销毁。
-
session
- 在一次 Http Session 中,容器会返回该 Bean 的同一实例。而对不同的 Session 请求则会创建新的实例,该 bean 实例仅在当前 Session 内有效。同 Http 请求相同,每一次session 请求创建新的实例,而不同的实例之间不共享属性,且实例仅在自己的 session 请求内有效,请求结束,则实例将被销毁。
-
global Session
- global Session:在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。
Spring Bean 生命周期
Spring对Bean进行实例化(相当于程序中的new Xx())
Spring将值和Bean的引用注入进Bean对应的属性中,利用IOC
如果Bean实现了BeanNameAware接口,Spring将Bean的ID传递给setBeanName()方法(实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID,一般业务中是很少有用到Bean的ID的)
如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。(实现BeanFactoryAware 主要目的是为了获取Spring容器,如Bean通过Spring容器发布事件等)
如果Bean实现了ApplicationContextAware接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,把应用上下文作为参数传入.(作用与BeanFactory类似都是为了获取Spring容器,不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入,而Spring容器在调用setBeanDactory前需要程序员自己指定(注入)setBeanDactory里的参数BeanFactory )
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法(作用是在Bean实例创建成功后对进行增强处理,如对Bean进行修改,(增加某个功能,这个很重要,AOP就是在这里实现的)
如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法(作用与6的一样,只不过6是在Bean初始化前执行的,而这个是在Bean初始化后执行的,时机不同 ) 这个时候已经再被使用了。
经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。
- 主要逻辑都在doCreate()方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应,非常重要,在后续扩展接口分析中也会涉及。
- createBeanInstance() -> 实例化
- populateBean() -> 属性赋值
- initializeBean() -> 初始化
// 忽略了无关代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 实例化阶段!
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性赋值阶段!
populateBean(beanName, mbd, instanceWrapper);
// 初始化阶段!
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
当容器关闭时调用bean的销毁方法
Spring Bean 依赖注入
● 理解基于构造函数的依赖注入
/*带参数,方便利用构造器进行注入*/
public CatDaoImpl(String message){
this. message = message;
}
<bean id="CatDaoImpl" class="com.CatDaoImpl">
<constructor-arg value=" message "></constructor-arg>
</bean>
● 理解基于设置函数的依赖注入
public class Id {
private int id;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
}
<bean id="id" class="com.id "> <property name="id" value="123"></property> </bean>
● 基于自动装配的依赖注入(配置文件里不用写依赖关系了,有相应的方法就行)
byType(类型模式),byName(名称模式)、constructor(构造函数模式)
● 基于注解的依赖注入(方法都省略了)
@Autowired @Resource
Spring AOP
AOP 把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
1、切面(aspect):一般就是一个类。切入点 + 通知,通俗点就是:在什么时机,什么地方,做什么增强!
2、横切关注点:对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点。 (相对于核心关注点来说的)
3、连接点(joinpoint):连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时、甚至修改一个字段时。虚的概念
4、切入点(pointcut):在哪些类,哪些方法上切入(where)
5、通知(advice):在方法执行的什么实际(when:方法前/方法后/方法前后)做什么(what:增强的功能),通知分为前置、后置、异常、最终、环绕通知五类。
6、目标对象:代理的目标对象。
7、织入(weave):把切面加入到对象,并创建出代理对象的过程。(由 Spring 来完成)
8、引入(introduction):在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
-
连接点和切入点的理解
连接点:连接点是一个虚拟的概念,可以理解为所有满足切点扫描条件的所有的时机。
具体举个例子:比如开车经过一条高速公路,这条高速公路上有很多个出口(连接点),但是我们不会每个出口都会出去,只会选择我们需要的那个出口(切点)开出去。
简单可以理解为,每个出口都是连接点,但是我们使用的那个出口才是切点。每个应用有多个位置适合织入通知,这些位置都是连接点。但是只有我们选择的那个具体的位置才是切点。
@Aspect
public class TransactionDemo {
@Pointcut(value="execution(* com.yangxin.core.service.*.*.*(..))")
public void point(){
}
@Before(value="point()")
public void before(){
System.out.println("transaction begin");
}
@AfterReturning(value = "point()")
public void after(){
System.out.println("transaction commit");
}
@Around("point()")
public void around(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("transaction begin");
joinPoint.proceed();
System.out.println("transaction commit");
} }