spring一


BeanPostProcessor

bean

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Bean {
    private static final Logger log = LoggerFactory.getLogger(Bean.class);
    public Bean() {
        log.info("construct bean");
    }
}

beanPostProcessor

public class MyBeanPostProcessor2 implements BeanPostProcessor {
    private static final Logger log = LoggerFactory.getLogger(MyBeanPostProcessor2.class);
    public MyBeanPostProcessor2() {
        log.info("construct mybeanpostProcessor");
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Bean) {
            log.info("process bean before initialization");
        }
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Bean) {
            log.info("process bean after initialization");
        }
        return bean;
    }
}

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">
    <!--当我们加载spring框架时,spring就会自动创建一个bean对象,并放入内存相当于我们常规的new一个对象,而<property></property>中的value则是实现了“对象.set方法”,这里也体现了注入了概念-->
    <bean id="myBean" class="cn.orzlinux.Bean.BeanPostProcessorTest.Bean"></bean>
    <bean id="myBeanPostProcessor2" class="cn.orzlinux.Bean.BeanPostProcessorTest.MyBeanPostProcessor2"></bean>
</beans>

测试代码(普通测试,非test):

public class Test {
    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Bean my = (Bean) ac.getBean("myBean");
        System.out.println(my);
    }
}

运行结果:

[main] INFO MyBeanPostProcessor2 - construct mybeanpostProcessor
[main] INFO Bean - construct bean
[main] INFO MyBeanPostProcessor2 - process bean before initialization
[main] INFO MyBeanPostProcessor2 - process bean after initialization

显示:是先创建BeanPostProcessor对象,然后创建bean对象,再执行BeanPostProcessorbeforeafter方法。可以利用它,在对象创建之后,对对象进行修改。

查看调用栈:

image-20210926144648603

进入看源码:

protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
    if (System.getSecurityManager() != null) {
        AccessController.doPrivileged(() -> {
            this.invokeAwareMethods(beanName, bean);
            return null;
        }, this.getAccessControlContext());
    } else {
        this.invokeAwareMethods(beanName, bean);
    }

    Object wrappedBean = bean;
    if (mbd == null || !mbd.isSynthetic()) {
        // here ======================================
        wrappedBean = this.applyBeanPostProcessorsBeforeInitialization(bean, beanName);
    }

    try {
        // 其实这个方法就是Spring提供的,用于对象创建完之后,针对对象的一些初始化操作。
        // 这就好比你创建了一个英雄之后,你需要给他进行一些能力属性的初始化、服装初始化一样。
        this.invokeInitMethods(beanName, wrappedBean, mbd);
    } catch (Throwable var6) {
        throw new BeanCreationException(mbd != null ? mbd.getResourceDescription() : null, beanName, "Invocation of init method failed", var6);
    }

    if (mbd == null || !mbd.isSynthetic()) {
         // here =====================================
        wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
    }

    return wrappedBean;
}

先后调用了beforeafter方法。以before为例:

public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {
    Object result = existingBean;

    Object current;
    for(Iterator var4 = this.getBeanPostProcessors().iterator(); var4.hasNext(); result = current) {
        BeanPostProcessor processor = (BeanPostProcessor)var4.next();
        current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
    }

    return result;
}

这两个方法内部,则分别去遍历系统里所有的BPP,然后逐个执行这些BPP对象beforeafter方法,去处理对象。

bean初始化图:

bean-initialization

Spring核心知识

本节转载自:Spring核心知识详细教程(已完结),作者EasyChill,点击跳转。

声明:

本文全文手写,代码全部手写,也希望大家,可以做一遍,最起码调试一遍,这样比看的效果好的多,本文的spring使用的是5.0.4版本,ide使用的是IntelliJ IDEA,不足和错误之处还请大家指出,谢谢!!

一、spring是什么

Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson创建。简单来说,Spring是一个分层的JavaSE/EE full-stack(一站式) 轻量级开源框架。

