Spring 入门学习

116 阅读9分钟

IOC

IOC(Inversion of control) :控制反转,是一个理论,概念,思想。描述的是把对象的创建,复制,管理工作都交给代码之外的容器实现,也就是对象的创建是由其他的外部资源完成。

  • 控制:创建对象,对象属性的赋值,对象之间的关系管理。

  • 反转:把原来开发人员管理,创建对象的权限转移给代码之外的容器实现。由容器代替开发人员管理对象。创建对象,给属性赋值

  • 正转:由开发人员在代码中,使用new 构造方法创建对象,开发人员主动管理对象。

  • 容器: 是一个服务器软件,一个框架(例如Spring)

  • 为什么使用IOC:目的就是减少对代码的改动,也能实现不同的功能,实现解耦合。

Java中创建对象的方式?

  1. 构造方法,new Student()
  2. 反射
  3. 序列化
  4. 克隆
  5. ioc容器创建对象
  6. 动态代理

IOC的体现:

servlet:

  1. 创建类集成HttpServlet
  2. 在web.xml 注册servlet
  3. 没有创建Servlet对象,没有MyServlet s=new Myservlet();
  4. Servlet 是Tomcat服务器创建的,Tomcat也称为容器,Tomcat作为容器:里面存放的有Servlet对象,Listener,Filter对象。

IOC的技术实现: DI是IOC的技术实现。 DI(Dependency Injection):依赖注入,只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中赋值,查找,都由容器内部实现

Spring 是使用DI实现了IOC的功能,Spring底层创建对象,是通过反射机制

引入Spring依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.14</version>
</dependency>

sources文件夹中创建Spring配置文件beans.xml,名称自定义

beans是配置文件的跟标签,spring把java对象称为bean spring-beans.xsd 是约束文件,和 mybatis指定dtd是一样的

<?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="someService" class="zengqiang.service.impl.SomeServiceImpl"/>
</beans>

bean标签作用 声明bean,就是告诉Spring要创建某个类的对象

  • id: 对象的自定义名称,唯一值。spring通过这个名称找到对象
  • class:类的全限定名称(不能是接口,因为spring 是反射机制创建对选哪个,必须使用类)
  • spring是把创建好的对象放入到map中,spring框架有一个map存放对象。springMap.put(id的值,对象).例如 springMap.put("someService",new SomeServiceImpl()); 一个bean标签声明一个对象

第一个例子:

@Test
fun test02() {
    // 使用spring容器创建对象
    // 指定spring配置文件的名称
    val config = "beans2.xml"
    // 创建表示spring容器的对象,ApplicationContext
    // ApplicationContext 就是表示Spring容器,通过容器获取对象
    // ClassPathXmlApplicationContext :表示从类路径中加载Spring的配置文件
    val applicationContext = ClassPathXmlApplicationContext(config)
    // 从容器中获取某个对象,要调用对象的方法
    // getBean("配置文件中的bean的id值")
    val someService = applicationContext.getBean("someService") as SomeService
    // 使用Spring创建好的对象
    someService.doSome()
}

Spring创建对象的时机 Spring在创建Spring容器时,会创建所有的对象。即执行 val applicationContext = ClassPathXmlApplicationContext(config)的时候

Spring创建对象默认调用无参构造方法

获取Spring容器中对象的信息

@Test
fun test03() {
    val config = "beans2.xml"
    val applicationContext = ClassPathXmlApplicationContext(config)
    //获取容器中的对象数量
    println(applicationContext.beanDefinitionCount)
    //获取容器中每个对象的名称
    applicationContext.beanDefinitionNames.forEach {
        println(it)
    }
}

创建一个非自定义类的对象

bean.xml

<bean id="date" class="java.util.Date"/>
@Test
fun test04() {
    val config = "beans2.xml"
    val applicationContext = ClassPathXmlApplicationContext(config)
    val date = applicationContext.getBean("date") as Date
    print(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date))

}

DI

DI:依赖注入,表示创建对象,给属性赋值

DI的实现有两种:

  1. 在Spring的配置文件中,使用标签和属性完成,叫做基于XML的DI实现
  2. 在Spring中的朱姐,完成属性赋值,叫做基于注解的DI实现

DI的语法分类

  1. set注入(设置注入):Spring调用类的set方法,在set方法可以实现属性的赋值。80左右都是使用set注入
  2. 构造注入,Spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值。

注入:就是赋值的意思

DI:给属性赋值

  1. set注入(设置注入):Spring调用类的set方法,可以在set方法中完成属性赋值 1.简单类型的set注入
    语法:
    <bean id="xx" class="yy>
        <property name="属性名字" value="此属性的值"/>
        一个property只能给一个属性赋值
        <property......>
    

