Spring——学习笔记

一、Spring

  1. IOC:控制反转,把创建对象过程交给spring进行管理
  2. AOP:面向切面,不修改源代码进行功能增强
    在这里插入图片描述

二、Spring入门

  1. maven导入spring
  2. 创建普通用户类加个方法
public class User {
    public void add(){
        System.out.println("add.....");
    }
}
  1. 创建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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user" class="com.atguigu.spring5.User"></bean>
</beans>
    @Test
    public void testAdd(){
        // 1 加载spring 配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("spring5/bean1.xml");
        // 2 获取配置创建的对象
        User user = context.getBean("user", User.class);
        System.out.println(user);
        user.add();
    }

在这里插入图片描述

三、IOC

1. 原始方式

在这里插入图片描述
耦合度太高了

2.工厂模式

在这里插入图片描述
有一个工厂类,降低了service类和dao类的耦合度

3.IOC解耦

在这里插入图片描述

  • xml文件致命创建对象的Dao类与对象名,第一步的那句话就是:我有一个dao类在com.atguigu.UserDao路径下!
  • 第二步:
    1. String classValue = class属性值
      实际的语句是 ApplicationContext context = new ClassPathXmlApplicationContext("spring5/bean1.xml");
      通过这句话,我们就能得到“com.atguigu.UserDao”
    2. 实际的语句就是:通过反射获取对象
      // 2 获取配置创建的对象
      User user = context.getBean(“user”, User.class);
  • 这样比工厂模式进一步降低了耦合度,因为如果要更改,仅需更改xml文件即可,完全不需要动其他的类。

4.容器——对象工厂

spring提供实现IOC容器的两个接口:

  1. BeanFactory
    是Spring内部的使用接口,不提供开发人员进行使用。
  2. ApplicationContext
    BeanFactory接口的子接口,提供更多更强大的能力,一般由开发人员进行使用。
实现IOC的方式位置不同
BeanFactorySpring内部的使用接口加载配置文件时不会创建对象
ApplicationContextBeanFactory接口的子接口,提供更多更强大的能力,一般由开发人员进行使用加载配置文件就创建了对象
  • ctrl+H查看ApplicationContext实现类
    在这里插入图片描述
    FileSystem…:要写好全路径,比如C盘/program/.....
    ClassPath…:在src下的路径,比如XXXX.xml直接在src下,就直接写“XXXX.xml”

四、bean管理

1. 基于xml创建对象

	<!--1 配置User对象创建-->
    <bean id="user" class="com.gaoth.spring5.User"></bean>

id:唯一标识
class:路径

2.基于xml注入属性

DI(依赖注入):在IOC创建完对象以后,利用DI注入属性

  1. set方法注入属性
    即在类中添加setget方法(lombok),再在xml文件中写
  2. 有参构造器注入属性
    类中写好有参构造器,然后再在xml中写好

3.两种Bean

  1. 普通bean:在配置文件中定义 bean 类型就是返回类型。即上面演示过的那种。
  2. 工厂bean(FactoryBean):在配置文件定义 bean 类型可以和返回类型不一样。
  3. 工厂bean的实现步骤:
    1)创建类,让这个类作为工厂 bean,实现官方接口 FactoryBean
    2)重写方法,在实现的方法中定义返回什么类型
public class MyBean implements FactoryBean<Course> {
    //定义返回bean
    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setCname("abc");
        return course;
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
}
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean">
</bean>
    @Test
    public void test3() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
        Course course = context.getBean("myBean", Course.class);
        System.out.println(course);
    }

4.bean实例

1)默认单例模式
2)可以更改,在xml文件中的scope属性:singleton和prototype
在这里插入图片描述
singleton在加载配置文件时就创建好了单例对象
prototype在调用getBean方法的时候创建多实例对象

5. bean生命周期

(1)通过构造器创建 bean 实例(无参数构造)
(2)为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
(3)调用 bean 的初始化的方法(需要进行配置初始化的方法)
(4) bean 可以使用了(对象获取到了)
(5)当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
在这里插入图片描述
在这里插入图片描述

请参考bean生命周期完整版

6. xml自动装配

  1. byName
<!--实现自动装配 
    bean 标签属性autowire,配置自动装配 
    autowire 属性常用两个值: 
        byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 
        byType 根据属性类型注入 
--> 
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"> 
    <!--<property name="dept" ref="dept"></property>--> 
</bean> 
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean> 
  1. byType
<!--实现自动装配 
    bean 标签属性autowire,配置自动装配 
    autowire 属性常用两个值: 
        byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样 
        byType 根据属性类型注入 
--> 
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"> 
    <!--<property name="dept" ref="dept"></property>--> 