二、spring快速入门

  • 什么是spring?

    首先我们了解到struts是web框架(jsp/action/actionform)

    hibernate是 orm框架处于持久层

    spring是容器框架,用于配置bean,并维护bean之间的关系的框架

    • spring中的bean:是一个很重要的概念,这里的bean可以是Java中的任何一种对象:JavaBean/service/action/数据源/dao等等
    • spring中的ioc(inverse of control 控制反转)

    • spring中的di(dependency injection 依赖注入)

    • 接下来看一个层次框架图:

      image-20210926154723833

      说明

      web层: struts充当web层,接管jsp,action,表单,主要体现出mvc的数据输入数据的处理数据的显示分离

      model层: model层在概念上可以理解为包含了业务层,dao层,持久层,需要注意的是,一个项目中,不一定每一个层次都有

      持久层:体现oop,主要解决关系模型和对象模型之间的阻抗

  • 入门项目

    创建java项目(web中也可以使用)

    创建lib文件夹引入spring的开发最小包(最小配置,spring.jar(该包把最常用的包都包括),commons-logging.jar(日志包))

    创建配置文件,一般在src目录下

    配置bean

    image-20210926155028355

    说明:<bean></bean>这对标签元素的作用:当我们加载spring框架时,spring就会自动创建一个bean对象,并放入内存相当于我们常规的new一个对象,而<property></property>中的value则是实现了“对象.set方法”,这里也体现了注入了概念

    然后在java文件(测试文件)中调用

    接下来看具体的项目:

    image-20210926155100319

    说明:这是我的目录结构,其中我使用了ide整合了jar包,如果是手动创建时只需将jar包导入到项目里即可

    User.java

    package com.nuc.Bean;
    
    public class User {
        private String name;
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String sayHello(){
            System.out.println("hello"+ name);
            return "true";
        }
    }
    

抛开spring框架,使用传统方式实现在测试类中调用sayHello方法:

image-20210926155153573

这样,没有异议吧。

接下来使用spring调用该方法

image-20210926155215207

结果为小强,是因为上面的配置文件中配置value为小强

这样一个基本的项目就完成了~

接下来是细节:

  • 创建User2这个类
    package com.nuc.Bean;
    
    public class User2 {
        private String name;
    
        public String getName() {
            return name;
        }
    
       public void setName(String name) {
            this.name = name;
        }
        public void sayBye(){
            System.out.println("bye"+name);
        }
    }
    
  • 在User中新增private User2 user2;并在sayHello中调用sayBye方法 image-20210926155245875
  • 执行test类报出错误,这是由于user2未注入 image-20210926155303102
  • 在配置文件中配置注入 image-20210926155326534
  • 再次运行测试类 image-20210926155412079 spring运行原理图 image-20210926155445623 入门项目小结:

spring实际上是容器框架,可以配置各种bean,并可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以直接getBean(id),使用即可

现在我们来回答什么是spring这个问题

spring是一个容器框架,它可以接管web层,业务层,dao层,持久层的各个组件,并且可以配置各种bean, 并可以维护bean与bean的关系,当我们需要使用某个bean的时候,我们可以直接getBean(id),使用即可

接下来对几个重要的概念做说明:

  • ioc是什么?
    • ioc(inverse of control)控制反转:所谓反转就是把创建对象(bean)和维护对象(bean)的关系的权利从程序转移到spring的容器(spring-config.xml)
  • di是什么?
    • di(dependency injection)依赖注入:实际上di和ioc是同一个概念,spring的设计者,认为di更准确的表示spring的核心

实质上学习框架就是,最重要的就是学习各个配置

三、接口编程

  • spring就提倡接口编程,在配合di技术就可以达到层与层解耦的目的

  • 举案例说明:

    这个项目实现的是大小写转换

    基本思路:

    • 创建一个接口
    • 创建两个类实现接口
    • 配置bean
    • 使用

    下面是我的项目目录

    image-20210926155719827

ChangeLetter.java

package com.nuc;

public interface ChangeLetter {
    public String change();
}

LowerLetter.java

package com.nuc;

public class LowerLetter implements ChangeLetter {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public String change(){
        //大小字母转小写
        return str.toLowerCase();
    }
}

UpperLetter.java

package com.nuc;

public class UpperLetter implements ChangeLetter {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public String change(){
        //把小写字母转成大写
       return str.toUpperCase();
    }
}

spring-config.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="changeLetter" class="com.nuc.UpperLetter">-->
        <!--<property name="str">-->
            <!--<value>sjt</value>-->
        <!--</property>-->
    <!--</bean>-->

    <bean id="changeLetter" class="com.nuc.LowerLetter">
        <property name="str" value="SJT"/>
    </bean>
</beans>

说明:其中的两个bean id名相同是为了调试方便,可通过注释来调试

Test.java

package com.nuc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        //调用change不使用接口
//        UpperLetter changeLetter = (UpperLetter) ac.getBean("changeLetter");
//        System.out.println(changeLetter.change());

        //使用接口
        ChangeLetter changeLetter = (ChangeLetter)ac.getBean("changeLetter");
        System.out.println(changeLetter.change());
    }
}

