Spring入门

2022-05-05 19:54:19 浏览数 (1)

Spring相关知识点整理

  • Spring体系结构
  • Spring程序开发步骤
  • Spring配置文件
    • Bean标签的基本配置
    • Bean标签的范围配置
      • 默认情况下演示:
      • ` ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");`
    • 内部Bean----匿名,外部无法访问,无别名
    • util名称空间---创建集合的id,方便引用
    • 级联属性----选择属性的属性
    • ref引用是地址引用,可以理解为c 里面的地址传递
    • 继承配置信息
    • abstract---当前bean只能被其他bean继承相关配置数据,而无法创建实例化的bean对象,不用写全类名
    • IOC容器创建时候,容器中的所有Bean对象也会随之创建,并且Bean标签就等同于new 一个Bean对象,会在创建的Bean对象的时候,调用其无参构造。
    • Bean的创建顺序是按照xml中配置顺序创建的
    • Spring 给Bean属性注入null值
    • 在容器中注册一个组件(Bean标签就是组件的注册,等同于new一个Bean对象)时,同一个组件(对象)默认是单例的,容器启动完成,容器中所有组件创建完毕
    • IOC容器在创建组件对象的时候,如果使用Property标签对属性进行赋值,那么默认利用对象的setter属性进行赋值
    • javaBean对象的属性名由对象的setter方法,去掉set后面,后面的那一串首字母小写就是属性名
    • getBean方法的三个重置版本
      • 单例情况下演示:
      • 多例演示:
      • 注意
    • Bean(对象)的生命周期配置
    • 单例Bean的生命周期: (容器启动)构造器------>初始化方法---->容器关闭(销毁方法)
    • 多例Bean的生命周期: 获取Bean(构造器---->初始化方法)---->容器关闭不会调用Bean的销毁方法
    • 后置处理器----在Bean初始化前面调用该方法
      • 单例:(容器启动)构造器--->后置处理器before---->初始化方法---->后置处理器的after方法----》容器关闭(销毁方法)
      • 无论bean是否有初始化方法,后置处理器都会默认其有,还会继续工作
    • Bean(对象)实例化的三种方式
      • 工厂静态方法实例化Bean(对象)---返回的是Bean对象,而不是工厂对象
      • 工厂的实例方法
    • FactoryBean: 是Spring规定的一个接口,只要是这个接口的实现类,Spring都默认是一个工厂,并且无论指定工厂中创建的对象是单例还是多例,都是在获取的时候才会创建对象,IOC容器启动的时候不会创建实例
  • 依赖注入方式
    • set方式
    • 通过xml配置方式完成对属性的赋值,基本都使用set方式,切莫忘记在对应的类中给属性添加set方法
    • set简便注入方式---P命名空间注入
    • 有参构造注入时,不会再调用Bean对象的无参构造,直接走对应的有参构造
      • 调用有参构造器创建对象时,如果写了name属性,那么可以编制有参构造的参数顺序来为属性赋值,但是被赋值的参数个数,必须与对象中存在的有参构造的参数个数匹配,例如:我在类中只写了一个有两个参数的有参构造,那么在调用有参构造赋值的时候,必须给指定的两个参数赋值
      • 如果在调用有参构造创建对象的时候省略了name属性,那么必须严格按照构造器的参数的顺序挨个赋值
      • 调用有参构造创建对象的时候,可以通过index指定参数的索引,从0开始,并且如果出现了有参构造重载,还可以利用type指定参数的类型
  • Bean的依赖注入的数据类型
    • 普通数据类型的注入----通过类的set方法
    • 集合的注入
    • 引入其他配置文件,分模块开发----import标签
    • Spring重点配置
  • ApplicationContext的继承体系
  • ApplicationContext的实现类
  • getBean()两个重载方法的使用
    • 两种方法的区别
  • Spring配置数据源---连接池
    • 数据库连接池作为单实例是最好的,一个项目就一个连接池,连接池里面管理很多连接,连接是直接从连接池拿,因此可以让Spring帮我们创建连接池对象
    • 数据源(连接池)的作用
    • 数据源的开发步骤
    • 数据源的手动创建
    • 抽取jdbc.properties文件
  • Spring配置数据源
    • 按照类型获取组件,可以获取到这个类型下所有实现的子类---DataSource类是所有数据连接池对象的基类
  • Spring加载properties配置文件----将配置文件相关内容放到容器中
    • 加载外部配置文件的固定写法classpath: 表示引用类路径下的一个资源
    • ${}动态取出配置文件中某个key对应的值,但是注意username是Spring的key的一个关键字,为了防止配置文件中的key和spring自己的关键字冲突,起名的时候一般会加上一个防止冲突的前缀
  • Spring注解开发
    • Spring原始注解
    • 注解的组件扫描
    • 代码演示
      • 在进行组件扫描的时候,还是需要引入context命名空间,配置好基础包后,Spring会扫描基础包及其子包
      • 注解后面不标注ID,那么默认ID是类名的首字母小写
      • 组件的作用域默认是单例的,可以通过scope注解进行修改
    • xml方式注入和注解方式注入的一些区别
    • @Autowired和 @Qualifier的原理
      • @Autowired标注的自动装配的属性默认是一定装配上的,如果不能装配上,会报错
      • @Autowired(required=false):如果无法装配上,赋值为Null
      • @Autowired自动注入只针对引用类型生效,普通属性赋值使用value注解
      • @Autowired可以标注在方法上,@Qualifier可以标注在方法的形参上,装配原理和上面一致
      • @Autowired标注的方法,会在单例Bean创建的时候自动调用,即容器创建的时候调用
    • @Autowired注解详细使用规则参加下面两篇文章
    • @Autowired当标注的属性是接口时,其实注入的是这个接口的实现类,具体细节参考上面这篇文章链接
    • resource注解
      • @Resource和@Autowired的区别
    • 通过注解对属性完成注入---value注解
      • @Value注解常用方式---配合配置文件
        • 注意: xml中需要引入相关配置文件以及组件扫描
    • scope注解----设置对象的作用范围
    • postConstruct和predestory注解
  • Spring新注解
    • 核心配置类
    • 数据源配置类
    • 测试类
  • Spring集成Junit
    • 原始Junit测试Spring的问题
    • 解决思路
    • Spring集成Junit的步骤
    • spring单元测试原理(ContextConfiguration和runwith注解)
    • 代码实现
    • 泛型依赖注入原理图
    • IOC部分总结
  • Spring集成web环境
    • 导入servlet和jsp的坐标
  • 监听器的妙用---加载配置文件
    • 针对创建app对象时,xml配置文件路径写死的优化
      • 通过监听器的全局参数来进行优化
    • 针对在获取上下文对象时,属性名写死的优化
  • Spring提供获取应用上下文的工具---上面是铺垫
    • 我们需要做的事情
  • AOP
    • 什么是AOP
    • AOP底层实现
    • AOP动态代理技术
      • JDK动态代理实例演示
      • cglib动态代理实例演示
    • AOP相关概念
      • 连接点可以理解为可以被增强的方法
      • 切入点(切点): 对哪些连接点进行了配置,完成了对方法的增强,可以理解为被增强了的方法
      • advice: 简单理解为对要增强的方法中的增强逻辑的封装,封装为一个对象,这个对象就是增强对象,增强对象的方法就是增强逻辑
      • 切面: 目标方法加逻辑增强
      • 织入: 切点和增强结合的过程
    • AOP开发明确事项
    • 知识要点
  • 基于XML的AOP开发
    • 速入门的步骤
      • 1.导入aspectj的坐标
      • 2.目标接口和目标类
      • 接口不用加载到容器中,即使加载到了容器中,也不会创建对象,相当于告诉了Spring容器,ioc容器中可能有这种类型的组件
      • 3.切面类
      • 4.目标类和切面类交给Spring容器去创建
      • 5.在app.xml中配置织入关系---引入aop命名空间
      • 6.测试代码
      • 如果目标类有实现接口,那么AOP底层原理即使jdk动态代理,容器中保存的组件是其代理对象,而非本类类型,因此再使用getBean获取实例的时候,如果通过字节码文件类型获取,参数应该填入接口类型,返回值也用接口类型来接
      • 如果目标类没有实现接口,那么AOP底层实现原理就是cglib,我们可以用目标类去接,相当于多态形式
    • 切点表达式的写法
      • 切点表达式支持的通配符
    • 通知的类型
      • 通知的配置语法
      • before,after,aroud演示
      • after-throwing
      • after---最终增强,不管是否抛出异常,都会执行
    • 切点表达式的抽取
    • xml配置中也可以在指定前置...方法的时候,通过设置returning和throwing参数,来获取相应的返回值和异常信息
    • 抽取后得到的pointcut标签,如果写在了当前的切面类里面,那么也只能够在当前切面类里面引用,如果想扩大作用域范围,可以将标签写在config表签下面,相当于一个全局变量
    • 可以给aspect标签里面加上order属性,来指定切面的执行顺序
      • 知识要点
  • 基于注解的AOP开发
    • 快速入门的步骤
    • 别忘记开启基于注解的aop功能--->aop自动代理
    • 注解通知类型
      • 通知方法的执行顺序
    • 在通知方法运行时,拿到目标方法的详细信息----JoinPoint
    • throwing和returning来指定哪个参数用来接收异常和返回值
    • spring通知方法的参数列表一定不能乱写
    • 上面returning和throwing用来接收异常和返回值信息的指定参数的数据类型,最好往大了写,不然可能无法接收到数据
    • 切点表达式的抽取----随便声明一个返回值为void的空方法
    • 可以使用环绕通知完成四合一功能: 前置,返回,异常,后置
    • 如果在使用环绕通知时,代码里面对异常进行了抓取,为了能让外界知道这个异常,这个异常一定要抛出去
    • 环绕通知优先于普通通知执行
    • 如果想要通过通知来实现动态代理的功能,那么就可以选择环绕通知,利用里面的ProceedingJoinPoint 类型的对象,完成对方法执行的干预
    • 多切面运行顺序
    • 环绕只影响当前切面,他的优先执行顺序只体现在它所在的当前切面,与其他切面无关
    • AOP的使用场景
  • SpringJDBCTemplte
    • JDBCTemplte开发步骤
      • 1.导入spring-jdbc和spring-tx以及其他的一些坐标
      • 创建数据源,设置数据源,执行操作
    • 通过Spring让我们产生模板对象
    • 抽取数据库连接池配置时填入的参数,放到properties配置文件中
      • 在Spring容器中引入pro配置文件,然后修改刚才传入的参数---配置数据库的模板