</bean> 
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean> 

7. 外部属性文件.properties

比如数据库的地址,密码。。。

  1. 配置druid连接池
    (1)maven导入druid依赖
    (2) 原本必须这样在xml文件中写
<!--直接配置连接池--> 
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> 
    <property name="url" value="jdbc:mysql://localhost:3306/userDb"></property> 
    <property name="username" value="root"></property> 
    <property name="password" value="root"></property> 
</bean> 

(3)直接在文件中写
在这里插入图片描述

五、注解

1. 概念

代码的特殊标记。
格式:@注解名称(属性名称=属性值, 属性名称=属性值…)
(方法、属性、类)上面都可以加注解
目的:简化xml配置

2. Spring的注解

(1)@Component:普通的注解
(2)@Service:业务逻辑层/service层
(3)@Controller:Web层
(4)@Repository:DAO层/持久层

上面四个注解功能是一样的,都可以用来创建bean 实例!

3. 注解实现对象创建

  1. 引入aop依赖
  2. 开启组件扫描,在xml文件中添加
<!--开启组件扫描 
    1 如果扫描多个包,多个包使用逗号隔开 
    2 扫描包上层目录 
--> 
<context:component-scan base-package="com.atguigu"></context:component-scan>
  1. 直接写,注解的括号及括号的内容可以不写,默认是将其标注的类的首字母小写,作为名字
    在这里插入图片描述

4.注解实现属性注入

(1)@Autowired:根据属性类型进行自动注入
第一步 把 service 和 dao 对象创建,在 service 和 dao 类添加创建对象注解
第二步 在 service 注入 dao 对象,在 service 类添加 dao 类型属性,在属性上面使用注解

@Service 
public class UserService { 
    //定义 dao 类型属性 
    //不需要添加 set 方法 
    //添加注入属性注解 
    @Autowired 
    private UserDao userDao; 
 
    public void add() { 
        System.out.println("service add.	"); 
        userDao.add(); 
    } 
} 

(2)@Qualifier:根据名称进行注入
这个@Qualifier 注解的使用,和上面@Autowired 一起使用
如果存在一个接口的多个实现类,就再根据名称分辨一下!


//定义 dao 类型属性 
//不需要添加 set 方法

//添加注入属性注解 
@Autowired //根据类型进行注入 
@Qualifier(value = "userDaoImpl1")
 //根据名称进行注入private UserDao userDao; 


(3)@Resource:可以根据类型注入,可以根据名称注入,javax包的不是spring公司的

@Resource //根据类型进行注入 
or
@Resource(name = "userDaoImpl1") //根据名称进行注入private UserDao userDao; 

(4)@Value:注入普通类型属性

@Value(value = "abc") 
private String name; 

5.完全注解开发

xml不写了,全写注解!
在这里插入图片描述
创建配置类,用于代替xml配置文件@Configuration
这时spring也不知道这是个啥类?用注解告诉他

@Configuration //作为配置类,替代 xml 配置文件
@ComponentScan(basePackages = {"com.atguigu"}) 
public class SpringConfig { 
} 


以上的文字就代替了xml文件中的那句自动扫描的语句。删除xml文件,再见!
测试方法要改一下:

//编写测试类@Test 
public void testService2() { 
    //加载配置类 
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); 
    UserService userService = context.getBean("userService", UserService.class); 
    System.out.println(userService); 
    userService.add(); 
} 

六、AOP面向切面编程

1. 概念

业务逻辑部分进行隔离!
降低业务逻辑之间的耦合度!
不修改源代码的情况下,添加新的功能,这就是AOP

2. AOP底层是:动态代理

即现在想要给“登陆”中添加一个新功能,又不想去更改原来的代码,这是应用动态代理,搞一个具有原来登陆功能的代理对象,再在上面去做!

  1. 有接口的:使用JDK动态代理
    在这里插入图片描述

  2. 没有接口的情况:使用CGLIB动态代理
    在这里插入图片描述

  3. JDK动态代理在这里插入图片描述

  4. JDK动态代理代码实现
    (1)创建接口,定义方法

public interface UserDao {
    public int add(int a,int b);
    public String update(String id);
}

(2)创建接口实现类,实现方法

public class UserDaoImpl implements UserDao {
    @Override
    public int add(int a, int b) {
        System.out.println("add方法执行了.....");
        return a+b;
    }

    @Override
    public String update(String id) {
        System.out.println("update方法执行了.....");
        return id;
    }
}

(3)使用 Proxy 类创建接口代理对象

public class JDKProxy {

    public static void main(String[] args) {
        System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
        //创建接口实现类代理对象
        Class[] interfaces = {UserDao.class};

        UserDaoImpl userDao = new UserDaoImpl();
        UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao));
        int result = dao.add(1, 2);
        System.out.println("result:"+result);
    }
}

