Spring学习


一、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"?>

  
    



    



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方法注入) :注入标签(set注入,需要在使用注入资源的类中声明属性的set方法) name:标识属性名 普通值注入:value Bean对象注入: ref 数组注入: http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"

// 测试类
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类
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






    
    
    
    
    

这种方式不需要配置切面

3.2.2、自定类来实现AOP
● 自定义功能增强类,用于实现特定功能。
● 在applicationContent.xml文件中进行对应AOP的配置





 

    
     // ref="diy" 引用在Spring中定义好的Bean
        
        
        // 定义通知方式
          // 在diy中定义好的方法
         
    

3.2.3、注解方式实现AOP ● 在applicationContent.xml文件中进行对应AOP的配置,开启注解支持

// 开启注解支持



● 自定义功能增强类,用于实现特定功能。 ○ @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。