Spring体系结构

Spring程序开发步骤

1.创建一个maven项目,在pom.xml中导入spring的坐标

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>SpringDemo2</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.15.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2.创建UserDao接口和UserDao接口的实现类UserDaoImpl 3.创建spring的xml配置文件,然后在其中给实现类标注id

代码语言:javascript复制
   <bean id="userDao" class="com.impl.UserDaoImpl"></bean>
  1. 创建一个测试Demo,查看是否能够获取指定实现类对象,并调用其方法
代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        userDao.say();
    }
}

Spring配置文件

Bean标签的基本配置

Bean标签的范围配置

默认是singleton

默认情况下演示:

代码语言:javascript复制
    <bean id="userDao" class="com.impl.UserDaoImpl"></bean>
代码语言:javascript复制
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        UserDao userDao1=(UserDao)app.getBean("userDao");
        System.out.println(userDao);
        System.out.println(userDao1);

默认单例模式

ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");

意思是加载配置文件,创建Spring容器

内部Bean----匿名,外部无法访问,无别名

1.在或内部通过定义的, 2.该bean不管是否指定id或者name,该bean都有一个唯一的匿名标识符,且不能被指定别名 3.该bean队其他外部的bean不可见。

util名称空间—创建集合的id,方便引用

Util名称空间创建的集合有id,可以给其他bean引用,在使用前需要引入名称空间: xmlns:util=“http://www.springframework.org/schema/util”

代码语言:javascript复制
<?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:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
		
	<!-- 相当于new LinkedHashMap() -->
	<util:map id="myMap">
		<entry key="key01" value="value01"></entry>
	</util:map>
	
	<bean id="person" class="com.atguigu.bean.Person">
		<!-- 引用myMap ->
		<property name="map" ref="myMap"></property>
	</bean>
</beans>

级联属性----选择属性的属性

通过属性(a)选择属性(a)的属性(b)

代码语言:javascript复制
	<bean id="car01" class="com.atguigu.bean.Car">
		<property name="carName" value="宝马"></property>
	</bean>

	<!-- 级联属性:属性的属性 -->
	<bean id="person01" class="com.atguigu.bean.Person">
		<!-- 为car赋值的时候改变car的属性 -->
		<property name="car" ref="car01"></property>
		<!-- 给car属性的carName属性赋值 -->
		<property name="car.carName" value="奔驰"></property>
	</bean>

ref引用是地址引用,可以理解为c 里面的地址传递

继承配置信息

在bean定义中含了大量的配置信息,其中包括容器相关的信息(比如初始化方法、静态工厂方法名等等)以及构造器参数和属性值。子bean定义就是从父bean定义继承配置数据的bean定义。它可以覆盖父bean的一些值,或者添加一些它需要的值。使用父/子bean定义的形式可以节省很多的输入工作。实际上,这就是一种模板形式。

class全类名也会继承,但是只是继承配置信息,而不是父子关系

全类名省略不写的前提时,当前bean对象的类型与继承的bean类型一致

如果需要对继承的数据进行修改,就自行对相关属性再赋值,完成值的覆盖

代码语言:javascript复制
    <bean id="parent" class="com.timo.domain.Parent">
        <property name="name" value="ouyangfeng"/>
     </bean>
    <!--下面的parent表示这个child的bean的父亲是id=parent的这个类-->
    <bean id="child" class="com.timo.domain.Child" parent="parent">
        <property name="age" value="18"/>
     </bean>