//创建代理对象代码
class UserDaoProxy implements InvocationHandler {

    //1 把创建的是谁的代理对象,把谁传递过来
    //有参数构造传递
    private Object obj;
    public UserDaoProxy(Object obj) {
        this.obj = obj;
    }

    //增强的逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法之前执行...."+method.getName()+" :传递的参数..."+ Arrays.toString(args));

        //被增强的方法执行
        Object res = method.invoke(obj, args);

        //方法之后
        System.out.println("方法之后执行...."+obj);
        return res;
    }
}

3. AOP 术语

在这里插入图片描述

4.AspectJ实现AOP

  1. 引入依赖

  2. 切入点表达式:
    (1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强
    (2)语法结构: execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]) )
    举例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
    execution(* com.atguigu.dao.BookDao.add(..))
    举例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
    execution(* com.atguigu.dao.BookDao.* (..))
    举例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
    execution(* com.atguigu.dao.*.* (..))

  3. 创建类,定义方法

//被增强的类
@Component
public class User {
    public void add() {
        int i = 10/0;
        System.out.println("add.......");
    }
}

  1. 编写增强类
//增强的类
public class UserProxy {
    public void before() {//前置通知
        System.out.println("before......");
    }
}

  1. 开启注解扫描(配置类或者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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="com.atguigu.spring5.aopanno"></context:component-scan>

</beans>


在这里插入图片描述

<!-- 开启 Aspect 生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

//增强的类
@Component
@Aspect //生成代理对象
public class UserProxy {
    //前置通知
    //@Before 注解表示作为前置通知
    @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void before() {
        System.out.println("before.........");
    }
    
    //后置通知(返回通知)
    @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterReturning() {
        System.out.println("afterReturning.........");
    }

    //最终通知
    @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void after() {
        System.out.println("after.........");
    }

    //异常通知
    @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterThrowing() {
        System.out.println("afterThrowing.........");
    }

    //环绕通知
    @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws
            Throwable {
        System.out.println("环绕之前.........");
        //被增强的方法执行
        proceedingJoinPoint.proceed();
        System.out.println("环绕之后.........");
    }
}


在这里插入图片描述
完全注解,xml文件替换为:

@Configuration
@ComponentScan(basePackages = {"com.atguigu"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}

七、Spring源码阅读

1)IOC容器整体视图

在这里插入图片描述

核心容器:spring-beans,spring-core,spring-context和spring-expression组成

  • spring-beans,spring-core是实现了控制反转依赖注入的容器功能
  • 控制翻转IoC:将由程序代码直接操控的对象的调用权交给IOC容器,通过容器来实现对象组件的装配和管理
  • 依赖注入DI:接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。
  • BeanFactory:工厂模式,但是BeanFactory容器实例化后并不会自动实例化 Bean,只有当 Bean 被使用时 BeanFactory 容器才会对该 Bean 进行实例化与依赖关系的装配
  • spring-context模块构架于 spring-beans和spring-core核心包之上,拓展了BeanFactory生成Spring的上下文环境。
  • ApplicationContext是该模块的核心接口继承了BeanFactory,与 BeanFactory 不同,ApplicationContext 容器实例化后会自动对所有的单例 Bean 进行实例化与依赖关系的装配,使之处于待用状态。

AOP:spring-aop,spring-aspects和spring-instrumentation组成

  • spring-aop是AOP实现模块,以JVM的动态代理技术为基础,然后设计出了一系列的Aop横切实现,比如前置通知、返回通知、异常通知等

数据访问:spring-jdbc,spring-tx,spring-orm

  • spring-jdbc模块是Spring 提供的JDBC抽象框架的主要实现模块,用于简化Spring JDBC
  • spring-tx模块是Spring JDBC事务控制实现模块
  • spring-orm模块是ORM框架支持模块,主要集成 Hibernate, Java Persistence API (JPA) 和 Java Data Objects (JDO) 用于资源管理、数据访问对象(DAO)的实现和事务策略。

Web:spring-web,spring-webmvc

  • spring-web模块为Spring提供了最基础Web支持,主要建立于核心容器之上,通过Servlet或者Listeners来初始化IoC容器,也包含一些与Web相关的支持
  • spring-webmvc模块众所周知是一个的Web-Servlet模块,实现了Spring MVC(model-view-controller)的Web应用

2)IOC容器初始化

初始化包括四个步骤:
1. 读取xml文件
2. 获取Document对象
3. 解析Document对象创建BeanDefination实例
4. 注册BeanDefination