以上这个案例,我们可以初步体会到,di和接口编程,的确可以减少层(web层)和层(业务层)之间的耦合度,尽管看起来似乎没什么改变,而且好像麻烦了一些,但是当项目大了以后,这种耦合度的降低就显得尤为重要

四、获取Bean

  • ApplicationContext 应用上下文容器取
    ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
    
    当这句代码被执行,spring-config.xml文件中配置的bean就会被实例化。(但要注意bean的生命周期要为singleton),也就是说,不管没有getBean(),使用上下文容器获取bean,就会实例化该bean
  • Bean工厂容器取
    BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
    
    这句代码被执行,spring-config.xml文件中配置的bean不会被实例化,即光实例化容器,并不会实例化bean 而是在执行以下代码时才会被实例化,即使用bean的时候factory.getBean("beanId"); 如何验证上述说法呢?每一个java类都有一个默认的构造方法。给这个构造方法输出一句话。具体如下
    • 创建一个类,类有一个属性,装配,该属性
    • 重写该类的构造方法,输出bean被创建
    • 创建测试类,测试
    • 使用ApplicationContext应用上下文容器*
    image-20210926155911118 使用bean工厂 image-20210926155931804
    可以看到,这一行代码,并不能时bean实例化,接下来加factory.getBean(“student”);试试  
    
    image-20210926155953231 这样就是bean实例化了 那么在实际开发中选择哪种方式? 在移动开发中,即项目运行在移动设备中使用BeanFactory(节约内存,所以,你想节约内存开发就是使用bean工厂,但速度会受影响),但大部分的项目都是使用ApplicationContext(可以提前加载,缺点是消耗一点内存) 贴一张bean的生命周期图: image-20210926160020336 接下来我们验证前两种作用域: 第一种 image-20210926160038270 结果 image-20210926160059213 可以看到stu1和stu2拥有相同的地址,接下来测试第二种 image-20210926160112783 image-20210926160135141 测试结束! 至于后三种是在web开发中才有实际意义!

五、三种获取ApplicationContext对象引用的方法

  • ClassPathXmlApplicationContext (从类路径中加载)

    • 这个不在赘述,上面所有例子都是利用这种方式加载的
  • FileSystemXmlApplicationContext (从文件系统中加载)

    image-20210926160217742

    可以看到是没有问题的,需要注意的是,文件路径为绝对路径,且注意使用转义符,直接使用“C:\sjt\idea\code\spring\spring-interface\src”,会报错,需要将“\”转义,但实际开发中应用不多,了解即可

  • XmlWebApplicationContext (从web系统中加载)

    这种方式,注意,在tomcat启动时就会加载,此处不做说明,在web应用中说明

六、再谈Bean的生命周期

  • 生命周期是一个重点吗?答案是肯定的!!
    • 不了解生命周期难道不能开发了吗?那自然是可以的,但如果你想实现更加高级的功能,你不了解那可能是会出问题的!而在面试过程中也是经常提及的。
    • 接下里我们举例子说明
      • 生命周期分为以下几步:
        • 1、实例化
          • 当我们加载sping-config.xml文件时,bean就会被实例化到内存(前提是scope=singleton)
        • 2、设置属性值
          • 调用set方法设置属性,前提是有对应的set方法
        • 3、如果你调用BeanNameAware的set’Bean’Name()方法
          • 这是个接口,该方法可以给出正在被调用的bean的id
        • 4、如果你调用BeanFactoryAware的setBeanFactory()方法
          • 这也是个接口,该方法可以传递beanFactory
        • 5、如果你调用了ApplicationContextAeare的setApplicationContext()方法
          • 同样为接口,该方法传递一个ApplicationContext
        • 6、BeanPostProcessor的预初始化方法Before
          • 这个东西很厉害了,可以叫做后置处理器,它不是接口,具体细节,代码体现
        • 7、如果你调用了InitializingBean的afterPropertiesSet()方法
        • 8、调用自己的init方法,具体为在bean中有一个属性inin-method=”init”
        • 9、BeanPostProcessor的方法After
        • 10、使用bean,体现为调用了sayHi()方法
        • 11、容器关闭
        • 12、可以实现DisposableBean接口的destory方法
        • 13、可以在调用自己的销毁方法,类似于8
        • 实际开发过程中,并没有这么复杂,常见过程为,1,2,6,9,10,11*
        • 接下来看代码*
        image-20210926160303642 MyBeanPostProcessor.java
    package com.nuc.BeanLife;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    public class MyBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("第九步,postProcessAfterInitialization方法被调用");
            return null;
        }
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("第六步,postProcessBeforeInitialization方法被调用");
            System.out.println("第六步,"+bean+"被创建的时间为"+new java.util.Date());
            /*
            在这里,能做的事情可就不止上面的这么简单的一句输出了,它还可以过滤每个对象的ip
            还可以给所有对象添加属性或者函数,总之就是所有对象!
            其实,这里体现了AOP编程的思想,AOP呢就是面向切成编程(针对所有对象编程)
             */
    
            return null;
        }
    }
    