abstract—当前bean只能被其他bean继承相关配置数据,而无法创建实例化的bean对象,不用写全类名

下面说的就是: 抽象bean不必映射到任何类,即不用写全类名

Spring抽象bean abstract=true声明

IOC容器创建时候,容器中的所有Bean对象也会随之创建,并且Bean标签就等同于new 一个Bean对象,会在创建的Bean对象的时候,调用其无参构造。

Bean的创建顺序是按照xml中配置顺序创建的

Spring 给Bean属性注入null值

在容器中注册一个组件(Bean标签就是组件的注册,等同于new一个Bean对象)时,同一个组件(对象)默认是单例的,容器启动完成,容器中所有组件创建完毕

IOC容器在创建组件对象的时候,如果使用Property标签对属性进行赋值,那么默认利用对象的setter属性进行赋值

javaBean对象的属性名由对象的setter方法,去掉set后面,后面的那一串首字母小写就是属性名

getBean方法的三个重置版本

代码语言:javascript复制
getBean(people.class);//类型查找
getBean("people01");//ID查找
getBean("people01",people.class);//ID加类型查找

单例情况下演示:

代码语言:javascript复制
  <bean id="userDao" class="com.impl.UserDaoImpl" scope="singleton"></bean>

多例演示:

代码语言:javascript复制
 <bean id="userDao" class="com.impl.UserDaoImpl" scope="prototype"></bean>

多例模式每一次获取的对象都不相同

注意

Bean(对象)的生命周期配置

UserDaoImpl类:

代码语言:javascript复制
package com.impl;

import com.UserDao;

public class UserDaoImpl implements UserDao {
    @Override
    public void say() {
        System.out.println("用户登录");
    }
    void init()
    {
        System.out.println("初始化中...");
    }
    void destory()
    {
        System.out.println("被销毁中....");
    }
}

配置文件:

代码语言:javascript复制
 <bean id="userDao" class="com.impl.UserDaoImpl"  init-method="init" destroy-method="destory" ></bean>

测试类:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
        //需要强转成其子类,才能调用close方法,来关闭容器
        ((ClassPathXmlApplicationContext)app).close();
    }
}

容器没有关闭,所以对象没有被释放,也就不会去调用销毁方法

单例Bean的生命周期: (容器启动)构造器------>初始化方法---->容器关闭(销毁方法)

多例Bean的生命周期: 获取Bean(构造器---->初始化方法)---->容器关闭不会调用Bean的销毁方法

后置处理器----在Bean初始化前面调用该方法

单例:(容器启动)构造器—>后置处理器before---->初始化方法---->后置处理器的after方法----》容器关闭(销毁方法)

无论bean是否有初始化方法,后置处理器都会默认其有,还会继续工作

代码语言:javascript复制
//1.编写后置处理器的实现类
//2.将后置处理器注册在配置文件中
public class MyBeansProcess implements BeanPostProcessor {
    //初始化之前调用
    //Object bean: 将要初始化的bean
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName "Bean将要调用初始化方法" "  " bean);
        return bean;//返回传入的bean
    }
//String beanName: bean在xml中配置的id
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName "Bean初始化方法调用完毕" "  " bean);
        //初始化之后返回的是什么,容器中保存的就是什么
        return bean;
    }
}

配置文件:

代码语言:javascript复制
<bean id="book" class="com.dhy.Factory.book"/>
<bean id="beanPostProcess" class="com.dhy.Factory.MyBeansProcess"/>

book类;

代码语言:javascript复制
public class book {
    book()
    {
        System.out.println("book的初始化方法");
    }

    @Override
    public String toString() {
        return "图书";
    }
}

测试类:

代码语言:javascript复制
public class main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("factory.xml");
        System.out.println(app.getBean("book"));
        if(app.getBean("book")!=null)
            System.out.println("NO");
        else
            System.out.println("YES");
    }
}

Bean(对象)实例化的三种方式

工厂静态方法实例化Bean(对象)—返回的是Bean对象,而不是工厂对象

工厂类:

代码语言:javascript复制
public class sf {
    public  static UserDao  getUserDao()
    {
return new UserDaoImpl();
    }
}

配置文件:

当添加了factory-method属性后,就会去找当前全类名对应下面的getUserDao方法,返回对应的对象

代码语言:javascript复制
 <bean id="userDao" class="com.fac.staticFac.sf"  factory-method="getUserDao"></bean>

测试类:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
        //需要强转成其子类,才能调用close方法,来关闭容器
        ((ClassPathXmlApplicationContext)app).close();
    }
}

工厂的实例方法

工厂类:

代码语言:javascript复制
public class sf {
    public   UserDao  getUserDao()
    {
return new UserDaoImpl();
    }
}

配置文件:

在查找到userDao的ID时,会去查查找ID为f的bean标签,然后创建工厂对象,然后调用其方法,返回一个userDao对象

代码语言:javascript复制
   <bean id="f" class="com.fac.staticFac.sf" ></bean>
    <bean id="userDao" factory-bean="f" factory-method="getUserDao"></bean>

测试类代码不变,:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) app.getBean("userDao");
        System.out.println(userDao);
        //需要强转成其子类,才能调用close方法,来关闭容器
        ((ClassPathXmlApplicationContext)app).close();
    }
}
FactoryBean: 是Spring规定的一个接口,只要是这个接口的实现类,Spring都默认是一个工厂,并且无论指定工厂中创建的对象是单例还是多例,都是在获取的时候才会创建对象,IOC容器启动的时候不会创建实例

实现了接口的工厂类:

代码语言:javascript复制
/*
* 1.编写一个FactoryBean的实现类
* 2.在Spring配置文件中进行注册
* */
public class factory implements FactoryBean {
    /*工厂方法,返回创建的对象
    * */
    public Object getObject() throws Exception {
        Book b=new Book();
        return b;
    }
/*返回创建的对象的类型,Spring会自动调用这个方法来确认创建的对象是什么类型
* */
    public Class<?> getObjectType() {
        return Book.class;
    }
 //是单例吗 true:是单例  false:不是单例
    public boolean isSingleton() {
      return false;
    }
}

配置文件:

代码语言:javascript复制
<bean id="myFacBeanImple" class="com.dhy.Factory.factory"/>

测试类:

代码语言:javascript复制
public class main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("factory.xml");
        //返回的对象是工厂方法里面返回的对象
        System.out.println(app.getBean("myFacBeanImple"));
    }
}

依赖注入方式

UserService类中需要用到User类对象,因此需要将User类注入到UserService类中,下面演示:

set方式

通过xml配置方式完成对属性的赋值,基本都使用set方式,切莫忘记在对应的类中给属性添加set方法

配置文件:

代码语言:javascript复制
    <bean id="userDao" class="com.User.User"></bean>
    <!--property标签写在被注入的类标签内部,即要设置谁的属性,就写在谁的bean标签内部-->
    <bean id="userService" class="com.User.UserService">
    <!--通过属性注入,这里name填入的是截取set方法后set部分后,剩余部分变小写的值,即属性的名字-->
    <!--ref是要注入哪个类,这里注入的是userDao的ID表示的User类-->
    <property  name="user" ref="userDao"></property>
    </bean>

userService类:

代码语言:javascript复制
public class UserService {
    private User user;

    public void setUser(User user) {
        this.user = user;
    }

    public void show()
    {
        //在容器内部,将user对象注入UserService,即通过UserService的set方法,给US的成员变量user赋值
        user.show();
    }
}

user类:

代码语言:javascript复制
public class User {
public void show()
{
    System.out.println("大忽悠来了");
}
}

测试类:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService us=(UserService)app.getBean("userService");
        us.show();
    }
}

注意:只有从容器内部拿出来的UserService才通过US类的setUser方法,完成了对user对象的赋值,即注入,直接new一个,没有用,因此其没有被注入user对象,会报空指针异常

set简便注入方式—P命名空间注入

第一步: 引入p命名空间

代码语言:javascript复制
xmlns:p="http://www.springframework.org/schema/p"

第二步: 修改注入方式

注意: userDao-ref是用来注入对象的,而userDao是用来注入普通属性的

代码语言:javascript复制
    <bean id="userDao" class="com.User.User"></bean>
    <bean id="userService" class="com.User.UserService"
    p:user-ref="userDao">
    </bean>

有参构造注入时,不会再调用Bean对象的无参构造,直接走对应的有参构造

调用有参构造器创建对象时,如果写了name属性,那么可以编制有参构造的参数顺序来为属性赋值,但是被赋值的参数个数,必须与对象中存在的有参构造的参数个数匹配,例如:我在类中只写了一个有两个参数的有参构造,那么在调用有参构造赋值的时候,必须给指定的两个参数赋值

如果在调用有参构造创建对象的时候省略了name属性,那么必须严格按照构造器的参数的顺序挨个赋值

调用有参构造创建对象的时候,可以通过index指定参数的索引,从0开始,并且如果出现了有参构造重载,还可以利用type指定参数的类型

userService类:

代码语言:javascript复制
public class UserService {
    private User user;

    public UserService(User user) {
        this.user = user;
    }

    public UserService() {
    }

    public void show()
    {
        //在容器内部,将user对象注入UserService,即通过UserService的set方法,给US的成员变量user赋值
        user.show();
    }
}

配置文件:

代码语言:javascript复制
    <bean id="userDao" class="com.User.User"></bean>
    <bean id="userService" class="com.User.UserService">
        <constructor-arg name="user" ref="userDao"></constructor-arg>
    </bean>

测试类:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService us=(UserService)app.getBean("userService");
        us.show();
    }
}

Bean的依赖注入的数据类型

普通数据类型的注入----通过类的set方法

给User的name和age属性注入两个值

代码语言:javascript复制
package com.User;

public class User
{
    private  String name;
    private  int age;
public void show()
{
    System.out.println("姓名: " name "  年龄: " age);
}

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

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

配置文件:

代码语言:javascript复制
//这里是通过setName<---Name<---name来找到对应的set方法,进行属性的设置
    <bean id="userDao" class="com.User.User">
        <property name="name" value="大忽悠"/>
        <property name="age" value="18" />
    </bean>

测试:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        User us=(User)app.getBean("user");
        us.show();
    }
}

集合的注入

ref是引用,被引用的内容,必须已经存在于Spring容器中

被注入的类:

代码语言:javascript复制
public class UserService {
    private List<String> list;
    private Map<String,User> map;
    private Properties properties;

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMap(Map<String, User> map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

  void show()
  {
      System.out.println(list);
      System.out.println(map);
      System.out.println(properties);
  }
    
}

map中的user对象

代码语言:javascript复制
public class User
{
    private  String name;
    private  int age;
    public void setName(String name) {
    this.name=name;
    }

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

配置文件:

代码语言:javascript复制
 <bean id="user" class="com.User.UserService">
          <property name="list" >
              <list>
                  <!--普通类型,不是引用类型,用value-->
                  <value>大忽悠</value>
                  <value>和</value>
                  <value>小朋友</value>
              </list>
          </property>

        <property name="map">
            <map>
                <entry key="用户:" value-ref="user1" ></entry>
                <entry key="用户:" value-ref="user2" ></entry>
            </map>
        </property>
        <property name="properties">
            <props>
                <prop key="p1">ppp1</prop>
                <prop key="p3">ppp2</prop>
                <prop key="p2">ppp3</prop>
            </props>
        </property>

    </bean>