实例:

applicationContext.xml:

<!--声明Student对象-->
<bean id="student" class="zengqiang.di.bao01.Student">
    <property name="name" value="李四"/>
    <property name="age" value="20"/>
</bean>

kt代码:
fun test01() {
    val config = "bao01/applicationContext.xml"
    val applicationContext = ClassPathXmlApplicationContext(config)
    val student = applicationContext.getBean("student") as Student
    println(student.toString())

}

spring使用property属性赋值最终调用的还是属性的set方法。所以没有set方法是不行的。

给引用类型属性set注入

<bean id="student" class="zengqiang.di.bao02.Student">
    <property name="name" value="lisi"/>
    <property name="age" value="22"/>
    <property name="school" ref="school"/>
</bean>
<bean id="school" class="zengqiang.di.bao02.School">
    <property name="name" value="清华大学"/>
    <property name="address" value="北京"/>
</bean>

@Test
fun test02() {
    val config = "bao02/applicationContext.xml"
    val applicationContext = ClassPathXmlApplicationContext(config)
    val student = applicationContext.getBean("student") as Student02
    println(student.toString())

}

构造注入

使用name参数(推荐使用)
<bean id="constructorStudent" class="zengqiang.di.bao02.Student">
    <constructor-arg name="name" value="10"/>
    <constructor-arg name="age" value="22"/>
    <constructor-arg name="school" ref="school"/>
</bean>

<!--index-->
<bean id="student2" class="zengqiang.di.bao02.Student">
    <constructor-arg name="name" value="学生2"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="school" ref="school"/>
</bean>
<!--  省略index-->
<bean id="student3" class="zengqiang.di.bao02.Student">
    <constructor-arg value="学生3"/>
    <constructor-arg value="25"/>
    <constructor-arg ref="school"/>
</bean>

引用类型的自动注入(byName,byType)

byName实例

<bean name="school" class="zengqiang.di.bao04.School">
    <property name="name" value="家里蹲"/>
    <property name="address" value="江苏省花桥镇"/>

</bean>
<!--    添加autowire="byName" 将自动给Student中的school属性赋值为 同名的bean对应的对象-->
<bean name="student" class="zengqiang.di.bao04.Student" autowire="byName">
    <property name="name" value="李增强"/>
    <property name="age" value="25"/>
</bean>

byType实例

<bean name="mySchool" class="zengqiang.di.bao04.School">
    <property name="name" value="家里蹲"/>
    <property name="address" value="江苏省花桥镇"/>
</bean>
<!--    添加autowire="byType" 将自动给Student中的school属性赋值为 同类型的bean对应的对象-->
<bean name="student" class="zengqiang.di.bao04.Student" autowire="byType">
    <property name="name" value="任香芹"/>
    <property name="age" value="18"/>
</bean>

byType注意: 如果一个对象中的引用类型有两个同类型的bean则会导致异常

多配置文件 为了解决项目过大,applicationContext.xml 过于庞大问题。 案例:

spring-school.xml

<bean name="school" class="zengqiang.di.bao05.School">
    <property name="name" value="清华大学"/>
    <property name="address" value="北京"/>
</bean>
spring-student.xml

<bean name="student" class="zengqiang.di.bao05.Student" autowire="byType">
    <property name="name" value="张三"/>
    <property name="age" value="25"/>
</bean>
total.xml主配置文件
<!--语法:<import resource="路径"
关键字:classpath: 表示类路径(class文件所在的目录)-->
<import resource="classpath:bao05/spring-school.xml"/>
<import resource="classpath:bao05/spring-student.xml"/>
total.xml主配置文件
<!--    在包含关系的配置文件中,可以使用通配符,注意:主的配置文件名称不能包含在通配符的范围内-->
<import resource="classpath:bao05/spring-*.xml"/>

如果使用通配符方案,需要放在一个目录里面,直接放到resource文件夹中spring无法进行读取

基于注解的DI

通过注解完成java对象的创建,属性的赋值。

  1. 加入Maven依赖spring-context,在加入spring-context的同时,间接加入spring-aop的依赖。使用注解必须加入spring-aop依赖

  2. 在类中加入Spring的注解(多个不同功能的注解)

  3. 在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你项目中的位置

学习的注解

  • @Component
  • @Respository
  • @Service
  • @Controller
  • @Value
  • @Autowired
  • @Resource

@Component

package ba01;

import org.springframework.stereotype.Component;