PersonService.java

package com.nuc.BeanLife;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class PersonService implements BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean,DisposableBean{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("第二步调用set方法");
    }
    public void sayHi(){
        System.out.println("第十步,hi"+ name);
    }
    public PersonService(){
        System.out.println("第一步,实例化bean");
    }
    @Override
    public void setBeanName(String arg0){
        //该方法可以给出正在被调用的bean的id
        System.out.println("第三步,setBeanName被调用,调用的id名为:"+arg0);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        //该方法可以传递beanFactory
        System.out.println("第四步,setBeanFactory被调用,beanFactory为:"+beanFactory);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //该方法传递一个ApplicationContext
        System.out.println("第五步,调用setApplicationContext方法:"+applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("第七步,调用afterPropertiesSet()方法");
    }
    public void init(){
        System.out.println("第八步、调用我自己的init()方法");
    }

    @Override
    public void destroy() throws Exception {
        //关闭数据连接,socket,文件流,释放资源
        //这个函数的打印你看不到,应为
        System.out.println("第十步,销毁方法(但不建议使用这种方式释放资源)");
    }
    public void destory(){
//        也看到不
        System.out.println("销毁");
    }
}

Test.java

package com.nuc.BeanLife;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        PersonService person1= (PersonService) ac.getBean("personService");
        person1.sayHi();
    }
}

spring-config.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="personService" init-method="init" destroy-method="destroy" scope="singleton" class="com.nuc.BeanLife.PersonService">
        <property name="name" value="sjt"></property>
    </bean>
    <bean id="personService2" class="com.nuc.BeanLife.PersonService">
        <property name="name" value="sjt2"></property>
    </bean>
    <!--配置自己的后置处理器,优点类似filter-->
    <bean id="myBeanPostProcessor" class="com.nuc.BeanLife.MyBeanPostProcessor">

    </bean>
</beans>

测试结果

4月 17, 2018 4:57:26 下午 
信息: Loading XML bean definitions from class path resource [spring-config.xml]
第一步,实例化bean
第二步调用set方法
第三步,setBeanName被调用,调用的id名为:personService
第四步,setBeanFactory被调用,beanFactory为:org.springframework.beans.factory.support.DefaultListableBeanFactory@ae13544: defining beans [personService,personService2,myBeanPostProcessor]; root of factory hierarchy
第五步,调用setApplicationContext方法:org.springframework.context.support.ClassPathXmlApplicationContext@646d64ab: startup date [Tue Apr 17 16:57:26 CST 2018]; root of context hierarchy
第六步,postProcessBeforeInitialization方法被调用
第六步,com.nuc.BeanLife.PersonService@2e6a8155被创建的时间为Tue Apr 17 16:57:27 CST 2018
第七步,调用afterPropertiesSet()方法
第八步、调用我自己的init()方法
第九步,postProcessAfterInitialization方法被调用
第一步,实例化bean
第二步调用set方法
第三步,setBeanName被调用,调用的id名为:personService2
第四步,setBeanFactory被调用,beanFactory为:org.springframework.beans.factory.support.DefaultListableBeanFactory@ae13544: defining beans [personService,personService2,myBeanPostProcessor]; root of factory hierarchy
第五步,调用setApplicationContext方法:org.springframework.context.support.ClassPathXmlApplicationContext@646d64ab: startup date [Tue Apr 17 16:57:26 CST 2018]; root of context hierarchy
第六步,postProcessBeforeInitialization方法被调用
第六步,com.nuc.BeanLife.PersonService@6221a451被创建的时间为Tue Apr 17 16:57:27 CST 2018
第七步,调用afterPropertiesSet()方法
第九步,postProcessAfterInitialization方法被调用
第十步,hisjt

