看了一段时间视频教程,还是需要总结一下,不然很容易忘的,笔记如下。
一、基本概念
1、三大框架SSM:Spring、SpringMvc、Mybaits,现在用的比较多是springboot
2、程序间的依赖关系:类之间的依赖、方法间的依赖,spring理念就是最大限度的降低耦合
3、IOC(控制反转)
但是想提一点:框架如何知道你需要哪些对象呢?框架是不知道,所以还得需要程序员告诉框架,我后面需要用到哪些对象,你要提前帮我创建好。
IoC作用:降低程序间的耦合度,耦合度不能消除,只能降低
4、AOP
AOP面向切面编程,是一种编程思想,它减少重复代码,提高开发效率,维护方便(这也是动态代理模式优点)
AOP底层是利用动态代理,Spring实现动态代理方式有两种:jdk proxy和cglib
Spring 的AOP中有很多相关术语,这里用通俗的话进行说明(老外就是喜欢创造名词):
名词 | 解释 |
Aspect(切面) | 实际就是动态代理模式中代理对象(增强的类) |
Pointcut(切入点) | 实际是被代理对象中的方法,要被增强的方法 |
Advice(通知) | 实际为代理对象中增强逻辑的那部分代码。有前置通知(Before)、后置通知(AfterReturning)、异常通知、最终通知(After)、环绕通知(指方法前后都增强) |
我个人感觉,Spring实现的AOP并不是很优雅,不方便,增加了用户的使用难度。
5、动态代理
但是子类不能是final类型的类,因为cglib是通过继承方式实现子类来增强)
6、自动装配
自动装配就是spring框架根据属性名称或者属性类型自动识别并且注入,即用户不需要手动设置property等则为自动装配。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 自动装配 byName方式, 类中的属性名字必须与bean id一样 -->
<bean id="emp" class="com.example.Employee" autowire="byName">
<!-- <properties name="dept" ref="dept"/> 手动状态 -->
</bean>
<!-- 自动装配 byType方式 -->
<bean id="emp2" class="com.example.Employee" autowire="byType">
<!-- <properties name="dept" ref="dept"/> 手动状态 -->
</bean>
<bean id="dept" class="com.example.Department">
</bean>
</beans>
二、Spring容器接口
Spring创建容器有两个接口:AppliactionContext和BeanFactory,两者对比:
接口 | 特点 | 方法 |
AppliactionContext | 1、创建对象采用立即加载策略,即立刻实例化对象 2、创建的对象是单例的 | 以下三个类均实现了该接口 ClassPathXmlApplicationContext:读取classpath路径下的xml文件 FileSystemXmlApplicationContext:读取文件系统下xml文件 AnnotationConfigApplicationContext:用于读取注解创建容器 |
BeanFactory | 创建对象采用延迟加载 AppliactionContext继承了BeanFactory | ClassPathResource classPathResource = new ClassPathResource("beans.xml"); BeanFactory beanFactory = new XmlBeanFactory(classPathResource); |
三、Spring Bean对象
3.1、bean的分类
Spring中有两种Bean:普通bean,工厂bean(FactoryBean)
普通bean:用于的定义普通类
注入类型和获取到的类型不一致,真正的实例对象是在getBean的时候才会创建
如下代码所示:
//蜘蛛侠玩具
public class SpiderMan {
@Override
public String toString() {
return "This is Spider Man!";
}
}
//玩具工厂, 生产SpiderMan
public class ToyFactory implements FactoryBean<SpiderMan> {
@Override
public SpiderMan getObject() throws Exception {
return new SpiderMan();
}
@Override
public Class<?> getObjectType() {
return SpiderMan.class;
}
@Override
public boolean isSingleton() {
return false;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 注入玩具工厂 -->
<bean id="toyFactory" class="com.example.ToyFactory"/>
</beans>
public class MyTest {
@Test
public void aaa() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 这里只能写SpiderMan,不能写ToyFactory.class,会报错
SpiderMan spiderMan = context.getBean("toyFactory", SpiderMan.class);
System.out.println(spiderMan);
}
}
源码底层是怎么实现的呢?
1)通过上面的配置可知,注入到容器的时候,类型为ToyFactory类型
2)在获取真正实例对象的时候,框架进行了判断,如果容器中bean,实现了FactoryBean接口,则去调用接口中getObject方法创建真的实例对象,所以在这个地方可能性能有一些损失
3.2、bean对象创建方式
Spring提供两种方式创建bean对象:一种是xml方式,一种是注解方式(jdk1.8以后),目前使用的最多还是注解方式。关于注解方式,可参考《spring家族-spring基础知识注解版》
3.2.1、基于xml三种创建方式
在idea中resources目录右键,选择xml,spring config就可以创建xml文件
基于xml文件有三种方式:直接使用构造方法、利用工厂创建对象、利用工厂静态方法创建对象
<!-- 构造方法 -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
</bean>
<!-- 利用工厂 -->
<bean id="myInstanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="myInstanceFactory" factory-method="getAccountService"></bean>
<!-- 利用工厂静态方法 -->
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
3.2.2、将第三方一个纯java普通类注入到spring容器
有些时候引入一个第三方jar包,需要将某个普通java类(非接口,没有任何注解,纯java类)注入的容器中,那么我们应该如何做呢?xml方式和注解方式均可
通过xml方式:
1)在idea中resources目录右键,选择xml,spring config就可以创建xml文件,名字随意,这里叫spring-bean.xml
2)xml文件内容
<?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: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/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--
spring的属性加载器,加载properties文件/yml文件中的属性
通过获取配置文件中的配置项
-->
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:application.properties"/>
<property name="fileEncoding" value="utf-8" />
</bean>
<!--
<bean id="yamlProperties" class="org.springframework.beans.factory.config.YamlPropertiesFactoryBean">
<property name="resources" value="classpath:application-demo.yml"/>
</bean>
<context:property-placeholder properties-ref="yamlProperties"/>
-->
<bean name="zkClient" class="com.example.ZKClientImpl">
<constructor-arg name="zkServers" value="${zk.address}"/>
<constructor-arg name="connectionTimeout" value="${zk.port}"/>
</bean>
<!--
<bean name="" class="com.example.ZKClientImpl">
<constructor-arg name="zkServers" value="127.0.0.1"/>
<constructor-arg name="connectionTimeout" value="2181"/>
</bean>
-->
</beans>
3)在springboot启动进行注入,注意这里classpath,指的是resources根目录
public class DemoApplication {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-bean.xml");
ZKClientImpl client = context.getBean("zkClient", ZKClientImpl.class);
System.out.println(client);
context.close();
}
}
通过注解方式:
注解方式比较简单,只需要创建一个配置类,用@Configuration标注,然后用@Bean标注一个方法,具体内容如下:
@Configuration
public class ZkClientConfig {
@Value("${zkClusters.address}")
private String cluster;
@Value("${zkClusters.timeout}")
private int timeout;
private ZkClientService zkClient;
@Bean
ZkClientService createZkClient() {
zkClient = new ZKClientImpl(cluster, timeout);
return zkClient;
}
@PreDestroy
private void destroy() {
zkClient.close();
}
}
3.3、bean对象作用域范围
在xml文件中bean节点下面有一个属性scope,代表作用范围,取值如下:
singleton | 单例的 默认值 |
prototype | 多例的 |
request | 作用于web应用请求范围 |
session | 作用于web应用会话范围 |
global-session | 作用于集群环境的会话范围(全局范围),如果不是集群则同session属性 |
3.4、bean对象生命周期(重点)
3.4.1、生命周期
①实例化(即new)--> ②属性赋值(执行setter方法)-->③ 初始化(执行 init-method)--> ④使用bean对象 --> ⑤销毁 (执行destroy-method)。注意:init-method、destroy-method是在bean标签中的一个属性,这两个属性可以指定类中的方法,我们也可以用注解@PostConstruct,@PreDestroy进行标注初始化和销毁的两个方法
3.4.2、生命周期扩展性
在面试的时候,上面5步骤可能不够完善,面试官可能会追问,这个流程有什么扩展性?答案是有的。
经过扩展后,生命周期变为:实例化(即new)-->属性赋值(执行setter方法)--> bean后置处理器的before方法 --> 属性后置处理afterPropertiesSet -->初始化(执行 init-method)--> bean后置处理器的after方法 --> 使用bean对象 --> 销毁 (执行destroy-method)
这个地方需要去理解,千万不要死记硬背,我曾经尝试过背,但过一段时间就又忘了。后来我突然想明白了一件事情:
1)原始的生命周期5个状态中,哪个步骤可用于扩展(增强)呢?
答:①实例化,这个阶段不行,这个过程完全由jvm操作,我们没有办法介入
④使用bean对象,对象都在使用了,我们在这个时候去扩展已经来不及了
⑤销毁,对象都要销毁了,我们也没必要在去扩展了
所以剩下只有②属性赋值,③ 初始化
2)属性赋值阶段扩展-接口InitializingBean
如果存在后置处理器,则先执行后置处理器before方法,再后执行afterPropertiesSet
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
3)初始化阶段扩展-接口BeanPostProcressor
具体在属性赋值后)和初始化后进行扩展。Spring为我们提供了一个接口BeanPostProcressor(后置处理器)。
这里是BeanPostProcessor接口,不是BeanFactoryPostProcessor
注意事项:
1)这个接口需要单独定义一个实现类,并且注入到Spring中
2)以后所有Bean在初始化阶段,前后,都会执行后置处理器中的before,after方法
3)后置处理可以有多个
3.4.3、生命周期扩展性应用
// 创建bean,实现InitializingBean接口
public class Orders implements InitializingBean {
private String oname;
public Orders() {
System.out.println("第一步,执行Orders无参数构造");
}
public void setOname(String oname) {
this.oname = oname;
System.out.println("第二步,Set方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("第四步,AfterProperties方法");
}
public void initMethod() {
System.out.println("第五步,initMethod方法");
}
public void destroyMethod() {
System.out.println("第八步,destroyMethod方法");
}
@Override
public String toString() {
return "Orders{" +
"oname='" + oname + '\'' +
'}';
}
}
//创建Bean后置处理器,实现BeanPostProcessor接口
public class MyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//这里根据bean类型,做一些特殊操作
/*
* if (bean instance XXX && beanName.startsWith("AAA")) {
* //进行增强处理
* return bean;
* } else {
* //do nothing
* return bean;
* }
*/
System.out.println("第三步,后置处理器前置方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("第六步,后置处理器后置方法");
return bean;
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 注入bean -->
<bean id="order" class="com.example.Orders" init-method="initMethod" destroy-method="destroyMethod">
<property name="oname" value="jd-order"/>
</bean>
<!-- 注入后置处理器 -->
<bean id="myBeanProcessor" class="com.example.MyBeanProcessor"/>
</beans>
public class MyTest {
@Test
public void testLife() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Orders order = context.getBean("order", Orders.class);
System.out.println("第7步,应用bean对象,order="+order);
context.close();
}
}
/*
输出打印:
第一步,执行Orders无参数构造
第二步,Set方法
第三步,后置处理器前置方法
第四步,AfterProperties方法
第五步,initMethod方法
第六步,后置处理器后置方法
第7步,应用bean对象,order=Orders{oname='jd-order'}
第八步,destroyMethod方法
*/
注意bean生命周期最后一个小知识点:假设分别注入A,B,C三个对象,执行构造器方法顺序是A,B,C,然而执行销毁方法顺序是反的,C,B,A。
四、IOC注入 - xml方式
上面说了IoC实际是把对象的创建、属性初始化工作交给框架。这里的IoC注入实际上就是属性初始化流程。那么spring有两种注入方式:基于xml文件和基于注解的。
4.1、基于xml的注入
基于xml注入分为:基于构造方法和基于setter方法
<!-- 基于构造方法 其中有一个引用类型Date -->
<bean id="date-now" class="java.util.Date"></bean>
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="date-now"></constructor-arg>
</bean>
<!-- 基于setter方法 -->
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<!-- java中代码为setUserName函数 -->
<property name="userName" value="TEST"></property>
<!-- java中代码为setAge函数 -->
<property name="age" value="19"></property>
<!-- java中代码为setBirthday函数 -->
<property name="birthday" ref="date-now"></property>
</bean>
当然也支持复杂类型注入,例如Map,List等,例如:
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myMap"> <!-- Map类型 -->
<map>
<entry key="test1" value="value1"></entry>
<entry key="test2"><value>value2</value></entry>
</map>
</property>
<property name="myProps"> <!-- Properties类型 -->
<props>
<prop key="myProps1">props1-value1</prop>
<prop key="myProps2">props2-value2</prop>
</props>
</property>
<property name="myStrs"> <!-- 数组类型 -->
<array>
<value>Array-AAA</value>
<value>Array-bbb</value>
</array>
</property>
<property name="myList"> <!-- List类型 -->
<list>
<value>List-AAA</value>
<value>List-bbb</value>
</list>
</property>
<property name="mySet"> <!-- Set类型 -->
<set>
<value>Set-AAA</value>
<value>Set-bbb</value>
</set>
</property>
</bean>
五、Spring AOP
步骤:
- 定义要增强功能的类,例如class A,对于xml文件来说定义一个bean
- 定义如何增强功能类,例如class B,用于增强class A的功能,对于xml文件来说定义一个bean
- 定义切面以及切入点aop:config、aop:aspect以及通知类型
aop:before - - 前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义是对业务层中那些方法增强
aop:after-returning - - 后置通知
aop:after-throwing - - 异常通知
aop:after 最终通知
aop:round环绕通知
六、切入点表达式
切入点表达式作用:用于表示对类中某个方法进行增强
切入表达式格式:execution([权限修饰符][返回类型][类全路径][方法名称][参数列表])
切入点表达式举例:
- 全通配写法: *.*..*.*(..) ==> 用于整包 整类 所有方法增强
- 返回值可以使用通配符,表示任意返回值,例如:* com.itheima.service.impl.AccountServiceImpl.saveAccount()
- 包名可以使用通配符,表示任意包,但是有几级包就需要写几个*,例如:* *.*.*.*.AccountServiceImpl.saveAccount() 也可以写成 * *..AccountServiceImpl.saveAccount()
- 函数有形参,可以用..用于表示,例如:* com.itheima.service.impl.*.*(..)
xml文件中定义切入点表达式,可以被通知类型中进行引用
<aop:pointcut id=”” expression=””>