/**
 * @Component 创建对象的,等同与<bean></bean>的功能
 * 属性 value就是对象的名称,也就是bean的id值
 * value的值是唯一的,创建对象在整个spring容器中就一个
 * 此注解作用在类上
 *
 * @Component(value = "myStudent")等同于
 * <bean id="myStudent" ></bean>
 *
 * 声明组件扫描器(component-scan), 组件就是java对象
 * base-package :指定注解在你项目中的包名
 * component-scan工作方式:spring会扫描遍历base-package指定的包,
 * 把包中和子包中所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值
 * 在applicationContext.xml中
 * <context:component-scan base-package=""/>
 */
@Component(value="student")
public class Student {
    private String name;
    private String age;

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + ''' +
                ", age='" + age + ''' +
                '}';
    }
}

Component如果没有指定value,则生成的对象名称为类名小写

Spring中和@Component 功能一直的注解还有:

  1. @Respository(用在持久层上面):放在dao的实现类上面,表示创建dao对象,dao对象是用来访问数据库的
  2. @Service(用在业务层上面): 放在service的实现类上面,创建Service对象,Service对象是做业务处理,可以有事务等功能。
  3. @Controller(用在控制器上面):放在控制器上面,创建控制器对象的,控制器对象,能够接受用户提交的参数,显示请求的处理结果。

以上三个注解的使用语法和@component一样的。都能创建对象,但是这三个注解还有额外的功能。主要是给项目分层的。

如何指定多个组件扫描器的包

1. 多次使用<context-component-scan/>
2. 使用<context-component-scan base-package="包1,包2,包3 3...."/>,包之间使用逗号分隔
3. 指定父包(如果是顶级包的话,会扫描所有包,效率较低,根据情况选择使用)

@Value

简单类型的属性赋值

  • 属性:value是String类型,表示简单类型的属性值
  • 位置:
    • 在属性定义的上面,无需set方法,推荐使用
    • 在set方法上面

Autowired

引用类型的属性赋值

  1. 使用的是自动注入原理,支持byName,byType

  2. 默认使用的是byType创建

  3. 多添加一个@Qualifier("对象名称"),就会使用byName进行注入

  4. required属性默认true,required=true:表示引用类型赋值失败,将会报错,如果required=false:表示如果引用类型赋值失败,正常执行,只是引用类型为null

@Resource (这个是jdk中的注解)

引用类型的属性赋值

  • 使用的是自动注入原理,支持byName,byType
  • 默认使用的是byName创建,如果byName失败,再用byType创建
  • 如果给value赋值,则表示只使用byName去进行对象创建

经常改变使用配置文件,不经常改变使用注解

AOP

  1. 动态代理
  • jdk动态代理,使用jdk中的Proxy,Method,InvocationHandler创建代理对象。
  • cglib动态代理:第三方工具库,创建代理对象,原理是继承。通过继承目标类,创建子类,子类就是代理对象。要求目标类不能是final,方法也不能是final的
  1. AOP 面向切面编程,基于动态代理,可以使用jdk,cglib两种代理方式。 Aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了 AOP:面向切面编程(Aspect Oriented Programmering)

术语:

  1. Aspect:切面,表示增强的功能,就是一堆代码,完成某个功能。非业务功能,常见的切面功能主要有日志,事务,统计信息,参数检查,权限验证。
  2. JoinPoint:连接点,连接业务中方法和切面的位置。就是某个类中的业务方法 3.目标对象:给哪个类的方法增加功能,这个类就是目标对象
  3. Advice:通知,通知表示切面功能执行的时间

说一个切面有三个关键的要素:

  1. 切面的功能代码,切面要干什么
  2. 切面的执行位置,使用Pointcut表示切面执行的位置
  3. 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

AOP的实现

aop是一个规范,是动态的一个规范化,一个标准 AOP的技术实现框架:

  1. Spring:Spring内部实现了AOP的规范,能做AOP的工作,Spring主要在事务处理时使用AOP。我们项目开发中很少使用Spring的AOP实现,因为Spring的AOP比较笨重。
  2. aspectJ:一个开源的专门做AOP的框架。Spring框架中集成了aspectJ框架,通过Spring就能使用aspectJ的功能。aspectJ框架实现AOP有两种方式: 1.使用xml的配置文件:配置全局事务 2.使用注解,我们在项目中要做AOP功能,一般都使用注解,aspectJ有5个注解

学习aspectJ框架的使用

1.切面的执行时间,这个执行时间在规范中叫做Advice(通知,增强) 1.@Before 2.@AfterReturning 3.@Around 4.@AfterThrowing 5.@After 2.切入点表达式