Process finished with exit code 0

动手做一遍是最好的选择!!

使用bean工厂获取bean对象,生命周期是和上下文获取的不一样的,如下图

image-20210926160449032

其中我只装配了一个bean,可见执行步骤的短缺

七、装配Bean

  • 使用xml装配

    • 上下文定义文件的根元素是<beans></beans>,有多个子元素<bean></bean>,每个<bean>元素定义了bean如何被装配到spring容器中

    • 对子元素bean最基本的配置包括bean的ID和它的全称类名

    • 对bean的scope装配,默认情况下为单例模式,具体情况上面已经说过,建议查看文档,更加具体,尽量不要使用原型bean,即scope设置为propotype,这样子会对性能有较大的影响

    • bean的init-methodestory-method的书写,在生命周期那一块儿已经很清楚了,此处不再赘述,需要说明的是,可以通过注解的方式来配置,而不是在bean中使用init-metho和destory-method属性

      image-20210926160508717
    • 注入集合类型的数据,例如,map,set,list,数组,Properties….

      接下来举例子

      目录结构:

      image-20210926160559234

      Department.java

      package com.nuc;
      
      import java.util.List;
      import java.util.Map;
      import java.util.Set;
      
      public class Department {
          private String name;
          private String []empName;//这里int的数组也可以注入成功
          private List<Employee> empList;
          private Map<String,Employee> empMap;
          private Properties pp;
      
          public Properties getPp() {
              return pp;
          }
      
          public void setPp(Properties pp) {
              this.pp = pp;
          }
      
          public Set<Employee> getEmpSet() {
              return empSet;
          }
      
          public void setEmpSet(Set<Employee> empSet) {
              this.empSet = empSet;
          }
      
          private Set<Employee> empSet;
          public List<Employee> getEmpList() {
              return empList;
          }
      
          public void setEmpList(List<Employee> empList) {
              this.empList = empList;
          }
      
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String[] getEmpName() {
              return empName;
          }
      
          public void setEmpName(String[] empName) {
              this.empName = empName;
          }
      
          public Map<String, Employee> getEmpMap() {
              return empMap;
          }
      
          public void setEmpMap(Map<String, Employee> empMap) {
              this.empMap = empMap;
          }
      }
      

      Employee.java

package com.nuc;

public class Employee {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Test.java

package com.nuc;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        Department department = (Department)ac.getBean("department");
        System.out.println(department.getName());
//        取集合
        for(String empName:department.getEmpName()){
            System.out.println(empName);
        }
        System.out.println("取list...");
        for (Employee e:department.getEmpList()){
            System.out.println("name="+e.getName());
        }
        System.out.println("取set...");
        for (Employee e:department.getEmpSet()){
            System.out.println("name="+e.getName());
        }
        System.out.println("迭代器取map...");
        //1.迭代器
        Map<String,Employee> employeeMap = department.getEmpMap();
        Iterator iterator = employeeMap.keySet().iterator();
        while (iterator.hasNext()){
            String key = (String)iterator.next();
            Employee employee=employeeMap.get(key);
            System.out.println("key="+key+" "+ employee.getName());
        }
        System.out.println("entry取map...");
        //2.简洁(建议使用这种方式)
        for (Entry<String,Employee> entry:department.getEmpMap().entrySet()){
            System.out.println(entry.getKey()+" "+entry.getValue().getName());
        }
    }
    System.out.println("通过properties取数据");
    Properties properties = department.getPp();
    for (Entry<Object,Object> entry:properties.entrySet()){
        System.out.println(entry.getKey().toString()+" "+entry.getValue());
    }
}

spring-config.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="department" class="com.nuc.Department">
        <property name="name" value="财务部"></property>

        <!--给数组注入-->
        <property name="empName">
            <list>
                <value>小明</value>
                <value>小花</value>
            </list>
        </property>

        <!--给list注入-->
        <!--list可以存放相同的对象,并当作不同对象输出-->
        <property name="empList">
            <list>
                <ref bean="employee1"></ref>
                <ref bean="employee2"></ref>
            </list>
        </property>

        <!--给set注入-->
        <!--set集合不可以存放相同对象-->
        <property name="empSet">
            <set>
                <ref bean="employee1"></ref>
                <ref bean="employee2"></ref>
            </set>
        </property>

        <!--给map注入-->
        <!--输出的对象取决于key值,key值不同,对象相同也可以打出-->
        <!--当key值相同时,对象相同或者不同都打出最后一个key所对应的对象-->
        <property name="empMap">
            <map>
                <entry key="1" value-ref="employee1"></entry>
                <entry key="2" value-ref="employee2"></entry>
                <entry key="3" value-ref="employee2"></entry>
            </map>
        </property>
        <!--给属性集合注入-->
        <property name="pp">
            <props>
                <prop key="1">hello</prop>
                <prop key="2">world</prop>
            </props>
        </property>
    </bean>

    <bean id="employee1" class="com.nuc.Employee">
        <property name="name" value="北京"></property>
    </bean>
    <bean id="employee2" class="com.nuc.Employee">
        <property name="name" value="太原"></property>
    </bean>