(一)容器的初始化入口

  1. ApplicationContext实现初始化
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  1. XMLBeanFactory初始化
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
  1. 以上两个内部都是采用了DefaultListableBeanFactory接口实现初始化的
    看源码:
    在这里插入图片描述
    其中的super方法就是DefaultListableBeanFactory的创建。
    步骤均为:
    在这里插入图片描述
Resource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

创建XmlBeanDefinitionReader读取器并绑定BeanFactory,将xml中解析出的resource注册进XmlBeanDefinitionReader

(二)读入XML资源文件

可以看到所有的IOC容器的初始化都集成在了loadBeanDefinitions这个函数中,非常重要:
在这里插入图片描述
看源码:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(new EncodedResource(resource));
}

再看:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if(this.logger.isInfoEnabled()) {
        this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }
    Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if(currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if(!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
        try {
            InputStream ex = encodedResource.getResource().getInputStream();
            try {
                InputSource inputSource = new InputSource(ex);
                if(encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
                
            } finally {
                ex.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if(((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
        return var5;
    }
}

调用doLoadBeanDefinitions()方法开始解析。

(三) 从资源文件中获取Document对象

doLoadBeanDefinitions调用doLoadDocument()方法解析传入的XML配置文件资源,生成对应的Document实例,并根据返回的Document对象生成BeanDefinitons注册进BeanFactory。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
    try {
        Document ex = this.doLoadDocument(inputSource, resource);
        return this.registerBeanDefinitions(ex, resource);
    } catch (BeanDefinitionStoreException var4) {
        throw var4;
    } catch (SAXParseException var5) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
    } catch (SAXException var6) {
        throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
    } catch (ParserConfigurationException var7) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
    } catch (IOException var8) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
    } catch (Throwable var9) {
        throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
    }
}

(四)从Document中获取Bean实例

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
    BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
    documentReader.setEnvironment(this.getEnvironment());
    int countBefore = this.getRegistry().getBeanDefinitionCount();
    
    documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
    
    return this.getRegistry().getBeanDefinitionCount() - countBefore;
}

在这个registerBeanDefinitions中将将BeanDefinitions放入BeanDefinitionsMap

(五)总结

xml资源解析成Document;
Document解析成一条条BeanDefinition;
BeanDefinition注册到缓存BeanDefinitionMap中;

注意:
容器的初始化并未进行相应bean的依赖注入,只是将XML配置文件中的信息转换成一条条的bean的信息存储在BeanDefinitionMap中。

3)IOC容器依赖注入

IOC容器的初始化容器依赖注入是两个相对独立的过程,一般依赖注入发生于getBean()时。

1. IOC容器依赖注入入口

factory.getBean("boy");

从加载好的BeanFactory工厂中调用getBean()方法加载Bean,该方法会触发容器的依赖注入。

2. Bean的加载

getBean()会触发AbstractBeanFactory类的doGetBean()方法。

这个方法的两步:

  1. 尝试从缓存中取Bean
  2. 如果缓存中不存在则创建Bean

3. 从缓存中加载Bean

  • 单例Bean在Spring的同一个容器中只会被创建一次,后续再获取bean都会直接从单例缓存中获取。
  • 单例Bean缓存在map型数据结构singletonObjects(一级缓存)。
  • 如果singletonObjects缓存中不存在,且通过isSingletonCurrentlyInCreation()方法知道该bean正在被创建则从earlySingletonObjects(二级缓存,也是map)尝试获取。
  • 如果earlySingletonObjects也没有单例Bean,则获取ObjectFactory类型的singletonFactory(三级缓存)工厂,从singletonFactory中利用getObject()方法获取singleObject并将其放入earlySingletonObjects中。
  • earlySingletonObjectssingletonObjects都是用来保存beanName和bean实例之间的关系,与singletonObjects不同之处在于,当一个bean被放在earlySingletonObjects里面之后,当bean还在创建过程中,就可以通过getBean方法获取到,其目的是用来检测循环引用。