    <bean id="user1" class="com.User.User">
      <!--  name-Name-setName-->
        <property  name="name" value="Tom"></property>
        <property  name="age" value="18"></property>
    </bean>
    <bean id="user2" class="com.User.User">
        <!--  name-Name-setName-->
        <property  name="name" value="Booby"></property>
        <property  name="age" value="19"></property>
    </bean>

测试类:

代码语言:javascript复制
public class testDemo 
{
    public static void main(String[] args) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService us=(UserService) app.getBean("user");
       us.show();
    }
}

引入其他配置文件,分模块开发----import标签

Spring重点配置

ApplicationContext的继承体系

ApplicationContext的实现类

getBean()两个重载方法的使用

第二种传入一个字节码对象类型演示:

代码语言:javascript复制
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService bean = app.getBean(UserService.class);
        bean.show();

两种方法的区别

1.通过获取字节码文件对象,获取的返回值,不用进行强制类型转换

2.当出现多个类型相同的bean(对象)时,只能使用第一种方式

Spring配置数据源—连接池

数据库连接池作为单实是最好的,一个项目就一个连接池,连接池里面管理很多连接,连接是直接从连接池拿,因此可以让Spring帮我们创建连接池对象

数据源(连接池)的作用

数据源的开发步骤

数据源的手动创建

手动创建c3p0数据源

代码语言:javascript复制
    /*手动创建c3p0数据源*/
    @Test
    public  void test1() throws PropertyVetoException, SQLException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
       dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
       dataSource.setUser("root");
       dataSource.setPassword("126433");
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

手动创建druid数据源

代码语言:javascript复制
    /*手动创建druid数据源*/
    @Test
    public  void test2() throws PropertyVetoException, SQLException {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/test");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("126433");
        DruidPooledConnection connection = druidDataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

抽取jdbc.properties文件

jdbc.properties配置文件:

代码语言:javascript复制
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test1
jdbc.username=root
jdbc.password=126433

演示:

代码语言:javascript复制
    /*手动创建c3p0数据源(加载配置文件形式)*/
    @Test
    public  void test1() throws PropertyVetoException, SQLException {
//读取配置文件
        //参数: 基名,相对于类加载路径地址---resource文件下面的地址
        //不需要扩展名
        ResourceBundle rb=ResourceBundle.getBundle("jdbc");
        String driver=rb.getString("jdbc.driver");
        String url=rb.getString("jdbc.url");
        String username=rb.getString("jdbc.username");
        String password=rb.getString("jdbc.password");
        //创建数据源对象,设置连接参数
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(password);

        Connection connection=dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

Spring配置数据源

可以将DataSource的创建权交给Spring容器去完成

第一步: 导入坐标

代码语言:javascript复制
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.15.RELEASE</version>
        </dependency>

第二步: 配置文件中完成注入

代码语言:javascript复制
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test1"></property>
    <property name="user" value="root"></property>
    <property name="password" value="126433"></property>
</bean>

第三步: 测试

按照类型获取组件,可以获取到这个类型下所有实现的子类—DataSource类是所有数据连接池对象的基类

代码语言:javascript复制
    ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource bean = app.getBean(DataSource.class);
        Connection connection=bean.getConnection();
        System.out.println(connection);
        connection.close();

Spring加载properties配置文件----将配置文件相关内容放到容器中

第一步: 引入命名空间和约束路径

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
<!--上面一行copy一份,前面加个context前缀,把后面的beans改成context-->
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
       <!--上面一行copy一份,把后面的beans都改成context-->
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

第二步: 加载配置文件

加载外部配置文件的固定写法classpath: 表示引用类路径下的一个资源

代码语言:javascript复制
   <!--加载外部的properties文件-->
    <!--当前要加载的properties文件在资源文件下,前面需要加上classpath:-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

第三步: 完成对数据库连接池对象的相关信息注入

${}动态取出配置文件中某个key对应的值,但是注意username是Spring的key的一个关键字,为了防止配置文件中的key和spring自己的关键字冲突,起名的时候一般会加上一个防止冲突的前缀

代码语言:javascript复制
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--通过键值的方式引入值-->
<property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
</bean>

jdbc.properties配置文件:

代码语言:javascript复制
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test1
jdbc.username=root
jdbc.password=126433

第四步:测试

代码语言:javascript复制
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource bean = app.getBean(DataSource.class);
        Connection connection=bean.getConnection();
        System.out.println(connection);
        connection.close();

Spring注解开发

Spring原始注解

注解的组件扫描

代码演示

环境准备:

需要注入的类:

代码语言:javascript复制
//<bean id="userDao" class="com.User.User"></bean>
@Component("userDao")
public class User
{
    public void show()
    {
        System.out.println("User注入完成");
    }
}

被注入的类:

代码语言:javascript复制
//<bean id="user" class="com.User.UserService">
@Component("user")
public class UserService {
    // <property name="user" ref="userDao"></property>
    @Autowired
    @Qualifier("userDao")
    private User user;

    public void setUser(User user) {
        this.user = user;
    }

    public void show()
    {
      user.show();
    }
}

在进行组件扫描的时候,还是需要引入context命名空间,配置好基础包后,Spring会扫描基础包及其子包

注解后面不标注ID,那么默认ID是类名的首字母小写

组件的作用域默认是单例的,可以通过scope注解进行修改

组件扫描配置:

代码语言:javascript复制
<!--配置组件扫描-->
        <context:component-scan base-package="com"/>

测试:

代码语言:javascript复制
      ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService bean = app.getBean(UserService.class);
        bean.show();

xml方式注入和注解方式注入的一些区别

如果是xml方式配置,那么在进行对象注入的时候,被注入的类,需要提供相关成员变量的set方法

如果是注解配置,我们把注解放到属性,即成员变量上面后,注解会直接通过反射给成员变量赋值,这样就不需要set方法了

如果按照类型注入,只需要写Autowired,前提是对应的数据类型在容器中的对象只有一个 如果按照id注入,那么还需要在上面加上Autowired注解

代码语言:javascript复制
@Component("user")
public class UserService {
    @Autowired//按照数据类型从Spring容器中进行匹配,如果数据类型的对象只有一个
    //下面的qualifier可以省略不写,如果存在多个相同数据类型的对象,则会产生二义性
    @Qualifier("userDao")//按照id的名称,从容器中进行匹配的
    private User user;
    public void show()
    {
      user.show();
    }
}

@Autowired和 @Qualifier的原理

如果资源类型的bean不止一个,默认根据@Autowired注解标记的成员变量名作为id查找bean,进行装配

@Qualifier:指定一个名作为id,让spring别使用变量名作为id

@Autowired标注的自动装配的属性默认是一定装配上的,如果不能装配上,会报错

@Autowired(required=false):如果无法装配上,赋值为Null

@Autowired自动注入只针对引用类型生效,普通属性赋值使用value注解

@Autowired可以标注在方法上,@Qualifier可以标注在方法的形参上,装配原理和上面一致

@Autowired标注的方法,会在单例Bean创建的时候自动调用,即容器创建的时候调用

@Autowired注解详细使用规则参加下面两篇文章

使用@Autowired注解完成属性依赖注入时,写在属性上与写在set方法上的区别 @Autowired注解详解——超详细易懂

@Autowired当标注的属性是接口时,其实注入的是这个接口的实现类,具体细节参考上面这篇文章链接

resource注解

resource相当于Autowired加上Qualifier

代码语言:javascript复制
@Component("user")
public class UserService {
@Resource(name="userDao")
    private User user;
    public void show()
    {
      user.show();
    }
}

@Resource和@Autowired的区别

通过注解对属性完成注入—value注解

简单使用演示:

代码语言:javascript复制
@Component("user")
public class UserService {
    @Value("18")
    private int age;
    @Resource(name="userDao")
    private User user;
    public void show()
    {
        System.out.println("年龄为:" age);
    }
}

@Value注解常用方式—配合配置文件

代码语言:javascript复制
@Component("user")
public class UserService {
    @Value("${jdbc.driver}")
    private String driver;
    @Resource(name="userDao")
    private User user;
    public void show()
    {
        System.out.println(driver);
    }
}
注意: xml中需要引入相关配置文件以及组件扫描
代码语言:javascript复制
<!--配置组件扫描-->
        <context:component-scan base-package="com"/>
        <!--加载外部的properties文件-->
        <!--当前要加载的properties文件在资源文件下,前面需要加上classpath:-->
        <context:property-placeholder location="classpath:jdbc.properties"/>

scope注解----设置对象的作用范围

一个类上面只能添加一个scope注解

代码语言:javascript复制
//一个类上面只能添加一个scope注解
//@Scope("prototype")
@Scope("singleton")
public class testDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService bean = app.getBean(UserService.class);
        bean.show();
    }
}

postConstruct和predestory注解

代码语言:javascript复制
@Component("user")
public class UserService
{
    public UserService() {
        System.out.println("构造方法执行中...");
    }
    @PostConstruct
    void init()
    {
        System.out.println("初始化中...");
    }
    @PreDestroy
    void destory()
    {
        System.out.println("销毁中....");
    }
    public void show()
    {
        System.out.println("show方法调用");
    }
}

容器没有关闭,所以对象没有被释放,也就不会去调用销毁方法,因此只有在调用了close方法,容器被关闭后,对象才会被释放,才会去调用销毁方法

测试类:

代码语言:javascript复制
public class testDemo {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService bean = app.getBean(UserService.class);
        bean.show();
        app.close();
    }
}