</beans>

Student.java

package com.nuc.inherit;

public class Student {
  protected String name;
  protected int age;

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public int getAge() {
      return age;
  }

  public void setAge(int age) {
      this.age = age;
  }
}

Gradate.java

package com.nuc.inherit;

public class Gradate extends Student {
    private String degree;

    public String getDegree() {
        return degree;
    }

    public void setDegree(String degree) {
        this.degree = degree;
    }
}

spring-config.java

<?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="student" class="com.nuc.inherit.Student">
        <property name="name" value="sjt"></property>
        <property name="age" value="22"></property>
    </bean>
    <!--配置gradate对象-->
    <bean id="gradate" parent="student" class="com.nuc.inherit.Gradate">
        <!--如果子类重新赋值,则覆盖父类的-->
        <property name="name" value="小明"></property>
        <property name="degree" value="博士"></property>
    </bean>
</beans>

Test2.java

package com.nuc.inherit;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test2 {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("com/nuc/inherit/spring-config.xml");
        Gradate gradate = (Gradate) ac.getBean("gradate");
        System.out.println(gradate.getName()+" "+gradate.getAge()+" "+gradate.getDegree());
    }
}
  • 以上我们都是用set注入依赖的,下面介绍构造函数注入依赖

    <bean name="user" class="com.nuc.Bean.User">
        <!--通过constructor-arg标签完成了对构造方法的传参-->
        <!--如果是属性是类类型,则使用ref=""-->
        <constructor-arg index="0" type="java.lang.String" value="小强"></constructor-arg>
        <constructor-arg index="1" type="java.lang.String" value="男"></constructor-arg>
        <constructor-arg index="2" type="int" value="20"></constructor-arg>
    </bean>
    

    当然对应的User要有相应的构造方法。

    set注入的缺点是无法清晰的表达哪个属性是必须的,哪些是可选的,构造器注入的优势,是可以通过构造强制依赖关系,不可能实例化不完全或者不能使用的bean

    但其实实际开发中还是set注入较多,即property注入

  • bean的自动装配:

    image-20210926161327704

    接下来是实例:

目录图

image-20210926161354654

Dog.java

package com.nuc.autowire;

public class Dog {
    private String name;
    private int age;
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

Master.java

package com.nuc.autowire;

public class Master {
    private String name;
    private Dog dog;

    private Master(Dog dog){
        //为了自动装配的constructor
        this.dog= dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }
}

Test.java

package com.nuc.autowire;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("com\\nuc\\autowire\\beans.xml");
        Master master = (Master)ac.getBean("master");
        System.out.println(master.getName()+"养了只狗,它的名字叫"+ master.getDog().getName()+",他今年"+master.getDog().getAge()+"岁了");
    }
}

beans.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">
    <!--配置master对象-->
    <bean id="master" class="com.nuc.autowire.Master" autowire="constructor">
        <property name="name" value="sjt"></property>
        <!--传统方式-->
        <!--<property name="dog" ref="dog"></property>-->
    </bean>

    <!--配置dog对象,byName时使用-->
    <!--<bean id="dog" class="com.nuc.autowire.Dog">-->
        <!--<property name="name" value="小黄"></property>-->
        <!--<property name="age" value="2"></property>-->
    <!--</bean>-->

    <!--配置dog对象,byType时使用-->
    <!--<bean id="dog11" class="com.nuc.autowire.Dog">-->
        <!--<property name="name" value="小黄"></property>-->
        <!--<property name="age" value="2"></property>-->
    <!--</bean>-->

    <!--配置dog对象,constructor时使用-->
    <bean id="dog22" class="com.nuc.autowire.Dog">
        <property name="name" value="小黄"></property>
        <property name="age" value="2"></property>
    </bean>