public Object getSingleton(String beanName) {
    return this.getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if(singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        Map var4 = this.singletonObjects;
        synchronized(this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if(singletonObject == null && allowEarlyReference) {
                ObjectFactory singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                if(singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject != NULL_OBJECT?singletonObject:null;
}

4. 从Bean获取对象

现在已经获取了Bean,可以通过getObjectForBeanInstance()方法从bean中获取对象.

5. 缓存中没有Bean,就生成Bean

Spring中通过getSingleton()的重载方法实现bean的加载过程。

即刚才3中的这句话:
如果earlySingletonObjects也没有单例Bean,则获取ObjectFactory类型的singletonFactory工厂,从singletonFactory中利用getObject()方法获取singleObject并将其放入earlySingletonObjects中。

4)IOC容器初始化+IOC容器依赖注入の总结框图

IOC容器初始化框图总结:
在这里插入图片描述
IOC容器依赖注入框图:
在这里插入图片描述

5)容器的功能拓展ApplicationContext

1. ApplicationContext .vs BeanFactory

ApplicationContextBeanFactory
注入依赖形式初始化时创建所有单例Bean延迟加载:只有在调用getBean()的时候才对该Bean进行加载实例化
加载resource扩展了ResourceLoader,能加载多个resource只能加载单resource

2. ApplicationContext入口

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
HelloWorld helloWorld = context.getBean("hello",HelloWorld.class);

ClassPathXmlApplicationContext()–>setConfigLocations()将配置文件加载进内存–>refresh()进行上下文初始化
在这里插入图片描述

3. ApplicationContext上下文的初始化

refresh()实现了以下功能:

  1. 读取xml文件,实现BeanFactory功能
  2. 对BeanFactory进行功能拓展,比如配置“后处理器”和国际化、实现事件机制
  3. 初始化所有单例的Bean

6)AOP基础

1. AOP实现原理

  1. 核心:动态代理
  2. 做了什么:功能增强,比如:前置增强、返回增强

2. JDK动态代理

  1. 反射技术
  2. 动态代理类实现InvocationHandler接口并重写invoke()方法
  3. 通过Proxy的静态方法newProxyInstance()创建代理类
  4. 正常使用代理类对象的方法

注意Proxy.newProxyInstance():底层由JDK动态代理完成:
先获取class—获取构造器—创建对象

3. cglib与JDK动态代理对比

JDKCGLIB
代理类与被代理类实现了相同的接口代理类是被代理类的子类
反射机制类似索引一样直接调用

4. AOP增强类型

前置增强类: MethodBeforeAdvice
后置增强类: AfterReturningAdvice
异常抛出增强:ThrowsAdvice

7)AOP在spring中的实现

AOP在xml中入口为:

<aop:aspectj-autoproxy>

分为“普通标签”和“自定义标签”,aop的属于自定义标签。

在这里插入图片描述
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:aop="http://www.springframework.org/schema/aop"
 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.xsd">

 <!--配置Spring的IOC,把service对象配置进来-->
 <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

 <!--Spring中基于XML的AOP配置步骤
  1.把通知Bean也交给Spring来管理
  2.使用aop:config标签表明开始AOP的配置
  3.使用aop:aspect标签表明配置切面
   id属性: 是给切面提供唯一标识
   ref属性: 是指定通知类bean的id
  4.在aop:aspect标签的内部使用对应的标签来配置通知的类型
   我们现在的示例是让Logger类的printLog方法在切入点方法执行之前执行, 所以是前置通知
   aop:before : 表示配置前置通知
    method属性: 用于指定Logger类中哪个方法是前置通知
   pointcut属性: 用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

  切入点表达式的写法:
   关键字: execution(表达式)
   表达式:
    访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
   标准的切入点表达式:
    public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
 -->

 <!--配置Logger类-->
 <bean id="logger" class="com.itheima.utils.Logger"></bean>

 <!--配置AOP-->
 <aop:config>
 <!--配置切面-->
 <aop:aspect id="logAdvice" ref="logger">
  <!--配置通知的类型,并且建立通知方法和接入点方法的关联-->
  <aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
 </aop:aspect>
 </aop:config>
</beans>

AOP的代理创建主要是增强与被代理类组合生成代理类:

  1. JDK动态代理:将传入的增强封装成代理链放入invoke()中,通过Proxy.newProxyInstance()方法将生成代理类。
  2. CGLIB动态代理:传入的增强封装成代理连放入Callback中,最后传给Enhancer中通过该类的create()方法产生代理类。

1. 通过ProxyFactory简化AOP实现

静态代理:编译时增强
动态代理:运行时增强

在这里插入图片描述

2. 自动代理机制 简化AOP的实现

  • Spring提供了自动代理机制;
  • 在调动getBean()方法时获取bean实例会自动判断该类是否与切面匹配?
  • 匹配:创建代理实例。
  • 不匹配:创建普通实例。

Spring——AOP总结

在这里插入图片描述
自定义标签解析:
在这里插入图片描述
创建AOP代理:
在这里插入图片描述
createProxy()返回了一个AOP代理。

AOP动态代理执行:有两种,以JDK动态代理为例

  1. 获取拦截器
  2. 判断拦截器链是否空,空直接调用切点方法,不空就封装拦截器方法
  3. 执行proceed(),依次执行拦截器链

8)SpringMVC基础

