一、Spring概述
● Spring的核心概念IOC(控制反转)和AOP(面向切面编程)
二、IOC
2.1、IOC概念
2.2、Bean对象的三种创建方式
2.2.1、XML配置文件
1、将applicationContent.xml文件导入src包下resource中
?xml version="1.0" encoding="UTF-8"?>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
2、applicationContent.xml文件中属性解析
● :定义Spring中资源,将需要交给Spring控制的资源用标签定义。
id:bean对象的唯一标识,类比于new对象中的变量名。
class:交个Spring控制资源的全路径类名(包名+类名),类比与new对象中的 “new 对象名()”这个操作。
name:别名,和id的含义类似,但是可以配置多个不同的值,不同值之间用逗号隔开。
scope:配置bean对象是否是为单例(默认为单例),singleton--单例、prototype--多例
lazy-init:配置bean对象的创建时机,默认bean是在ioc容器创建时创建,可设置为true,在使用时创建。
init-method:指定初始化方法,bean对象初始化过程中会自动调用该方法。
destory-method:指定销毁方法,bean对象销毁过程中会自动调用该方法。
生成bean对象是用到的属性
3、DI(Dependency injection)
● DI是将容器创建的对象赋值给bean对象属性的过程。
● 属性值注入方式一:属性注入(通过无参构造函数+setter方法注入)
property:注入标签(set注入,需要在使用注入资源的类中声明属性的set方法)
name:标识属性名
普通值注入:value
Bean对象注入: ref
数组注入: xml <array> <value>xxx</alue> </array>
List注入:
<list>
<value>xxx</alue>
</list>
map注入: xml <map> <entry key = "" ,value = "">xxx</entry> </map>
set注入: xml <set> <value>xxx</alue> </set>
Null注入: xml <null/>
properties注入: xml <props> <prop key = ""> xxx </prop> </props>
● 属性值注入方式二:通过有参的构造函数注入。
必须存在有参构造函数
<constructor-arg>:有参构造函数注入(需要在bean对应的类中声明对应构造方法)
name/index/type:定位要为注入的属性
name:通过参数名称定位到要注入的参数
index:通过下标索引定位要注入的参数
type:通过参数类型定位要注入的参数(参数类型要保持唯一)
value:普通属性注入
ref:bean对象注入
● 属性值注入方式三:扩展方式
1、P命名空间注入:可以直接注入属性值(用于简化“属性注入”方式书写)
2、C命名空间注入:通过构造器注入(用于简化“有参构造器注入”方式书写)
xml文件头文件需要引入对应的约束才能生效
4、标签下其他重要标签
● import:在当前配置文件中导入其他配置文件
○ resource:加载的配置文件名
2.2.2、注解开发
1、在applicationContent.xml文件中配置对应的属性
<!--开启注解扫描,只有配置这个,注解才能生效!-->
<context:annotation-config/>
<!--指定要扫描的包-->
<context:component-scan base-package="com.pojo"/>
2、配置Bean
● @Component --- 标识这个类被Spring所管理。
○ Controller --- 对应的Web层
○ Service --- 对应的是服务层
○ Repositoty --- 对应的数据层
- 如果没有指定具体的id,默认的id是类名(首字母小写)
● @Scope:定义在类上,控制单例或者多例。
● @PostConstruct:定义在方法上,指定该方法为初始化方法。
● @PreDestory :定义在方法上,指定该方法为销毁方法。
3、Bean中属性注入
● @Value:普通数据注入
● @Autowired:引用类型注入
○ 查找方法:先通过Type查找,类型不唯一,通过变量名进行匹配。类型和变量名都不唯一,查看是否有一个被@Primary修饰,或者结合@Qualifier使用。
● @PropertySource:定义在类上方,用于加载properties文件中属性值。
2.2.3、java方式
1、@Configuration的使用
● 从Spring3.0,@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法会被AnnotationConfigApplicationContest或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
● @Configuration注解的配置类有如下要求:
- @Configuration不可以是final类型;
- @Configuration不可以是匿名类;
- 嵌套的configuration必须是静态类。
2、@Configuration如何加载Spring
● @Configuration配置spring并启动spring容器
// @Configuration标注在类上,相当于把该类作为Spring的xml配置文件中的<beans>,作用为:配置Spring容器(应用上下文)
@Configuration
public class TestConfiguratin{
public TestConfiguration(){
System.out.println("TestConfiguration容器启动初始化......")
}
}
相当于:.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"
</beans>
// 测试类
public class TestMain {
public static void main(String[] args) {
// @Configuration注解的spring容器加载方式,用AnnotationConfigApplicationContext替换ClassPathXmlApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
// 如果加载spring-context.xml文件:
// ApplicationContext context = new
// ClassPathXmlApplicationContext("spring-context.xml");
}
// 输出结果
"TestConfiguration容器启动初始化......"
● @Configuration启动容器+@Bean注册Bean
"@Bean标注在方法上(返回某个实例的方法),等价于spring的xml配置文件中的<bean>,作用为:注册bean对象"
// Bean类
public class TestBean {
private String username;
public void sayHello() {
System.out.println("TestBean sayHello...");
}
public String toString() {
return "username:" + this.username;
}
public void start() {
System.out.println("TestBean 初始化。。。");
}
public void cleanUp() {
System.out.println("TestBean 销毁。。。");
}
}
// 配置类
@Configuration
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
// @Bean注解注册bean,同时可以指定初始化和销毁方法
// @Scope 指定Bean的范围,是单例还是多例
// @Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")
@Bean
@Scope("prototype")
public TestBean testBean() {
return new TestBean();
}
}
注:
(1)、@Bean注解在返回实例的方法上,如果未通过@Bean指定bean的名称,则默认与标注的方法名相同;
(2)、@Bean注解默认作用域为单例singleton作用域,可通过@Scope(“prototype”)设置为原型作用域;
(3)、既然@Bean的作用是注册bean对象,那么完全可以使用@Component、@Controller、@Service、@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
● @Configuration启动容器+@Component注册Bean
"在配置类上添加对应的包扫描范围,不用定义方法,通过注解同样可以定义Bean"
// Bean类
@Component
public class TestBean {
private String username;
public void sayHello() {
System.out.println("TestBean sayHello...");
}
public String toString() {
return "username:" + this.username;
}
public void start() {
System.out.println("TestBean 初始化。。。");
}
public void cleanUp() {
System.out.println("TestBean 销毁。。。");
}
}
// 配置类
@Configuration
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
// @Bean注解注册bean,同时可以指定初始化和销毁方法
// @Scope 指定Bean的范围,是单例还是多例
// @Bean(name="testBean",initMethod="start",destroyMethod="cleanUp")
/* @Bean
@Scope("prototype")
public TestBean testBean() {
return new TestBean();
} */
}
注: 效果同@Configuration启动容器+@Bean注册Bean是一致的。
3、@Configuration如何组合多个配置类
● 在@Configuration中引入Spring的xml配置文件
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
public class WebConfig {
}
● 在@Configuration中引入其他注释配置
@Configuration
@ImportResource("classpath:applicationContext-configuration.xml")
@Import(TestConfiguration.class)
public class WebConfig {
}
● @configuration嵌套(嵌套的Configuration必须是静态类)
@Configuration
@ComponentScan(basePackages = "全路径类名")
public class TestConfiguration {
public TestConfiguration() {
System.out.println("TestConfiguration容器启动初始化。。。");
}
@Configuration
static class DatabaseConfig {
@Bean
DataSource dataSource() {
return new DataSource();
}
}
}
三、AOP
3.1、AOP概念
● AOP:面向切面编程,可以理解为由一点及面,在程序中只需修改一个地方,就可以影响很多地方,这些很多地方在一起组成一个面,故称之为面向切面编程。
● OOP:面向对象编程,可以理解为由点及点,在程序中只需修改一个地方,只可影响修改的哪个地方,故称之为面向对象编程。
3.2、AOP应用的三种方式
3.2.1、使用spring接口【springAPI接口实现】
● 定义我们需要的类继承SpringAOP相应的类
○ MethodBeforeAdvice -- 前置通知
○ AfterAdvice -- 后置通知
○ AfterReturningAdvice -- 返回值通知
○ 略。。。。
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//args:参数
//target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+method.getName());
}
}
● 在applicationContent.xml文件中配置开启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"
xsi:schemaLocation="http://www.springframework.org/schema/beanss
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="log" class="com.log.Log"/>
<!--配置aop-->
<aop:config>
<!--切入点:expression:表达式,execution(要执行的位置)-->
<aop:pointcut id="point" expression="execution(* com.service.UserServiceImp.*(..))"/>
<!--执行环绕-->
<aop:advisor advice-ref="log" pointcut-ref="point"/>
<aop:advisor advice-ref="afterlog" pointcut-ref="point"/>
</aop:config>
</beans>
这种方式不需要配置切面
3.2.2、自定类来实现AOP
● 自定义功能增强类,用于实现特定功能。
● 在applicationContent.xml文件中进行对应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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="log" class="com.log.Log"/>
<bean id="diy" class="com.diy.DiyPointcut">
</bean>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="diy"> // ref="diy" 引用在Spring中定义好的Bean
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.service.UserServiceImp.*(..))"/>
// 定义通知方式
<aop:before method="before" pointcut-ref="point"/> // 在diy中定义好的方法
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
3.2.3、注解方式实现AOP
● 在applicationContent.xml文件中进行对应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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="ann" class="com.diy.Annotation"></bean>
// 开启注解支持
<aop:aspectj-autoproxy/>
<!--注册bean-->
<bean id="userservice" class="com.service.UserServiceImp"></bean>
</beans>
● 自定义功能增强类,用于实现特定功能。
○ @Aspect --- 标注这类是一个切面
○ @Before("execution(* com.service.UserServiceImp.(..))") --- 标注这个方法通知方式以及切入点
○ @Pointcut("execution( com.service.UserServiceImp.*(..))") --- 配置切点
○ 略。。。。。。
3.3、AOP原理(代理模式)
3.3.1、为什么要用代理模式?
● 我们可以使用代理对象来替代真实对象的访问,这个就能在不修改目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
● 代理模式的主要作用是扩展目标对象的功能,比如说在目标对象的某个方法执行前后可以增加一些自定的操作。
3.3.2、静态代理
● 静态代理:我们对目标对象的每个方法的增强都是手动完成的,从JVM层面上来说,静态代理在编译时就以及将接口、实现类、代理类这些都变成了一个个实际的class文件。
● 静态代理实现步骤:
○ 定义一个接口及其实现类
○ 创建一个代理类同样是实现这个接口。
○ 将目标对象注入代理类,然后在代理类的对应方法调用目标类中的方法。
● 代码实现
// 定义一个租房接口
public interfare Rent{
void rentHous();
}
// 定义租房实现(目标对象)
public class RentImpl implements Rent{
@Override
public void rendHouse() {
System.out.println("出租房子");
}
}
// 定义代理类,实现同一个接口
public class ProxyRent implements Rent{
private Rent rent;
//定义有参构造
public ProxyRent(Rent rent) {
this.rent = rent;
}
@Override
public void rendHouse() {
System.out.println("操作前");// 自定义操作
rent.rentHoues(); // 调用Rent接口的方法
}
}
// 定义Test类
public class Test {
public static void main(String[] args) {
ProxyRent proxy = new ProxyRent(new RentImpl());
proxy.rendHouse();;
}
}
● 静态代理的优点:
○ 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
● 静态代理缺点:
○ 得为每一个服务都创建代理类,工作量大且不易管理,耦合性强。
3.3.3、JDK动态代理机制
● JDK动态代理:只能代理实现了接口得类
● 动态代理: Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
● Proxy类中使用频率最高的方法是:newProxyInstance( ),这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?> interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
参数解析
1、loader : 类加载器,用于加载代理对象。
2、interfaces:被代理类实现的一些接口。
3、h:实现了 InvocationHandler接口的对象。
● 要实现动态代理的话,还必须要实现InvocationHandler来自定义处理逻辑,当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口类的invoker方法来调用。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoker() 方法参数解析:
1、proxy:动态生成的代理类
2、method:与代理类对象调用的方法相对应
3、args:当前method方法的参数
● 通过Proxy类的newProxyInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHanler接口的类的invoke()方法,可以在invoke()方法中自定义处理逻辑。
● JDK 动态代理类使用步骤:
1、定义一个接口及其实现类;
2、自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3、通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
● 代码实现
//定义一个接口
public interface Rent {
void RentHouse();
}
// 定义这个接口的实现类
public class RentImpl implements Rent {
@Override
public void RentHouse() {
System.out.println("我就想租房子");
}
}
// 自定义类实现InvocationHandler接口并重写invoke方法
public class JDKInvocationHandler implements InvocationHandler {
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
Object result = method.invoke(target, args);
return result;
}
public void seeHouse(){
System.out.println("看房子");
}
}
// 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
public class Test {
public static void main(String[] args) {
Rent rent = new Rent1();
JDKInvocationHandler j = new JDKInvocationHandler(rent);
Rent proxy = (Rent) Proxy.newProxyInstance(rent.getClass().getClassLoader(), rent.getClass().getInterfaces(), j);
proxy.RentHouse();
}
}
3.3.4、CGLIB 动态代理机制
● CGLIB动态代理:CGLIB 通过继承方式实现代理。
● CGLIB:CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。
● 动态代理 : 在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
● MethodInterceptor 接口
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
// intercept方法参数解析
1 obj:被代理的对象(需要增强的对象)
2 method:被拦截的方法(需要增强的方法)
3 args:方法入参
4 proxy:用于调用原始方法
● 可以通过Enhancer类来动态获取代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor中的intercept方法.
● CGLIB 动态代理类使用步骤
1 定义一个类;
2 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
3 通过 Enhancer 类的 create()创建代理类;
● 代码演示
// 定义一个类
public class Rent {
public void RentHouse() {
System.out.println("唉,就是玩");
}
}
// 自定义类实现MethodInterceptor接口并重写 intercept 方法.
public class DebugMethodInterceptor implements MethodInterceptor {
/**
*
* @param o 代理对象(增强的对象)
* @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" + method.getName());
Object object = methodProxy.invokeSuper(o, objects);
return object;
}
}
//测试
public class Test {
public static void main(String[] args) {
CglibProxyFactory cglibProxyFactory = new CglibProxyFactory();
RentImpl proxy = (RentImpl) cglibProxyFactory.getProxy(RentImpl.class);
proxy.RentHouse();
}
// 通过Enhancer 类的 create()创建代理类;
static class CglibProxyFactory{
public Object getProxy(Class<?> clazz){
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(clazz.getClassLoader());
enhancer.setSuperclass(clazz);
enhancer.setCallback(new DebugMethodInterceptor());
return enhancer.create();
}
}
}
四、Spring源码分析
4.1、IOC及Bean的生命周期
4.1.1、【流程图】
4.1.2、容器启动阶段
● 首先会通过某种途径加载配置信息,大部分情况下,容器需要依赖Spring自定义的某些工具类(BeanDefinitionReader)对加载的配置信息进行解析和分析,并将解析后的信息编组为对应的BeanDefinition,最后把这些保存了Bean定义信息的BeanDefinition,注册到相应的Be按DifinitonRegistry,这样容器启动工作就完成了。
4.1.3、干预容器启动
● Spring提供了一种叫做BeanFactoryPostProcesson的扩展机制,允许在容器实例化对象之前,对注册到容器的BeanDefinition所保存信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做的一些额外的操作,比如修改其中Bean定义的某些属性,为beand定义增加其他信息等。
4.1.4、Bean实例化阶段
● 【流程图】
● 实现流程
a. Spring 启动,查找并加载需要被 Spring 管理的 Bean,并实例化 Bean。
b. 利用依赖注入完成 Bean 中所有属性值的配置注入。
c. 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前 Bean 的 id 值。
d. 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。
e. 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。
f. 如果 Bean 实现了 BeanPostProcessor 接口,则 Spring 调用该接口的预初始化方法 postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要,Spring 的 AOP 就是利用它实现的。
g. 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。
h. 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。
i. 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法 postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。
j. 如果在 中指定了该 Bean 的作用域为 singleton,则将该 Bean 放入 Spring IoC 的缓存池中,触发 Spring 对该 Bean 的生命周期管理;如果在 中指定了该 Bean 的作用域为 prototype,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该 Bean。
k. 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法销毁 Bean;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。
4.2、循环依赖
4.2.1、循环依赖定义
● java中的循环依赖分为两种,一种是构造器的循环依赖,另一种是属性的循环依赖。
○ 构造器的循环依赖:这种循环依赖没有什么解决办法,因为JVM虚拟机在对类进行实例化的时候,需要实例化构造器的参数,而由于循环引用这个参数无法提前实例化,故只能抛出异常。
○ 属性的循环依赖:在构造器执行的时候为完成属性的注入,而在调用方法的时候已经完成了注入。
4.2.2、解决循环依赖
● 【流程图】
● Spring 单例 Bean 的创建 中介绍介绍了使用三级缓存。
○ singletonObjects: 一级缓存,存储单例对象,Bean 已经实例化,初始化完成。
○ earlySingletonObjects: 二级缓存,存储 singletonObject,这个 Bean 实例化了,还没有初始化。
○ singletonFactories: 三级缓存,存储 singletonFactory。