</beans>
  • autodetect:是在constructor和byType之间选一种

  • default:这种方式在文档中没有提及,需要在beans中指定,当你在beans中指定以后,所有的bean都是你所指定的装配方式,如果没有指定,则默认为no,所以,no之所以为默认指定装配方式,其实是从beans那里来的

    image-20210926161457517

  • 其实在实际开发中,很少用到自动装配, 一般都是手动set装配的(property),而且自动装配也是在bean中没有配置才取执行自动装配的

    • spring本身提供的bean
      • 分散配置

八、AOP编程(难点)

  • aop:aspect oriented programming(面向切面编程),它是对一类对象或所有对象编程。
    • 核心:在不增加代码的基础上,还增加新功能
    • 提醒:aop编程,实际上是开发框架本身用的多,开发中不是很多,将来会很多
    • 初步理解:面向切面:其实是,把一些公共的“东西”拿出来,比如说,事务,安全,日志,这些方面,如果你用的到,你就引入。
    接下来通过例子来理解这个抽象的概念,概念稍后再说 步骤: 拿前置通知打比方,后来还会有,后置通知,环绕通知,异常通知,引入通知
    • 定义接口
    • 编写对象(被代理对象=目标对象)
    • 编写通知(前置通知目标方法调用前调用)
    • 在beans.xml中配置
      • 配置被代理对象
      • 配置通知
      • 配置代理对象(是proxyFactoryBean的对象实例)
        • 配置代理接口集
        • 织入通知
        • 配置被代理对象
    接下来看代码: 目录结构:
image-20210926161525435

TestServiceInter.java(interface)

package com.nuc.Aop;

public interface TestServiceInter {
    public void sayHello();
}

TestServiceInter2.java(interface)

package com.nuc.Aop;

public interface TestServiceInter2 {
    public void sayBye();
}

TestService.java

package com.nuc.Aop;

public class TestService implements TestServiceInter,TestServiceInter2{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void sayHello(){
        System.out.println("hi "+name);
    }

    @Override
    public void sayBye() {
        System.out.println("bye "+name);
    }
}

MyMethodBeforeAdvice.java

package com.nuc.Aop;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
    //前置通知
    @Override
    public void before(Method method, Object[] objects, Object o)
            throws Throwable {
        //method:被调用方法的名字
        //objects:给method传递的参数
        //o:目标对象
        System.out.println("记录日志。。。"+method.getName());

    }
}

beans.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="testService" class="com.nuc.Aop.TestService">
        <property name="name" value="sjt"/>
    </bean>
    <!--配置前置通知-->
    <bean id="myMethodBeforeAdvice" class="com.nuc.Aop.MyMethodBeforeAdvice"></bean>
    <!--配置代理对象-->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>com.nuc.Aop.TestServiceInter</value>
                <value>com.nuc.Aop.TestServiceInter2</value>
            </list>
        </property>
        <!--把通知织入到代理对象-->
        <property name="interceptorNames">
            <!--相当于把myMethodBeforeAdvice前置通知和代理对象关联起来-->
            <value>myMethodBeforeAdvice</value>
        </property>
        <!--配置被代理对象,可以指定-->
        <property name="target" ref="testService"></property>
    </bean>
</beans>

Test.java

package com.nuc.Aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("com/nuc/Aop/beans.xml");
        TestServiceInter testService = (TestServiceInter)ac.getBean("proxyFactoryBean");
        testService.sayHello();
        //当一个类继承多个接口,那么他们之间可以互转
        ((TestServiceInter2)testService).sayBye();
    }
}

测试结果

image-20210926161623795

  • AOP的术语
    • 切面:要实现交叉功能,是系统模块化的一个切面领域,如记录日志
    • 连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段
      • 连接点是一个静态的概念
    • 通知: 切面的实际实现,它通知系统的新行为,如日志通知包含了实现日志功能的代码,如向日志文件写日志,通知在连接点插入应用系统中。
    • 切入点:定义了通知应该应用在哪些连接点通知可以应用到AOP框架支持的任何连接点
      • 切入点是动态概念,当通知应用了连接点,连接点就变成了切入点
    • 引入:为类添加新方法和属性
    • 目标对象:通知的对象,既可以是你编写的类,也可以是第三方类
    • 代理:通知应用到目标对象后创建后的对象,应用系统的其他部分不用为了支持代理对象而改变
      • spring的两种代理:
        • 目标对象实现了若干个接口,spring使用JDK的java.lang.reflect.Proxy类代理
        • 目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类
    • 织入:切面应用到目标对象从而创建一个新代理对象的过程,织入发生在目标对象生命周期的多个点上
      • 编译期:切面在目标对象编译时织入,这需要一个特使的编译器
      • 类装载期:切面在目标对象被载入jvm时织入,这需要一个特殊的类加载器
      • 运行期:切面在应用系统运行时切入
    接下来引入后置通知,环绕通知,异常通知,引用通知 类似于前置通知,前三者需要继承一种接口,引用通知直接配置