1. Servlet定义

  • Servlet是Web浏览器服务器数据库或者应用程序的中间层
  • 主要用来连接HTTP请求和后端服务程序
  • Java Servlet 是运行在 Web 服务器上的 Java 类
  • Servlet应用程序需要运行在Servlet容器内,如Tomcat

2. Servlet的作用

  1. 接收客户端发送的http请求,显示请求:表单数据;隐式请求:cookies
  2. 处理接收到的数据并生成结果
  3. 发送结果数据到客户端(浏览器)

3. Servlet的生命周期

  1. 初始化:调用Servlet类的init()方法
    • 只在第一次创建Servlet执行
    • 浏览器请求进入Tomcat会首先检查这个Servlet实例是否存在
    • 存在:直接service()
    • 不存在:创建
  2. 具体服务方法:调用Servlet类的service()方法
  3. 关闭:调用Servlet类的destroy()方法
    • 只会执行一次
    • 关闭数据库连接、停止后台线程

9)Listener监听器:一下都是众多接口

1. Servlet对象监听器

  1. ServletContextListener是监听ServletContext对象,一个web工程一般只有一个ServletContextListener监听器:
    • contextInitialized()当Context上下文创建时后触发该方法;
    • contextDestroyed()当Context上下文销毁时后触发该方法,用于数据库的关闭;
  2. ServletContextAttributeListener监听ServletContext属性的删除或者增加事件。

2. HttpSession对象的监听器

  1. HttpSessionListener:监听HttpSession对象
    • sessionCreated():创建session对象,一般是一个ip浏览器第一次访问web工程时会创建一个session对象;
    • sessionDestroyed():销毁session对象,浏览器关闭,session超时,Context关闭都会触发session的关闭;
  2. HttpSessionAttributeListener:监听HttpSession属性的删除或者增加事件;

3. ServletRequest对象的监听器

  1. ServletRequestListener:主要监听ServletRequest对象,一个web工程可以有多个ServletRequest监听器:
  2. ServletRequestAttributeListener监听ServletRequest属性的删除或者增加事件

4. 监听器使用举例

public class ListenerTest implements HttpSessionListener, ServletContextListener, ServletRequestListener {

    // 创建 session
    public void sessionCreated(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("新创建一个session, ID为: " + session.getId());
    }

    // 销毁 session
    public void sessionDestroyed(HttpSessionEvent se) {
        HttpSession session = se.getSession();
        System.out.println("销毁一个session, ID为: " + session.getId());
    }

    // 加载 context
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        System.out.println("即将启动" + servletContext.getContextPath());
    }

    // 卸载 context
    public void contextDestroyed(ServletContextEvent sce) {
        ServletContext servletContext = sce.getServletContext();
        System.out.println("即将关闭" + servletContext.getContextPath());
    }

    // 创建 request
    public void requestInitialized(ServletRequestEvent sre) {

        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();

        String uri = request.getRequestURI();
        uri = request.getQueryString() == null ? uri : (uri + "?" + request.getQueryString());

        request.setAttribute("dateCreated", System.currentTimeMillis());

        System.out.println("IP " + request.getRemoteAddr() + " 请求 " + uri);
    }

    // 销毁 request
    public void requestDestroyed(ServletRequestEvent sre) {

        HttpServletRequest request = (HttpServletRequest) sre.getServletRequest();

        long time = System.currentTimeMillis() - (Long) request.getAttribute("dateCreated");

        System.out.println(request.getRemoteAddr() + "请求处理结束, 用时" + time + "毫秒. ");
    }

}

10)SpringMVC初始化

1. ContextLoaderListener初始化

  • 在web应用启动以前,自动装配ApplicationContext上下文环境,将Spring的IOC,AOP等功能与Web环境结合起来;
  • 每个Web应用都有一个ServletContext与之相关联作为整个Web应用的全局变量的存放地
  • ContextLoaderListener的核心逻辑就是初始化WebApplicationContext实例并将之放在servletContext中
  1. ContextLoaderListener监听器实现了ServletContextListener接口对ServletContext进行监听
  2. ServletContextListener创建ApplicationContext上下文环境主要包括以下几步:
    • 反射创建实例
    • 将配置文件参数传入ApplicationContext对象并调用refresh()方法刷新Spring容器,完成Spring容器的创建和启动
    • 将创建好的ApplicationContext放入ServletContext中

2. DispatcherServlet的初始化

判断是否WebApplicationContext已经被创建如果没有则重新创建,如果有则进行相应的属性注入,同时也会对servlet功能所使用的变量进行初始化.

11)SpringMVC处理逻辑

1. DispatchServlet处理逻辑

DispatchServlet继承了HttpServlet类,即请求经由Servlet转你发到DispatchServlet,由其的doGet()doPost()方法处理,这两个又委托给processRequest()进行具体的处理。