Spring新注解

核心配置类

代码语言:javascript复制
//标志该类是Spring的核心配置类
@Configuration
//<context:component-scan base-package="com"/>
//相当于配置组件扫描
@ComponentScan("com")
//<import resource=""/> 通过import将数据域相关的配置加载到核心(总)配置中
@Import(DataSourceCofiguration.class)
//如果要加载多个配置
//@Import({DataSourceCofiguration.class,...})
public class SpringCofiguration {}

数据源配置类

代码语言:javascript复制
//<context:property-placeholder location="classpath:jdbc.properties"/>
//加载配置文件进Spring容器
@PropertySource("classpath:jdbc.properties")
public class DataSourceCofiguration {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String pasword;
    @Bean("dataSource") //Spring会将当前方法的返回值以指定名称存储到Spring容器中
    public DataSource getDataSource() throws PropertyVetoException, SQLException {
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setDriverClass(driver);
        dataSource.setJdbcUrl(url);
        dataSource.setUser(username);
        dataSource.setPassword(pasword);
        Connection connection = dataSource.getConnection();
        System.out.println("执行getDataSource方法,返回connection:" connection);
        connection.close();
        return dataSource;
    }
}

测试类

代码语言:javascript复制
public class test {
    public static void main(String[] args) throws SQLException {
       ApplicationContext app= new AnnotationConfigApplicationContext(SpringCofiguration.class);
app.getBean(UserService.class).show();
    }
}

Spring集成Junit

原始Junit测试Spring的问题

解决思路

Spring集成Junit的步骤

spring单元测试原理(ContextConfiguration和runwith注解)

代码实现

第一步: 在pom.xml中导入Spring-test的坐标

代码语言:javascript复制
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>

第二步: 创建测试类

代码语言:javascript复制
@RunWith(SpringJUnit4ClassRunner.class)//让测试运行于Spring测试环境
//通过读取配置文件,在测试类中完成对Bean的注入
//@ContextConfiguration("classpath:applicationContext.xml")
//通过加载核心配置类,完成注入操作
@ContextConfiguration(classes = SpringCofiguration.class)//如果要加载多个,使用数组形式
public class JunitTest
{
    @Autowired
    private UserService us;
    @Autowired
    private DataSource ds;
    @Test
    public void test() throws SQLException {
        us.show();
        System.out.println(ds.getConnection());
    }

}

泛型依赖注入原理图

IOC部分总结

Spring集成web环境

导入servlet和jsp的坐标

代码语言:javascript复制
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.1</version>
      <scope>provided</scope>
    </dependency>
    
        <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>

第二步: 完善web目录结构

第三步: xml文件配置(也可以使用注解配置)

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

       <servlet>
         <servlet-name>UserServlet</servlet-name>
         <servlet-class>Web.UserServlet</servlet-class>
       </servlet>
  <servlet-mapping>
    <servlet-name>UserServlet</servlet-name>
    <url-pattern>/userServlet</url-pattern>
  </servlet-mapping>
  
</web-app>

第四步: 通过xml配置,在容器中创建单例的userservice对象

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="userService" class="com.User.UserService"></bean>
</beans>

userServlet类进行测试:

代码语言:javascript复制
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService bean = app.getBean(UserService.class);
        bean.show();
    }

}

tomcat服务器别忘记部署

监听器的妙用—加载配置文件

要解决的问题:

解决方法:

代码:

监听器类:

代码语言:javascript复制
public class LS implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
        //将Spring的应用上下文对象存储到ServletContext域中
        ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("app",app);
        System.out.println("Spring容器创建完毕");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

web.xml中配置监听器

代码语言:javascript复制
      <!--配置监听器-->
    <listener>
        <listener-class>Listener.LS</listener-class>
    </listener>

userServlet类中通过从ServletContext域中拿到存放数据,来取出上下文对象,从而从Spring容器中拿出单例对象US

代码语言:javascript复制
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        ApplicationContext app = (ApplicationContext)servletContext.getAttribute("app");
        UserService bean = app.getBean(UserService.class);
        bean.show();
    }
}

针对创建app对象时,xml配置文件路径写死的优化

代码语言:javascript复制
  ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");

通过监听器的全局参数来进行优化

web.xml配置文件:

代码语言:javascript复制
   <!--全局初始化参数-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>applicationContext.xml</param-value>
    </context-param>

监听器类:

代码语言:javascript复制
  @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletContext servletContext = servletContextEvent.getServletContext();
        //读取web.xml中的全局参数
        String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
        ApplicationContext app=new ClassPathXmlApplicationContext(contextConfigLocation);
        //将Spring的应用上下文对象存储到ServletContext域中
        servletContext.setAttribute("app",app);
        System.out.println("Spring容器创建完毕");
    }

针对在获取上下文对象时,属性名写死的优化

WebAppUtils工具类:

代码语言:javascript复制
public class WebAppUtils {
    static public ApplicationContext getApp(ServletContext sc)
    {
        return (ApplicationContext)sc.getAttribute("app");
    }
}

UserServlet类:

代码语言:javascript复制
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = this.getServletContext();
        ApplicationContext app = WebAppUtils.getApp(servletContext);
        UserService bean = app.getBean(UserService.class);
        bean.show();
    }
}

Spring提供获取应用上下文的工具—上面是铺垫

我们需要做的事情

Spring-web坐标:

代码语言:javascript复制
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.9.RELEASE</version>
    </dependency>

web.xml配置:

代码语言:javascript复制
    <!--全局初始化参数-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    
    <!--配置监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

UserServlet类:

代码语言:javascript复制
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext = getServletContext();
        ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        UserService bean = app.getBean(UserService.class);
        bean.show();
    }
}

AOP

什么是AOP

AOP底层实现

AOP动态代理技术

JDK动态代理实例演示

TargetInterface接口:

代码语言:javascript复制
public interface TargetInterface {
    void show();
}

target类:

代码语言:javascript复制
public class Target implements TargetInterface {

    @Override
    public void show() {
        System.out.println("show方法调用");
    }
}

advice增强类:

代码语言:javascript复制
public class Advice {
    void berfore()
    {
        System.out.println("前置增强");
    }
    void after()
    {
        System.out.println("后置增强");
    }
}

main测试类:

代码语言:javascript复制
public class main {
    public static void main(String[] args) {
        //目标对象
         Target target=new Target();
         //增强对象
        Advice advice=new Advice();
        Object p = Proxy.newProxyInstance(
                //目标对象类加载器
                target.getClass().getClassLoader(),
                //目标对象相同的接口字节码对象数组
                target.getClass().getInterfaces(),
                //目标方法执行器,帮我们目标对象执行目标方法
                new InvocationHandler() {
                    //调用代理对象的任何方法,实际都是执行invoke方法
                    @Override
                    //proxy: 给jdk使用的,任何时候都不要动这个对象
                    //method:当前要执行的目标对象的方法
                    //args:这个方法调用时外界传入的参数
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        advice.berfore();//前置增强
                        //利用反射执行目标方法
                        //目标方法执行后的返回值
                        Object invoke = method.invoke(target, args);
                        advice.after();//后置增强
                        //返回值必须真正返回出去,外界才能拿到执行后的返回值
                        return invoke;
                    }
                }
        );
        //调用代理对象的方法
        ((TargetInterface)p).show();

    }
}

cglib动态代理实例演示

Spring五版本,已经将cglib相关jar包放入core的核心包里面了

target类:

代码语言:javascript复制
public class Target {
    public void show() {
        System.out.println("show方法调用");
    }
}

advice增强类:

代码语言:javascript复制
public class Advice {
    void berfore()
    {
        System.out.println("前置增强");
    }
    void after()
    {
        System.out.println("后置增强");
    }
}

main测试主类:

代码语言:javascript复制
public class main {
    public static void main(String[] args) {
        //目标对象
        Target target=new Target();

        //增强对象
        Advice advice=new Advice();

        //返回值就是动态代理生成的对象,基于cglib
          //1,创建增强器
        Enhancer enhancer=new Enhancer();
        //2.设置父类(目标对象)
        enhancer.setSuperclass(Target.class);
        //3.设置回调
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
               //前置增强
                advice.berfore();
                //执行目标
                Object invoke = method.invoke(target, args);
                //后置增强
                advice.after();
                return invoke;
            }
        });
        //4.创建代理对象
        Target proxy = (Target) enhancer.create();
        proxy.show();
    }
}

AOP相关概念

连接点可以理解为可以被增强的方法

切入点(切点): 对哪些连接点进行了配置,完成了对方法的增强,可以理解为被增强了的方法

advice: 简单理解为对要增强的方法中的增强逻辑的封装,封装为一个对象,这个对象就是增强对象,增强对象的方法就是增强逻辑

切面: 目标方法加逻辑增强

织入: 切点和增强结合的过程

AOP开发明确事项

知识要点

基于XML的AOP开发

快速入门的步骤

1.导入aspectj的坐标

代码语言:javascript复制
    <!--导入aspectj的坐标-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.4</version>
    </dependency>

2.目标接口和目标类

接口不用加载到容器中,即使加载到了容器中,也不会创建对象,相当于告诉了Spring容器,ioc容器中可能有这种类型的组件

代码语言:javascript复制
public interface TargetInterface {
    void show();
}

public class Target implements TargetInterface{
    public void show() {
        System.out.println("Target's show");
    }
}

3.切面类

代码语言:javascript复制
public class MyAspect {
    public void before()
    {
        System.out.println("前置增强");
    }
}

4.目标类和切面类交给Spring容器去创建

代码语言:javascript复制
    <!--配置目标对象-->
    <bean id="target" class="com.dhy.aop.Target"></bean>
    <!--配置切面-->
    <bean id="myAspect" class="com.dhy.aop.MyAspect"></bean>

5.在app.xml中配置织入关系—引入aop命名空间

代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置目标对象-->
    <bean id="target" class="com.dhy.aop.Target"></bean>
    <!--配置切面-->
    <bean id="myAspect" class="com.dhy.aop.MyAspect"></bean>
    <!--配置织入:在配置文件中告诉Spring框架,那些方法(切点)需要进行哪些增强(前置,后置...)-->
    <!--要引入AOP命名空间-->

    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面=切点 通知-->
            <aop:before method="before" pointcut="execution(public void com.dhy.aop.Target.show())"/>
        </aop:aspect>
    </aop:config>

</beans>

6.测试代码

先导入spring-test的坐标:

代码语言:javascript复制
    <!--引入Spring测试坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>

如果目标类有实现接口,那么AOP底层原理即使jdk动态代理,容器中保存的组件是其代理对象,而非本类类型,因此再使用getBean获取实例的时候,如果通过字节码文件类型获取,参数应该填入接口类型,返回值也用接口类型来接

如果目标类没有实现接口,那么AOP底层实现原理就是cglib,我们可以用目标类去接,相当于多态形式

代码语言:javascript复制
@RunWith(SpringJUnit4ClassRunner.class)
//在测试类的内部,完成Bean的注入
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {
    @Autowired
    private TargetInterface target;

    @Test
    public void test()
    {
target.show();
    }
}

切点表达式的写法

切点表达式支持的通配符

1.代表匹配一个或者多个字符: Math*r—>Math开头,r结尾的字符串都满足格式要求

2.匹配任意一个参数

3.只能匹配一层路径

代码语言:javascript复制
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面=切点 通知-->
            <!--cmo.dhy.aop报下的任意类的任意方法的任意参数,返回值任意的方法都会被增强-->
            <aop:before method="before" pointcut="execution(* com.dhy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

通知的类型

通知的配置语法

before,after,aroud演示

切面类:

代码语言:javascript复制
public class MyAspect {
    public void before()
    {
        System.out.println("前置增强");
    }
    public void after()
    {
        System.out.println("后置增强");
    }
    //Proceeding JoinPoint: 正在执行的连接点--->切点
      public Object around(ProceedingJoinPoint pjp) throws Throwable {
          System.out.println("环绕前");
          //就是利用反射调用的目标方法,等同于method.invoke(obj,args)
          Object proceed = pjp.proceed();//切点的方法
          System.out.println("环绕后");
          //反射调用的返回值也要返回回去,这个返回值就是方法的返回值
          return proceed;
      }
}

切面的配置:

代码语言:javascript复制
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面=切点 通知-->
            <aop:before method="before" pointcut="execution(* com.dhy.aop.*.*(..))"/>
            <aop:after method="after" pointcut="execution(* com.dhy.aop.*.*(..))"/>
            <aop:around method="around" pointcut="execution(* com.dhy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

结果:

after-throwing

目标类抛出异常:

代码语言:javascript复制
public class Target implements TargetInterface{
    public void show() {
        System.out.println("Target's show");
        int i=1/0;//制造数学异常
    }
}

切面配置:

代码语言:javascript复制
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面=切点 通知-->
            <aop:around method="around" pointcut="execution(* com.dhy.aop.*.*(..))"/>
            <aop:after-throwing method="throwing" pointcut="execution(* com.dhy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

增强方法:

代码语言:javascript复制
      public void throwing()
      {
          System.out.println("抛出异常");
      }

after—最终增强,不管是否抛出异常,都会执行

增强方法:

代码语言:javascript复制
    public void after()
    {
        System.out.println("最终增强");
    }

切面配置:

代码语言:javascript复制
   <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--切面=切点 通知-->
            <aop:around method="around" pointcut="execution(* com.dhy.aop.*.*(..))"/>
            <aop:after-throwing method="throwing" pointcut="execution(* com.dhy.aop.*.*(..))"/>
            <aop:after method="after" pointcut="execution(* com.dhy.aop.*.*(..))"/>
        </aop:aspect>
    </aop:config>

切点表达式的抽取

xml配置中也可以在指定前置…方法的时候,通过设置returning和throwing参数,来获取相应的返回值和异常信息

抽取后得到的pointcut标签,如果写在了当前的切面类里面,那么也只能够在当前切面类里面引用,如果想扩大作用域范围,可以将标签写在config表签下面,相当于一个全局变量

可以给aspect标签里面加上order属性,来指定切面的执行顺序

切面配置:

代码语言:javascript复制
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--抽取切点表达式-->
            <aop:pointcut id="myPointCut" expression="execution(* com.dhy.aop.*.*(..))"/>
            <!--切面=切点 通知-->
            <aop:around method="around" pointcut-ref="myPointCut"/>
            <aop:after-throwing method="throwing" pointcut-ref="myPointCut"/>
            <aop:after method="after" pointcut-ref="myPointCut"/>
        </aop:aspect>
    </aop:config>

知识要点

基于注解的AOP开发

快速入门的步骤

别忘记开启基于注解的aop功能—>aop自动代理

配置文件:

代码语言:javascript复制
    <!--组件扫描-->
    <!--使用时,加上context的命名空间-->
    <context:component-scan base-package="com.dhy.anno"/>
<!--aop的自动代理-->
<!--使用时,加上aop的命名空间-->
<aop:aspectj-autoproxy/>

切面类:

代码语言:javascript复制
@Component("MyAspect")
@Aspect //标注当前类是一个切面类
public class MyAspect {

    //Proceeding JoinPoint: 正在执行的连接点--->切点
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object proceed = pjp.proceed();//切点的方法
        System.out.println("环绕后");
        return proceed;
    }

    public void throwing() {
        System.out.println("抛出异常");
    }
    //配置最终增强
    @After("execution(* com.dhy.anno.*.*(..))")//参数是切点表达式
    public void after()
    {
        System.out.println("最终增强");
    }
}

目标类:

代码语言:javascript复制
@Component("Target")
public class Target implements TargetInterface {
    public void show() {
        System.out.println("Target's show");
    }
}

注解通知类型

通知方法的执行顺序

在通知方法运行时,拿到目标方法的详细信息----JoinPoint

我们只需要为通知方法的参数列表上写上一个参数: JoinPoint joinpoint (封装了目标方法的详细信息)

详细看本篇连接

throwing和returning来指定哪个参数用来接收异常和返回值

returning=“result” :告诉spring,使用result变量来接收返回值

throwing=“exception”: 告诉spring,exception变量用来接收异常

spring通知方法的参数列表一定不能乱写

如上面的returning和throwing方式,告诉spring我们填入的参数是什么

上面returning和throwing用来接收异常和返回值信息的指定参数的数据类型,最好往大了写,不然可能无法接收数据

切点表达式的抽取----随便声明一个返回值为void的空方法

切面类:

代码语言:javascript复制
@Component("MyAspect")
@Aspect //标注当前类是一个切面类
public class MyAspect {
    @Around("point()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕前");
        Object proceed = pjp.proceed();
        System.out.println("环绕后");
        return proceed;
    }
    @After("MyAspect.point()")
    public void after()
    {
        System.out.println("最终增强");
    }
    //定义切点表达式
    @Pointcut("execution(* com.dhy.anno.*.*(..))")
    public  void point(){};
}

可以使用环绕通知完成四合一功能: 前置,返回,异常,后置

如果在使用环绕通知时,代码里面对异常进行了抓取,为了能让外界知道这个异常,这个异常一定要抛出去

环绕通知优先于普通通知执行

如果想要通过通知来实现动态代理的功能,那么就可以选择环绕通知,利用里面的ProceedingJoinPoint 类型的对象,完成对方法执行的干预

多切面运行顺序

注解方式是默认按照类名的字符串大小比较来决定执行顺序,如果时xml配置,则是由配置的先后顺序决定 可以使用@order(int i)注解,来改变切面的运行顺序,数值越小,优先级越高

环绕只影响当前切面,他的优先执行顺序只体现在它所在的当前切面,与其他切面无关

AOP的使用场景

Spring的JDBCTemplte

JDBCTemplte开发步骤

1.导入spring-jdbc和spring-tx以及其他的一些坐标

代码语言:javascript复制
<!--mysql驱动的坐标-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.32</version>
    </dependency>
    <!--c3p0数据库连接池的坐标-->
    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>
    <!--druid数据库连接池坐标-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.10</version>
    </dependency>
    <!--spring jdbc的坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>
    <!--spring tx的坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.0.5.RELEASE</version>
    </dependency>

创建数据源,设置数据源,执行操作

代码语言:javascript复制
public class JdbcTemplteTest {
    @Test
    public void test1() throws PropertyVetoException {
        //创建数据源对象
        ComboPooledDataSource dataSource=new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test1");
        dataSource.setUser("root");
        dataSource.setPassword("126433");

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据源对象  知道数据库在哪
        jdbcTemplate.setDataSource(dataSource);
        //执行操作
          int row=jdbcTemplate.update("insert into account values (?,?)","大忽悠",8000);
        System.out.println("影响的行数:" row);
    }
}

通过Spring让我们产生模板对象

通过appOfDao.xml完成配置

将数据库连接池对象注入到jdbctemplate模板对象中

代码语言:javascript复制
   <!--数据源对象-->
     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
         <property name="driverClass" value="com.mysql.jdbc.Driver"/>
         <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test1"/>
         <property name="user" value="root"/>
         <property name="password" value="126433"/>
     </bean>
   <!--jdbc模板对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
    </bean>

测试类:

代码语言:javascript复制
public class JdbcTemplteTest {
    @Test
    public void test1() throws PropertyVetoException {
        ApplicationContext app = new ClassPathXmlApplicationContext("appOfDao.xml");
        JdbcTemplate jt=app.getBean(JdbcTemplate.class);
        int row = jt.update("insert account values (?,?)", "小朋友", 12000);
        System.out.println("影响的行数" row);
    }
}

抽取数据库连接池配置时填入的参数,放到properties配置文件中

properties配置文件:

代码语言:javascript复制
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test1
jdbc.username=root
jdbc.password=126433

在Spring容器中引入pro配置文件,然后修改刚才传入的参数—配置数据库的模板

代码语言:javascript复制
    <!--加载jdbc.properties-->
<!--引入context命名空间-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--数据源对象-->
     <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
         <property name="driverClass" value="${jdbc.driver}"/>
         <property name="jdbcUrl" value="${jdbc.url}"/>
         <property name="user" value="${jdbc.username}"/>
         <property name="password" value="${jdbc.password}"/>
     </bean>
   <!--jdbc模板对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
    </bean>

0 人点赞