MyAfterReturningAdvice.java

package com.nuc.Aop;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class MyAfterReturningAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object o, Method method, Object[] objects,
                               Object o1)
            throws Throwable {
        //后置通知
        //o:前面函数的返回值
        //method:哪个方法被调用
        //objects:调用方法的参数
        //o1:目标对象
        System.out.println("后置通知:调用结束,关闭资源。");
    }
}

MyMethodInterceptor.java

package com.nuc.Aop;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        //环绕通知
        System.out.println("环绕通知:进入函数体,调用方法前");
        Object obj = methodInvocation.proceed();
        System.out.println("环绕通知:完成调用");
        return obj;
    }
}

MyThrowsAdvice.java

package com.nuc.Aop;

import org.springframework.aop.ThrowsAdvice;

import java.lang.reflect.Method;

public class MyThrowsAdvice implements ThrowsAdvice {
    //异常通知
    //ThrowsAdvice这个接口是标识性接口,没有任何方法
   public void afterThrowing(Method m,Object[] os,Object target,Exception e){
        System.out.println("异常通知:出问题了:"+e.getMessage());
    }
}

beans.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="testService" class="com.nuc.Aop.TestService">
        <property name="name" value="sjt"/>
    </bean>
    <!--配置前置通知-->
    <bean id="myMethodBeforeAdvice" class="com.nuc.Aop.MyMethodBeforeAdvice"></bean>
    <!--配置后置通知-->
    <bean id="myAfterReturningAdvice" class="com.nuc.Aop.MyAfterReturningAdvice"></bean>
    <!--配置环绕通知-->
    <bean id="myMethodInterceptor" class="com.nuc.Aop.MyMethodInterceptor"></bean>
    <!--配置异常通知-->
    <bean id="myThrowsAdvice" class="com.nuc.Aop.MyThrowsAdvice"></bean>
    <!--定义前置通知的切入点(引用通知)-->
    <bean id="myMethodBeforeAdviceFilter" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
        <property name="advice" ref="myMethodBeforeAdvice"></property>
        <property name="mappedNames">
            <list>
                <!--这里支持使用正则表达式匹配-->
                <!--配置了sayHello使用前置通知过滤-->
                <value>sayHello</value>
            </list>
        </property>
    </bean>
    <!--配置代理对象-->
    <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置代理接口-->
        <property name="proxyInterfaces">
            <list>
                <value>com.nuc.Aop.TestServiceInter</value>
                <value>com.nuc.Aop.TestServiceInter2</value>
            </list>
        </property>
        <!--把通知织入到代理对象-->
        <property name="interceptorNames">
            <list>
                <!--相当于把myMethodBeforeAdvice前置通知和代理对象关联起来-->
                <!--使用自定义切入点-->
                <value>myMethodBeforeAdviceFilter</value>
                <!--织入后置通知-->
                <value>myAfterReturningAdvice</value>
                <!--织入环绕通知-->
                <value>myMethodInterceptor</value>
                <!--织入异常通知-->
                <value>myThrowsAdvice</value>
            </list>
        </property>
        <!--配置被代理对象,可以指定-->
        <property name="target" ref="testService"></property>
    </bean>
</beans>

TestService.java

image-20210926161706203

如图这一处变动

总之呢就是一个配置 -> 织入的过程

运行结果:

image-20210926161728262

可以看到前置通知和后置通知,似乎能够识别方法,事实上也是这样的(spring框架内置)。而且sayBay()也得到了应用。这正是,我们前面所提到了,AOP是对一类或所有对象编程的体现,又由于异常通知的配置,有了异常,由于引用通知的配置,致使sayBay的前置通知及后续无法通知。

正常结果(配置引用通知):

image-20210926161751235

PS

报错:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".

解决方案:

我原来用的是api,不是simple。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.25</version>
</dependency>

参考

BeanPostProcessor —— 连接Spring IOC和AOP的桥梁

Spring核心知识详细教程(已完结)