Spring

  • IoC(控制反转)/ DI(依赖注入)
  • AOP(面向切面编程)

1.创建IOC工程

1.1导入依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>

IOC框架只需要context依赖就够了

1.2创建实体类

1
2
3
4
5
public class Student {
private long id;
private String name;
private int age;
}

1.3编写对应的配置文件,Spring.xml(全局)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<bean id="student" class="com.Lei.pojo.Student">
<property name="id" value="0"></property>
<property name="name" value="张三"></property>
<property name="age" value="10"></property>
</bean>
</beans>
  • 上面的beans是文件头,黏贴就好,然后下面的bean就是写自己的对应的实体类
    • bean标签依据id来对应你需要创建的实体类,需要通过class指定好对应的实体类的路径
    • 下面的property就是为值附的属性,如果没有就是默认的结果

1.4主程序调试

1
2
3
4
5
6
7
8
public static void main(String[] args) {
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring.xml");
//通过配置文件的路径获取对象
Student student = (Student) applicationContext.getBean("student");
//同故宫这个对象获取对应的实体类
System.out.println(student);
}
//结果:Student(id=0, name=张三, age=10)

2.配置文件

2.1标准

  • 通过配置 bean 标签来完成对象的管理。

    • id:对象名。

    • class:对象的模版类(所有交给 IoC 容器来管理的类必须有无参构造函数,因为 Spring 底层是通过反射机制来创建对象,调用的是无参构造)

  • 对象的成员变量通过 property 标签完成赋值。

    • name:成员变量名。
    • value:成员变量值(基本数据类型,String 可以直接赋值,如果是其他引用类型,不能通过 value 赋值)
    • ref:将 IoC 中的另外一个 bean 赋给当前的成员变量(DI)