processRequest()–>doService()–>doDispatch()–>getHandler()从HandlerMapping中返回一个拦截链对象。

  • HandlerMapping在DispatchServlet初始化的时候进行初始化

在这里插入图片描述

2. 总结

DispatcherServlet逻辑处理主要是通过调用doDispatch()方法实现的,在该方法中主要先后完成了根据request请求从HandlerMapping中获取Handler映射并同拦截器一起封装成拦截链;
根据Handler获取对应的适配器Adapter;
从拦截链中获取所有注册的拦截器并执行其preHandler方法;
调用适配器执行具体的请求处理逻辑;
从拦截链中获取所有注册的拦截器并执行其postHandler方法;
将返回的ModlerAndeView对象进行页面跳转处理.

3. springMVC的工作原理

①客户端所有的请求都交给DispatchServlet处理
②DispatchServlet收到请求后从HandlerMapping中去取Handler
③会通过HanderAdapter对Handler进行封装,
④执行preHandler、Handler、postHandler方法
⑤返回ModelAndView给DispatchServlet
⑥DispatchServlet解析视图并渲染,返回给客户端。

12)事务

spring支持两种事务:1.编程式事务管理(用TransactionTemplate这个,即写代码实现) 2. 声明式事务管理(注解开发或xml文件开发,实际上是AOP)
在这里插入图片描述
在这里插入图片描述

是AOP的应用!

  1. 解析事务标签,并向容器中注册了3个bean:BeanFactoryTransactionAttributeSourceAdvisor、AnnotationTransactionAttributeSource、TransactionInterceptor
  2. 找到对应的增强器,只要是带着@Transaction的都适用于
    事务增强器BeanFactoryTransactionAttributeSourceAdvisor
  3. 故在getbean()期间,根据原类增强创建代理类,创建的代理类增强方法目标方法集合生成增强后的方法。
  4. 注意:在增强的方法中存在一个重要操作:将从数据库连接池获取到的connection,将其与当前线程绑定。
  5. 当JDBC执行具体的SQL时,首先判断当前线程是否有connection,有则直接返回此connection继续使用,那么就保证了目标方法中操作数据库用的时同一个connection。

八、循环依赖

1. 背景

声明了A与B两个Bean,但是A中需要注入B,B中需要注入A。

其实就是死锁的思想:
在这里插入图片描述
我们的想法时打破“互斥条件”,也就是这个资源不会被一个人拿到后就再也不能给别人,那么给什么?给缓存!

2. spring解决——三级缓存

在这里插入图片描述

  • 主要思想:提前曝光bean
  • 一级缓存:缓存正常的bean实例(完全体)
  • 二级缓存:缓存还未进行依赖注入初始化方法调用的bean实例(外壳,内部没完事),当对象需要被AOP切面代理时,保存代理bean的实例beanProxy
  • 三级缓存:缓存bean实例的ObjectFactory(工厂),ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式
// 一级缓存,缓存正常的bean实例
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存,缓存还未进行依赖注入和初始化方法调用的bean实例
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存,缓存bean实例的ObjectFactory
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

思想:
两个bean创建的过程中,其中一个bean的依赖注入过程中,getSingleton()能够获取到不完整的bean来注入!

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}
 
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先尝试中一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // 获取不到,并且当前需要获取的bean正在创建中
    // 第一次容器初始化触发getBean(A)的时候,这个isSingletonCurrentlyInCreation判断一定为false
    // 这个时候就会去走创建bean的流程,创建bean之前会先把这个bean标记为正在创建(isSingletonCurrentlyInCreation)
    // 然后A实例化之后,依赖注入B,触发B的实例化,B再注入A的时候,会再次触发getBean(A)
    // 此时isSingletonCurrentlyInCreation就会返回true了
    // 当前需要获取的bean正在创建中时,代表出现了循环依赖(或者一前一后并发获取这个bean)
    // 这个时候才需要去看二、三级缓存