2.2引用类型

  • 新的数据类型

    • public class Address {
          private long id;
          private String name;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8

      - ```java
      public class Student {
      private long id;
      private String name;
      private int age;
      private Address address;
      }
  • 在xml配置文件中添加新的bean

    • <bean id="address" class="com.Lei.pojo.Address">
          <property name="id" value="1"></property>
          <property name="name" value="科技路"></property>
      </bean>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      - ```xml
      <!--原来的实体类xml中是引用数据类型就通过ref标签调用xml中现在新加入的bean-->
      <bean id="student" class="com.Lei.pojo.Student">
      <property name="id" value="0"></property>
      <property name="name" value="张三"></property>
      <property name="age" value="10"></property>
      <property name="address" ref="address"></property>
      </bean>
  • 主程序调试

    • public static void main(String[] args) {
          ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring.xml");
          Student student = (Student) applicationContext.getBean("student");
          System.out.println(student);
          //一样的代码,降低了耦合度
      }
      //结果:Student(id=0, name=张三, age=10, address=Address(id=1, name=科技路))
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19

      ### 3模拟IOC底层

      > 思路
      >
      > 1. 解析xml配置文件
      > 2. 得到里面的元素,遍历获取对应的id、class路径
      > 3. 用类解析反射出整个类,获取构造器,创建对象
      > 4. 通过Map键值对存储获取的id和整个类对象
      > 5. getBean方法就是通过id来取Map中的对象

      - 导入dom4j依赖

      - ```xml
      <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
      </dependency>
  • 创建接口

    • //写的接口名和spring给的一样
      public interface ApplicationContext {
          public Object getBean(String id);
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38

      - 编写解析类

      - ```java
      public class ClassPathXmlApplicationContext implements ApplicationContext {
      Map<String, Object> Ioc = new HashMap<>();//键值对形式存储
      public ClassPathXmlApplicationContext(String path) {
      SAXReader saxReader = new SAXReader();//dom4j读入
      try {
      Document read = saxReader.read("./src/main/resources/" + path);//读取xml配置文件
      Element rootElement = read.getRootElement();//获取里面的元素 打印出来结果 Element: <beans>
      //
      Iterator<Element> iterator = rootElement.elementIterator();
      //获取迭代器遍历
      while (iterator.hasNext()){
      Element element = iterator.next();

      String id = element.attributeValue("id");
      String ClassName = element.attributeValue("class");
      //找到id、class字段
      Class aClass = Class.forName(ClassName);
      //通过ClassName反射出实体类
      Constructor constructor = aClass.getConstructor();
      //获取实体类的构造函数
      Object o = constructor.newInstance();
      //通过构造函数创建对象
      Ioc.put(id,o);
      }

      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      @Override
      public Object getBean(String id) {
      return Ioc.get(id);
      }
      }
  • 获取对应的property字段值

    • 原理再次嵌套一个element元素的遍历,获取相应的字段,然后通过invoke赋值给原来的对象

    • public ClassPathXmlApplicationContext(String path) {
          SAXReader saxReader = new SAXReader();//dom4j读入
          try {
              Document read = saxReader.read("./src/main/resources/" + path);//读取xml配置文件
              Element rootElement = read.getRootElement();//获取里面的元素  打印出来结果 Element: <beans>
              //
              Iterator<Element> iterator = rootElement.elementIterator();
              //获取迭代器遍历
              while (iterator.hasNext()) {
                  Element element = iterator.next();
      
                  String id = element.attributeValue("id");
                  String ClassName = element.attributeValue("class");
                  //找到id、class字段
                  Class aClass = Class.forName(ClassName);
                  //通过ClassName反射出实体类
                  Constructor constructor = aClass.getConstructor();
                  //获取实体类的构造函数
                  Object o = constructor.newInstance();
                  //通过构造函数创建对象
                  Iterator<Element> bean = element.elementIterator();
                  while (bean.hasNext()) {
                      Element property = bean.next();
                      String name = property.attributeValue("name");
                      String valueStr = property.attributeValue("value");
                      //获取字段
                      String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
                      //找到set的方法
                      Field field = aClass.getDeclaredField(name);
                      Method declaredMethod = aClass.getDeclaredMethod(methodName, field.getType());
                         //反射出方法和field
                      Object value = null;
                      //下面如果不指定类型会产生类型不匹配错误
                      if (field.getType().getName().equals("long")){
                          value =Long.parseLong(valueStr);
                      }
                      if (field.getType().getName().equals("java.lang.String")){
                          value =valueStr;
                      }
                      if (field.getType().getName().equals("int")){
                          value =Integer.parseInt(valueStr);
                      }
                      //每次invoke赋值
                      declaredMethod.invoke(o,value);
                  }
                  Ioc.put(id, o);
              }
      
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9

      ### 4.bean的调用

      #### 4.1通过运行时类获取 bean

      ```java
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
      Student student = (Student) applicationContext.getBean(Student.class);
      System.out.println(student);

这种方式存在一个问题,配置文件中一个数据类型的对象只能有一个实例,否则会抛出异常,因为没有唯一的 bean。还是推荐直接用id获取bean;

4.2通过有参构造创建 bean通过有参构造创建 bean

  • 在实体类中创建对应的有参构造函数。
  • 三种方式,推荐下面第一个,如果不写name或者index就需要按对应顺序写
1
2
3
4
5
6
<bean id="student3" class="com.Lei.pojo.Student">
<constructor-arg name="id" value="3"></constructor-arg>
<constructor-arg name="name" value="小明"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="address" ref="address"></constructor-arg>
</bean>
1
2
3
4
5
6
<bean id="student3" class="com.Lei.pojo.Student">
<constructor-arg index="0" value="3"></constructor-arg>
<constructor-arg index="2" value="18"></constructor-arg>
<constructor-arg index="1" value="小明"></constructor-arg>
<constructor-arg index="3" ref="address"></constructor-arg>
</bean>

4.3给bean注入集合

  • 将原来的实体类换成集合

    • public class Student {
          private long id;
          private String name;
          private int age;
          private List<Address> addresses;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33

      - 重新配置xml

      - ```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:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
      ">
      <bean id="student" class="com.Lei.pojo.Student">
      <property name="id" value="0"></property>
      <property name="name" value="张三"></property>
      <property name="age" value="10"></property>
      <property name="addresses" >
      <!--这里将addresses写入一个集合里-->
      <list>
      <ref bean="address01"></ref>
      <ref bean="address02"></ref>
      </list>
      </property>
      </bean>
      <bean id="address01" class="com.Lei.pojo.Address">
      <property name="id" value="1"></property>
      <property name="name" value="科技路"></property>
      </bean>
      <bean id="address02" class="com.Lei.pojo.Address">
      <property name="id" value="2"></property>
      <property name="name" value="高新区"></property>
      </bean>
      </beans>

5.作用域

Spring 管理的 bean 是根据 scope 来生成的,表示 bean 的作用域,共4种,默认值是 singleton。

  • singleton:单例,表示通过 IoC 容器获取的 bean 是唯一的。
  • prototype:原型,表示通过 IoC 容器获取的 bean 是不同的。
  • request:请求,表示在一次 HTTP 请求内有效。
  • session:回话,表示在一个用户会话内有效。

request 和 session 只适用于 Web 项目,大多数情况下,使用单例和原型较多。

特点:

  • prototype 模式当业务代码获取 IoC 容器中的 bean 时,Spring 才去调用无参构造创建对应的 bean。能优先判断是否需要创建,只有需要的时候才会创建,但是创建要更多空间。
  • singleton 模式无论业务代码是否获取 IoC 容器中的 bean,Spring 在加载 spring.xml 时就会创建 bean。耗资源省时间

6.Spring继承

  1. 与 Java 的继承不同,Java 是类层面的继承,子类可以继承父类的内部结构信息;Spring 是对象层面的继承,子对象可以继承父对象的属性值。
  2. Spring 的继承关注点在于具体的对象,而不在于类,即不同的两个类的实例化对象可以完成继承,前提是子对象必须包含父对象的所有属性,同时可以在此基础上添加其他的属性。
  • xml文件配置

    • 直接用parent字段就可以实现继承并且将原来的值全部赋好

    • <bean id="student02" class="com.Lei.pojo.Student" parent="student"></bean>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - 测试

      - ```java
      public static void main(String[] args) {
      ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring.xml");
      Student student = (Student) applicationContext.getBean("student02");
      System.out.println(student);
      }
      //结果:Student(id=0, name=张三, age=10, addresses=[Address(id=1, name=科技路), Address(id=2, name=高新区)])

7.Spring依赖

与继承类似,依赖也是描述 bean 和 bean 之间的一种关系,配置依赖之后,被依赖的 bean 一定先创建,再创建依赖的 bean,A 依赖于 B,先创建 B,再创建 A。

  • 创建依赖类

    • public class User {
          private long id;
          private String name;
      
          public User() {
              System.out.println("创建了User");
          }
      }
      
      1
      2
      3
      4
      5

      - xml配置

      - ```xml
      <bean id="user" class="com.Lei.pojo.User" depends-on="student"></bean>
  • 如果不加依赖就是按照xml的顺序直接创建,加了依赖一定先创建依赖的类

8.Spring 的 p 命名空间

p 命名空间是对 IoC / DI 的简化操作,使用 p 命名空间可以更加方便的完成 bean 的配置以及 bean 之间的依赖注入。

1
2
3
<bean id="student" class="com.southwind.entity.Student" p:id="1" p:name="张三" p:age="22" p:address-ref="address"></bean>

<bean id="address" class="com.southwind.entity.Address" p:id="2" p:name="科技路"></bean>

其实和property一样,简化写法

9.Spring工厂

IoC 通过工厂模式创建 bean 的方式有两种:

  1. 静态工厂

    • 创建实例类

      • public class Car {
            private long id;
            private String name;
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        - 创建静态工厂类

        - ```java
        public class StaticFactory {
        private static Map<Long,Car> carMap =new HashMap<>();//通过Map键值对直接存储静态内容
        static {
        carMap.put(1L,new Car(1L,"宝马"));
        carMap.put(2L,new Car(2L,"奔驰"));
        }
        public static Car getCar(Long id){
        return carMap.get(id);
        }
        }
    • 编写xml

      • <!-- 配置静态工厂创建 Car -->
        <bean id="car" class="com.Lei.pojo.StaticFactory" factory-method="getCar" >
            <constructor-arg value="1"></constructor-arg>
        </bean>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        - 主程序测试

        - ```java
        public static void main(String[] args) {
        ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring-factory.xml");
        Car car = (Car) applicationContext.getBean("car");
        System.out.println(car);
        }
        //结果:Car(id=1, name=宝马)
  2. 实例工厂

    • 创建实例工厂类->通过无参构造来创建对象

      • public class InstanceCarFactory {
            Map<Long, Car> carMap = new HashMap<>();
        
            public InstanceCarFactory() {
                carMap.put(1L, new Car(1L, "宝马"));
                carMap.put(2L, new Car(2L, "奔驰"));
            }
            public Car getCar(Long id) {
                return carMap.get(id);
            }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8

        - 编写xml

        - ```xml
        <bean id="CarFactory" class="com.Lei.pojo.InstanceCarFactory"></bean>
        <bean id="car" factory-bean="CarFactory" factory-method="getCar">
        <constructor-arg value="1"></constructor-arg>
        </bean>
      • 由于创建实例工厂需要首先工厂的无参构造执行,所以需要两个bean

    • 主程序测试

      • public static void main(String[] args) {
            ApplicationContext applicationContext =new ClassPathXmlApplicationContext("Spring-factory.xml");
            Car car = (Car) applicationContext.getBean("car");
            System.out.println(car);
        }
        //结果:Car(id=1, name=宝马)
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20

        ### 10.IoC 自动装载(Autowire)

        IoC 负责创建对象,DI 负责完成对象的依赖注入,通过配置 property 标签的 ref 属性来完成,同时 Spring 提供了另外一种更加简便的依赖注入方式:自动装载,不需要手动配置 property,IoC 容器会自动选择 bean 完成注入。

        自动装载有两种方式:

        - byName:通过属性名自动装载,找实体类中属性名与bean中对应的id
        - byType:通过属性的数据类型自动装载,找同一致的数据类型

        ```xml
        <bean id="cars" class="com.southwind.entity.Car">
        <property name="id" value="1"></property>
        <property name="name" value="宝马"></property>
        </bean>

        <bean id="person" class="com.southwind.entity.Person" autowire="byName">
        <property name="id" value="11"></property>
        <property name="name" value="张三"></property>
        </bean>

byType 需要注意,如果同时存在两个及以上的符合条件的 bean 时,自动装载会抛出异常。

11.Aop切面

AOP:Aspect Oriented Programming 面向切面编程。

AOP 的优点:

  • 降低模块之间的耦合度。
  • 使系统更容易扩展。
  • 更好的代码复用。
  • 非业务代码更加集中,不分散,便于统一管理。
  • 业务代码更加简洁存粹,不参杂其他代码的影响。

AOP 是对面向对象编程的一个补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是 AOP。

具体类比实现:

  1. 导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.11.RELEASE</version>
</dependency>
  1. 编写需要实现切面的接口
1
2
3
4
5
6
public interface Cal{
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
  1. 实现接口的实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component()//添加注解可以直接在spring.xml中获得bean,如果括号里有参数那么就将id映射
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1+num2;
return result;
}

public int sub(int num1, int num2) {
int result = num1-num2;
return result;
}

public int mul(int num1, int num2) {
int result = num1*num2;
return result;
}

public int div(int num1, int num2) {
int result = num1/num2;
return result;
}
}
  1. 编写抽象出来的切面类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Component
@Aspect//添加切面注解
public class LoggerAspect {
//还是通过注解的形式,execution
@Before(value = "execution(* com.Lei.pojo.CalImpl.*(..))")
public void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name + "方法的参数是"+args);
}
@After(value = "execution(* com.Lei.pojo.CalImpl.*(..))")
public void after(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行完毕");
}
@AfterReturning(value = "execution(* com.Lei.pojo.CalImpl.*(..))",returning = "res")
public void afterRun(JoinPoint joinPoint,Object res){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法执行后的结果是:"+res);
}

}
  • 关于execution表达式
标识符 含义
execution() 表达式的主体
第一个“*”符号 表示返回值的类型任意
com.Lei.pojo.CalImpl AOP所切的服务的包名,即,需要进行横切的业务类
包名后面的“..” 表示当前包及子包
.*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
  • 切面出来的JoinPoint连接点,那么就是可以用来get各种参数的方法

  • 分别有四种注解Before,After,AfterReturning,AfterThrowing分别用来表示执行前,后,执行的结果集,执行出的异常

  1. 编写切面的xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
">
<!--上面是文件声明,黏贴就好-->
<context:component-scan base-package="com.Lei"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  • context:component-scan 自动扫描包里的含有component的字段生成bean
  • aop:aspectj-autoproxyaop自动的动态代理