//判断目前一级缓存中没有 且 其bean正在被创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加锁了
        synchronized (this.singletonObjects) {
            // 从二级缓存获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 二级缓存也没有,并且允许获取早期引用的话 - allowEarlyReference传进来是true
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                // 从三级缓存获取ObjectFactory
                if (singletonFactory != null) {
                    // 通过ObjectFactory获取bean实例
                    singletonObject = singletonFactory.getObject();
                    // 放入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从三级缓存删除
                    // 也就是说对于一个单例bean,ObjectFactory#getObject只会调用到一次
                    // 获取到早期bean实例之后,就把这个bean实例从三级缓存升级到二级缓存了
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 不管从哪里获取到的bean实例,都会返回
    return singletonObject;
}

下面是三级缓存的伪代码:

// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
 
protected Object getBean(final String beanName) {
    // !以下为getSingleton逻辑!
    // 先从一级缓存获取
    Object single = singletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 再从二级缓存获取
    single = earlySingletonObjects.get(beanName);
    if (single != null) {
        return single;
    }
    // 从三级缓存获取objectFactory
    ObjectFactory<?> objectFactory = singletonFactories.get(beanName);
    if (objectFactory != null) {
        single = objectFactory.get();
        // 升到二级缓存
        earlySingletonObjects.put(beanName, single);
        singletonFactories.remove(beanName);
        return single;
    }
    // !以上为getSingleton逻辑!
    
    // !以下为doCreateBean逻辑
    // 缓存完全拿不到,需要创建
    // 创建实例
    Object beanInstance = createBeanInstance(beanName);
    // 实例创建之后,放入三级缓存
    singletonFactories.put(beanName, () -> return beanInstance);
    // 依赖注入,会触发依赖的bean的getBean方法
    populateBean(beanName, beanInstance);
    // 初始化方法调用
    initializeBean(beanName, beanInstance);
    
    // 依赖注入完之后,如果二级缓存有值,说明出现了循环依赖
    // 这个时候直接取二级缓存中的bean实例
    Object earlySingletonReference = earlySingletonObjects.get(beanName);
    if (earlySingletonReference != null) {
        beanInstance = earlySingletonObject;
    }
    // !以上为doCreateBean逻辑
    
    // 从二三缓存移除,放入一级缓存
    singletonObjects.put(beanName, beanInstance);
    earlySingletonObjects.remove(beanName);
    singletonFactories.remove(beanName);
    
    return beanInstance;
}

3. 使用二级缓存能否解决循环依赖问题?

不可以,由于可能的AOP操作。

假设此时需要AOP对原始对象进行代理,注意Spring对于AOP代理都是在postProcessAfterInitialization()方法中进行代理的,我们必须保证,当前singletonObjects一级缓存中存在的bean实例与注入到其他bean中的实例时相同的。

如果仅用两个缓存:
①一个缓存 存最终的AOP代理后的对象
②另一个缓存 存放刚生成未进行AOP代理的对象
将A的②号缓存注入进B的属性注入,这样B最终的bean中的A与A的①号缓存中的不一致。

所以使用三级缓存:
存在一个三级缓存,这个缓存中能进行AOP处理。
通过单例工厂创建可以实现aop创建的最终对象

4. Bean的生命周期

在这里插入图片描述
注意
第三步:
BeanFactoryPostProcessor 接口是 Spring 初始化 BeanFactory 时对外暴露的扩展点;
BeanDefinitionRegistryPostProcessor将BeanDefinition定义配置信息加载到BeanFactory;
再举例:Mybatis 中的 MapperScannerConfigurer 是一个典型的 BeanDefinitionRegistryPostProcessor 的扩展使用。

我们也可以implements BeanFactoryPostProcessor 再使用@Component注册到Spring,实现自己的 postProcessBeanFactory() 逻辑。

第四步:
将所有BeanDefinition对象实例化成具体的bean对象。通过getBean就能获取,之前是使用占位符填充,当运行到getBean时进行属性注入。

补充“销毁”:
在销毁之前将实现回调方法和distory();

5. 三级缓存也无法解决的问题

构造器循环依赖

在这里插入图片描述
由于java语言决定不允许使用未初始化完成的变量!

故不可以

怎么解决?
@Lazy
在这里插入图片描述
原理:底层是一个AOP代理,返回一个LazyProxy对象,LazyProxy对象持有一个TargetSource对象,而TargetSource对象封装了真正生成bean的逻辑。
语义:真正使用到这个bean的时候,才对这个属性进行初始化。

6. 谈谈你对IOC的理解

  • SpringIOC有两个核心思想就是IOC控制反转DI依赖注入
  • IOC 控制反转的基本思想是,将原来的对象控制权从使用者交给spring来帮我们进行管理,
  • DI 依赖注入,就是把对应的属性的值注入到具体的对象中。
  • spring提供标签和@Autowired和@Resource注解等方式注入,注入方式本质上是AbstractAutowireCapableBeanFactory的populateBean() 方法先从beanDefinition 中取得设置的property值,例如autowireByName方法会根据bean的名字注入;autowireByType方法根据bean的类型注入,完成属性值的注入(涉及bean初始化过程)。
  • bean对象会存储在map结构中,在spring使用Map结构的singletonObjects存放完整的bean对象(涉及三级缓存和循环依赖)。
  • 整个bean的生命周期,从创建到使用到销毁的过程全部都是由容器来管理(涉及bean的生命周期)。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值