超级详细Spring学习笔记 两万字警告!

本文深入介绍了Spring框架的核心概念,包括IOC(控制反转)和AOP(面向切面编程)。Spring作为一个轻量级的JavaEE框架,简化了企业级应用的开发。IOC通过依赖注入管理组件,而AOP则实现了在不修改源代码的情况下进行功能增强。此外,文章详细阐述了Spring的bean配置、生命周期、自动装配、SpEL表达式、单元测试以及动态代理等关键特性,帮助读者全面理解Spring的工作原理和使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring学习笔记

开始学习的时间:2021年6月10日

插入一条:对于框架和容器的理解。容器就相当于是土壤,框架就相当于是架子,植物没有土壤生长不了,没有架子长不好。但是本质都是java程序。
框架 = 反射 + 注解 + 设计模式

框架:高度抽取可重用代码的一种设计,高度的通用性,多个可重用模块的集合,形成一个某个领域的整体解决方案。
框架应当被视为半成品软件。

一、什么是Spring,Spring概述。

Spring是一个IOC(DI)和AOP容器(可以管理所有的组件(类))框架。

  1. Spring是轻量级的开源的JavaEE框架。(轻量级:体积很小,引用的jar包比较少,可以独立使用)

  2. Spring为简化企业级应用开发而生。使用Spring可以使简单的JavaBean实现以前只有EJB才能实现的功能。

    Spring的具体描述:
    轻量级:Spring是非入侵性的-基于Spring开发的应用中的对象可以不依赖于Spring的API。
    依赖注入(DI – dependency injection、IOC)。
    面向切面编程(AOP – aspect oriented programming)。
    容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。
    框架:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象。
    一站式:在IOC和AOP的基础上可以整合各种企业的开源框架和优秀的第三方类库(实际上Spring自身也提供了展现层的SpringMVC和持久层的Spring JDBC)。

  3. Spring有两个核心部分,IOC和AOP
    IOC(Inversion Of Control):控制反转,把创建对象的过程交给Spring进行管理。可以用来整合其他的框架。
    控制:资源的获取方式;

    主动式:(要什么资源自己去创建即可)
    BookServlet {
    	BookService bs = new Books=Service();
    	Airplane ap = new Airplane; //复杂对象的创建是比较庞大的工程。
    }
    被动式:(资源的获取不是自己创建,而是交给容器来创建和设置)
    BookServlet {
    	BookService bs;
    	public void test01() {
    		bs.checkout(); 
    	}
    }
    

    容器:管理所有的组件(有功能的类);假设,BookServlet受容器管理,BookService也受容器管理,容器可以自动地探查出哪些组件(类)需要用到另一些组件(类);容器帮我们创建BookService对象,并把BookService对象赋值过去。
    容器:主动的new资源变为被动的接受资源。

    IOC是一种思想,DI(Dependency Injection 依赖注入)就是这种思想的实现。
    容器能够知道哪个组件(类)运行的时候,需要另外一个类(组件);容器通过反射的形式,将容器准备好的BookService对象注入(利用反射给属性赋值)到BookServlet中;
    只要容器管理的组件,都能使用容器提供的强大功能;

    HelloWorld:(通过各种方式给容器中注册对象(注册会员))
    以前是自己new对象,现在所有的对象都交给容器创建;给容器注册组件。

  4. Spring特点:
    (1)方便解耦,简化开发
    (2)对Aop编程的支持
    (3)方便程序的测试(整合了Junit)
    (4)可以方便和其他优秀框架进行整合(如:Struts,Hibernate,Hessian,Quartz)
    (5)非侵入式:降低java API的使用难度(对很多做了封装)
    (6)方便进行事务操作
    (7)java源码的学习典范

    Github下载网址:https://repo.spring.io/webapp/#/home
    https://repo.spring.io/release/org/springframework/spring/

  5. Spring压缩包中内容的介绍:
    (1)docs:里面可以写一些文档之类的
    (2)libs:里面存的是一些jar包
    (3)schema:一些相关的配置文件
    Spring Framework

    Test:Spring的单元测试模块;spring-test
    Core Container:核心容器(IOC);黑色代表这部分的功能由哪些jar包组成。要使用这部分的完整功能,这些jar包都需要导入。
    spring-beans spring-core spring-context spring-expression

    AOP + Aspects面向切面编程模块。
    spring-aop

    数据访问/集成模块:Spring访问数据库模块
    spring-jdbc spring-orm(Object Relation Mapping 对象关系映射) spring-tx
    以上三者是与数据库访问有关的
    spring-ox(xml) spring-jms
    这两者是与集成(Integration)有关的
    ORM:我们访问数据库的目的就是把数据库的某一条记录的数据查出来,封装成一个javaBean对象,这个操作就叫做对象关系映射。

    Web:Spring开发Web应用模块;
    spring-websocket(新技术) spring-web(和原生web相关servlet) spring-webmvc(开发web项目的) spring-webmvc-portlet(开发web应用的组件集成)

    一般是用哪个模块,导哪个包。

    开发spring框架的应用,经常要写框架的配置文件,写起来复杂,我们需要提示。

  6. 导入Spring5相关jar包
    core,context,beans,expression,logging

    框架编写流程:
    1)导包;
    导入四个核心包,还有一个日志包。
    2)写配置;
    spring的配置文件中,集合了spring的IOC容器管理的所有组件(会员清单);
    创建一个Spring Bean Configuration File(Spring的bean配置文件);
    3)测试;

    spring-config.xml配置文件细则:

    <?xml version="1.0" encoding="UTF-8"?>
    <!--suppress ALL -->
    <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">
    
        <!--注册一个Person对象,Spring会自动创建这个Person对象-->
        <!--
        一个bean标签可以注册一个组件(对象、类)
        class:写要注册的组件的全类名;
        id:这个对象的唯一标识;
        -->
    
        <bean id="person01" class="Person">
            <!--
            使用property标签为 Person对象的属性赋值
            name:指定属性名
            value:指定属性值
            -->
            <property name="age" value="18"></property>
            <property name="email" value="kkkoke@qq.com"></property>
            <property name="gender" value="man"></property>
            <property name="lastName" value="kkkoke"></property>
        </bean>
    </beans>
    

常见的问题:

  1. src,源码包开始的路径,称为类路径的开始。src与conf(source文件夹)里的文件 最后都会合并到bin目录中。所有源码包里面的东西都会合并放在类路径下面。
    web项目的类路径在WEB-INF下的classes
  2. 导包的时候不能够少了日志包。
  3. 先导包再创建配置文件。
  4. Spring的容器接管了就会有特殊的标志。
    几个细节:
    1.new ClassPathXmlApplicationContext(“”); ioc容器的配置文件放在类路径下的时候,用 这个类。
    new FileSystemXmlApplicationContext(“”);ioc容器的配置文件放在磁盘路径下的时候, 用这个类。
    2.给容器中注册一个组件,我们也从容器中按照id拿到了这个组件的对象?
    组件的创建工作,是容器完成的。
    Person对象是什么时候创建的呢?
    容器中对象的创建在容器创建完成的时候就已经创建好了。
    容器要启动,里面的对象必须要先创建好。
    3.同一个组件在ioc容器中是单实例的。容器启动完成前都已经创建准备好的。
    4.容器中如果没有这个组件,获取这个组件就会报异常。
    5.ioc容器在创建这个组件对象的时候,会调用setter方法为JavaBean的属性进行赋值。
    6.javaBean的属性名是由什么决定的?
    getter和setter方法是属性名;set去掉后面那一串字母小写就是属性名。
    所有的getter和setter方法都采用自动生成的,不要随便改名字。

IOC实验:
实验1:通过IOC容器创建对象,并为属性赋值,通过id来获取bean的实例。

@Test
public void test() {
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml");
    Person bean = (Person)ioc.getBean("person1");
    System.out.println(bean);
}

实验2:根据bean的类型从ioc容器中获取bean的实例。
如果ioc容器中这个类型的bean有多个,查找就会报错。

@Test
public void test02() {
    Person bean = ioc.getBean(Person.class);
    System.out.println(bean);
}
@Test
public void test02() {
Person bean = ioc.getBean(“person01”, Person.class);
}

实验3:通过构造器为bean的属性赋值(index, type属性介绍)

<bean id="person02" class="com.kkkoke.Person">
    <!--调用有参构造器进行创建对象,并进行赋值-->
    <constructor-arg name="name" value="haojiahuo"></constructor-arg>
    <constructor-arg name="age" value="19"></constructor-arg>
    <constructor-arg name="email" value="jisefjesfsgesf"></constructor-arg>
</bean>

<!--省略了name属性,但是之后的内容要按顺序写-->
<bean id="person02" class="com.kkkoke.Person">
    <!--调用有参构造器进行创建对象,并进行赋值-->
    <constructor-arg value="haojiahuo"></constructor-arg>
    <constructor-arg value="19"></constructor-arg>
    <constructor-arg value="jisefjesfsgesf"></constructor-arg>
</bean>


<!--省略了name属性,之后也可用通过index属性来指定顺序,从零开始-->
<bean id="person02" class="com.kkkoke.Person">
    <!--调用有参构造器进行创建对象,并进行赋值-->
    <constructor-arg value="haojiahuo" index=0></constructor-arg>
    <constructor-arg value="jisefjesfsgesf" index=2></constructor-arg>
    <constructor-arg value="19" index=1></constructor-arg>
</bean>

实验4:通过p名称空间为bean赋值。
名称空间是用来防止标签重复的。
比如:

<book>
<b:name>西游记</b:name>
<price>20.20</price>
<author>
<a:name>吴承恩</a:name>
<gender></gender>
</author>
</book>

实验4:正确的为各种属性赋值
对象的里面套对象:

<bean id=”person01” class=”com.kkkoke.Person”>
<property name=”car”>
<!--对象我们可以使用bean标签创建 car = new Car(); 引用内部bean-->
<bean class=”com.kkkoke.Car”>
<property name=”carName” value=”自行车”></property>
</bean>
</property>
</bean>

对象里面有一个List集合:

<book id=”book01” class=”com.kkkoke.Book”>
<property name=”bookName” value=”西游记”></property>
</book>

<beans id=”person02” class=”com.kkkoke.Person”>
<!--如何为list类型赋值-->
<property name=”books”>
<!--books = new ArrayList<Book>();-->
<list>
<!--list标签体重添加每一个元素-->
<bean class=”com.kkkoke.Book” bookName=”三体”></bean>
<!--引用外部一个元素-->
<ref bean=”book01”/>
</list>
</property>

<property name=”maps”>
<!--maps = new LinkedHashMap<>();-->
<map>
<!--一个entry代表一个键值对-->
<entry key=”key01” value=”张三”></entry>
<entry key=”key02” value=”18”></entry>
<entry key=”key03” value-ref=”book01”></entry>
<entry key=”key04”>
<bean class=”com.kkkoke.Car”>
<property name=”carName” value=”迈凯伦”></property>
</bean>
</entry>
<entry key=”key05”>
<map></map>
</entry>
</map>
</property>

<!--private Properties properties;-->
<property name=”properties”>
<!--properties = new Properties(); 所有的k=v都是String-->
<props>
<prop key=”username”>root</prop>
<prop key=”password”>123456</prop>
</props>
</property>

<!--util名称空间创建集合类型的bean:方便别人引用-->
<bean class=”com.kkkoke.Person”>
<property name=”maps” ref=”myMap”></property>
</bean>
<!--相当于new LinkedHashMap()-->
<util:map id=”myMap”>
<entry key=”key01” value=”张三”></entry>
<entry key=”key02” value=”18”></entry>
<entry key=”key03” value-ref=”book01”></entry>
</util:map>

<!--级联属性: 级联属性就是属性的属性-->
<bean id=”Person04” class=”com.kkkoke.Person”>
<!--为car赋值的时候改变car的价格-->
<property name=”car” ref=”car01”></property>
<property name=”car.price” value=”900000”></property>
</bean>
<!--级联属性可以修改属性的属性,注意:原来bean的值可能会被修改-->
</beans>

ps:内部bean是不能通过设置id属性来获取的,只能能够在内部使用。

实验6:通过继承实现bean配置信息的重用

<bean id=”person05” class=”com.kkkoke.Person”>
<property name=”lastName” value=”张三”></property>
<property name=”age” value=”18”></property>
<property name=”gender” value=”男”></property>
<property name=”email” value=”zhangsan@qq.com”></property>
</bean>

<!--这里的继承只是指的是的配置信息的继承,而不是代表一种子父类的关系-->
<bean id=”person06” class=”com.kkkoke.Person” parent=”person05”>
<property name=”lastName” value=”李四”></property>
</bean>

实验7:通过abstract属性创建一个模板bean

<!--abstract=”true”: 这个bean的配置是一个抽象的,不能获取它的实例,只能被别人用来继承-->
<bean id=”person05” class=”com.kkkoke.Person” abstract=”true”>
<property name=”lastName” value=”张三”></property>
<property name=”age” value=”18”></property>
<property name=”gender” value=”男”></property>
<property name=”email” value=”zhangsan@qq.com”></property>
</bean>

实验8:bean之间的依赖(依赖只是改变创建顺序的)

<!--bean的创建时按照顺序来的-->
<!--改变bean的创建顺序-->
<bean id=”car” class=”com.kkkoke.Car” depends-on=”person, book”></bean>
<bean id=”person” class=”com.kkkoke.Person”></bean>
<bean id=”book” class=”com.kkkoke.Book”></bean>

实验9:测试bean的作用域,分别创建单实例和多实例的bean
bean的作用域:指定bean是否单实例。默认情况下是单实例的。
prototype:多实例的
1)容器默认不会去创建多实例的bean。
2)只有获取的时候才会创建这个bean值。
3)每次获取都会创建一个新的对象。
single:单实例的
1)单实例的bean在容器启动完之前就已经创建好对象,保存在容器中了。
2)如果是单实例的bean不论在什么时候获取都是一样的。
request:在web环境下,同一次请求创建一个bean实例(没用)
session:在web环境下,同一次会话创建一个bean实例(没用)

实验5:配置通过静态工厂方法创建的bean、实例工厂方法创建的bean、FactoryBean

分为两种:
静态工厂:工厂本身不用创建对象:通过静态方法调用。对象 = 工厂类.工厂方法名();
实例工厂:工厂本身需要创建对象:工厂类 工厂对象 = new 工厂类();
工厂对象.get对象();

静态工厂:

public class Person {
	public static Person getPerson(String name) {
		Person person = new Person();
		person.setName(“张三”);
		person.setAge(18);
		person.setWeight(60);
	}
}

要使用的时候,直接Person.getPerson();

实例工厂:

public class Person {
	public Person getPerson(String name) {
		Person person = new Person();
		person.setName(“张三”);
		person.setAge(18);
		person.setWeight(60);
	}
}

要使用的时候,要先new Person,再调用。

实例工厂使用:

<factory-method: 指定这个实例工厂中哪个方法是工厂方法>
<bean id=”personFactory” class=”com.kkkoke.Person”></bean>

<!--factory-bean:指定当前对象创建使用哪个工厂
1.先配置出实例工厂对象。
2.配置出要创建的Person使用哪个工厂实例
1)factory-bean:指定使用哪个工厂实例
2)factory-method:使用哪个工厂方法
-->
<bean id=”person01” class=”com.kkkoke.Person”
factory-bean=”personFactory” factory-method=”getPerson”>
<constructor-arg value=”王五”></constructor-arg>
</bean>

<!--FactoryBean是Spring规定的一个接口,只要是这个接口的实现类,Spring都认为是一个工厂类,Spring会自动的调用工厂方法创建实例-->
1.编写一个FactoryBean的实现类:
2.在Spring配置文件进行注册。

public class myBook implements FactoryBean<Book> {
	//getObject:工厂方法:
	//返回创建的对象
	@Override
	public Book getObject() throws Exception {
		Book book = new Book();
		book.setName(“三体”);
		book.setPrice(18);
		
		return book;
	}

	//返回创建的对象的类型;
	//Spring会自动调用这个方法来确认我们创建的对象是什么类型
	@Override
	public Class<Book> getObject() {
		return Book.class;
	}
	
	//isSingleton:是单例?
	//true:是单例;
	//false:不是单例;
	@Override
	public boolean isSingleton() {
		return false;
	}
}

<!--注册-->
<bean id=”myBook01” class=”com.kkkoke.myBook”></bean>

IOC容器创建的时候,不会调用工厂方法创建对象,都是在获取的时候才创建对象。

实验10:创建带有生命周期的方法的bean
生命周期:bean的创建和销毁
IOC容器中注册的bean:
1)单例bean,容器启动的时候就会创建好,容器关闭也会销毁创建的bean
2)多实例bean,在获取的时候才创建。
我们可以为bean自定义一些生命周期方法:Spring在创建或者销毁的时候就会调用指 定的方法。

单实例:bean的生命周期:
(容器启动)构造器----->初始化方法----->(容器关闭)销毁方法
多实例:bean的生命周期:
获取bean的时候(调动构造器)----->初始化方法----->容器关闭不会调用bean销毁放方法

实验11:测试bean的后置处理器
Spring中有一个接口成为后置处理器,作用是可以在bean的初始化前后调用方法。
无论bean是否有初始化方法,后置处理器都会默认其有初始化方法,还会继续工作。

实验12:引用外部属性文件
数据库连接池作为单实例是最好的:一个项目就一个连接池,连接池里面管理很多连接。连接是直接从连接池里面拿。
利用这个特性,可以让Spring帮我们创建连接池对象,(也就是管理连接池)

<beans id=”dataSource” class=”com.mchang.Combopool”>
<property name=”user” value=”root”></property>
<property name=”password” value=”123456”></property>
<property name=”jdbcUrl” value=”jdbc:mysql://localhost:3306”></property>
<property name=”driverClass” value=”com.myspl.jdbc.Driver”></property>
</bean>

dbconfig.properties文件的内容如下:

usernamehaha=root
password=123456
jdbcUrl=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
引用外部属性文件,需要依赖context名称空间:
<!--加载外部配置文件 固定写法classpath:表示引用路径下的一个资源-->
<!--使用username是不行的,因为这是Spring中的一个关键字-->
<context:property-placeholder location=”classpath:dbconfig.properties”/>
<beans id=”dataSource” class=”com.mchang.Combopool”>
<!--${key}动态取出配置文件中某个key对应的值-->
<property name=”user” value=”${usernamehaha}”></property>
<property name=”password” value=”${password}”></property>
<property name=”jdbcUrl” value=”${jdbcUrl}”></property>
<property name=”driverClass” value=”${driverClass}”></property>
</bean>

实验13:基于XML的自动装配(自定义类型自动赋值)
javaBean(基本类型)
(自定义类型的属性是一个对象,这个对象在容器中可能存在)

<bean id=”car” class=”com.kkkoke.Car”>
<property name=”carName” value=”兰博基尼”></property>
<property name=”color” value=”白色”></property>
</bean>

<!--为Person里面的自定义的属性赋值
property:手动装配
自动赋值(自动装配)
autowire=”default/no”:不自动装配
autowire=”byType”:以属性的类型作为查找依据去容器中找到这个组件。如果容器中有多个这个类型的组件就会报错。没找到的话,就装配null。
autowire=”byName”:按照名字:以属性名作为id去容器中找到这个组件,给他赋值,如	果找不到就装配null。
autowire=”constructor”:按照构造器进行赋值,
1)先按照有参构造器参数的类型进行装配(成功就赋值),没有直接装配null
2)如果按照类型有多个:参数的名作为id继续匹配;找不到就装配null
3)不会报错;
-->
<bean id=”person” class=”com.kkkoke.Person”>
<property name=”car” ref=”car”></property>
</bean>

<bean id=”person” class=”com.kkkoke.Person” autowire=”byName”></bean>

实验14:SpEL(Spring Expression Language)测试
在SpEL中使用字面量;
引用其他bean;
引用其他bean的某个属性值;
调用非静态方法;
调用静态方法;
使用运算符:都支持。

<!--在SpEL中使用字面量-->
<bean id=”person” class=”com.kkkoke.Person”>
<!--字面量:${};  #{}-->
<property name=”age” value=”#{12 * 5}”></property>
<property name=”lastName” value=”#{book01.bookName}”></property>
<property name=”car” value=”#{car}”></property>
<!--调用静态方法
UUUID.randomUUID().toString();
#{T(全类名).静态方法名(1, 2)}
-->
<property name=”email” value=”#{T(java.util.UUID).randomUUID().toString(0, 5)}”></property>
<!--调用非静态方法-->
<property name=”gender” value=#{book01.getBookName()}></property>
</bean>

AOP:面向切面(方面),在不修改源代码的基础上进行功能增强。

实验15:通过注解分别创建Dao、Service、Controller(控制器:控制网站跳转逻辑:Servlet)
通过注解分别创建Dao、Service、Controller
通过非bean上添加某些注解,可以快速地将bean加入到ioc容器中。
Spring有四个注解:
某个类上添加任何一个注解都能快速地将这个组件加入到ioc容器的管理中。
@Controller:控制器,我们推荐费控制层(Servlet包下的这些)的组件加这个注解。
@Service:业务逻辑:我们推荐业务逻辑的组件层添加这个注解:BookService。
@Repository:给数据库层(持久化层,dao层)的组件添加这个注释。
要是不想使用默认的名字,那么可以直接像这样:
@Repository(“bookdaohaha”)
@Component:给不属于以上几层的组件添加这个注释。

注解可以随便加:Spring底层不会去验证你的组件,是否如你的注解所说就是一个dao层的或者就是Service层的组件。
推荐各自层加各自层注解:这是给程序员自己看的。

使用注解将组件快速地加入到容器中需要的步骤:
1)给要添加的组件上标上四个注解中的任何一个
2)告诉Spring,自动扫描加了注解的组件,依赖context名称空间
3)一定要导入AOP包,支持注解模式的
使用注解加入到容器中的组件,和使用配置加入到容器中的组件行为都是默认的。

实验16:使用context:include-filter 指定扫描包时要包含的类。
只扫描进入哪些组件,默认都是全部扫描进来。

实验17:使用context:exclude-filter指定扫描包时不包含的类。
扫描时可以排除一些不需要的组件
type属性用来指定排除的规则。
type=”annotation”,指定扫描的规则,按照注解进行排除。标注指定注解耳朵组件就不要了。
expression=””:注解的全类名。
type=”aspectj”,后来的aspectj表达式,几乎没人用。
type=”assignable”,指定排除某个具体的类,按照类排除。
expression=””:类的全类名。
type=”custom”,自定义一个TypeFilter;自己写代码决定哪些使用
type=”regex”,还可以写正则表达式。

实验18:使用@Autowired注解实现根据类型实现自动装配
DI(依赖注入)
@Autowired:Spring会自动为这个属性赋值;一定是去容器中照这个属性的对应的组件。
@Autowired原理:
@Autowired
private BookService bookService;
1)先按照类型去容器中找到对应的组件:bookService = ioc.getBean(BookService.class);
1.找到一个,直接赋值。
2.没找到的话,就抛出异常。
3.如果找到多个的话,就会把变量名作为id继续匹配
@Qulifier:指定一个名作为id,让Spring不再使用变量名作为id。
@Quifiler(“bookService”)
@Autowired(required=false): 找不到就算了(装配null),没有加这个属性会一直找,找不到就报错。

//方法上如果有@Autowired的话,
//1)这个方法也会在bean创建的时候自动运行。
//2)这个方法上的每一个参数都会在自动注入值。

@Autowired
public void hahaha(BookDao bookDao) {

}

@Autowired,@Resource,@Inject:这些注解都是自动装配的意思。
@Autowired最强大,是Spring自己规定的。
而@Resource是java规定的,java标准,功能一般。
@Resource是标准的,扩展性更强,而@Autowired离开Spring就无法使用了。

使用Spring的单元测试:
1.导包,Spring单元测试包。
2.@ContextConfiguration(locations=””)使用它来指定Spring的配置文件的位置。
3.RunWith(SpringJUnit4ClassRunner.class)用来指定使用哪种驱动进行单元测试,默认就是junit。
表示使用Spring的单元测试模块来执行标了@Test注解的测试方法。
以前@Test注解只是有Junit执行。
好处是:不用再使用ioc.getBean()获取组件了,直接Autowired组件,Spring为我们自动装配。

实验23:测试泛型依赖注入
泛型依赖注入,注入一个组件的时候,它的泛型也是参考标准
Spring中可以使用带泛型的父类类型来确定子类的类型。

IOC容器总结:
IOC是一个容器,帮我们管理所有的组件。
1.依赖注入:@Autowired:自动赋值。
2.某个组件要使用Spring提供的更多(IOC、AOP)必须加入到容器中。
体会:
1.容器启动。会创建所有单实例bean;
2.Autowired自动装配的时候,是从容器中找这些符合要求的bean;
3.ioc.getBean(“bookservlet”);也是从容器中找到这个bean;
4.容器中包括了所有的bean;
5.调试Spring的源码,容器到底是什么?其实就是一个map
6.这个map中保存所有创建好的bean,提供外界获取功能…
7.探索,单实例的bean都保存到哪个map去了。
8.源码调试的思路:
所有源码的调试都是从helloworld开始的。给helloworld每一个关键点打上一个断点,进去看里面做了什么工作?
怎么知道哪些方法是干什么的?
1)先来翻译这个方法是干什么的。
2)放行这个方法,看控制台,或者看debug每一个变量的变化。
3)看方法注释。
学到的一点:
1)规范注释。
2)规范方法名和类名。

AOP面向切面编程:
AOP:(Aspect Oriented Programming)面向切面编程
OOP:(Object Oriented Programming)面向对象编程

面向切面编程:是一种新的编程思想,基于OOP基础的新的编程思想;
指在程序运行期间,将某段代码动态地切入到指定方法的指定位置进行运行的这种编程方式。

动态代理:

package com.kkkoke.proxy;

import com.kkkoke.inter.Calculator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class CalculatorProxy {

    //为传入的参数对象创建一个动态代理对象
    //Calculator calculator:被代理对象
    //返回的是代理对象
    public static Calculator getProxy(final Calculator calculator) {
        //目标方法。帮目标对象执行目标方法
        InvocationHandler h = new InvocationHandler() {
            /**
             * Object proxy:代理对象,这个是给JDK使用的,任何时候都不要动这个对象
             * Method method:当前将要执行的目标对象的方法
             * Object[] args:方法调用时,外界传入的参数值
             * @param proxy
             * @param method
             * @param args
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //利用反射执行目标方法
                //目标方法执行后的返回值
                //System.out.println("这是动态代理将要帮你执行方法");
                System.out.println("【" + method.getName() + "】方法开始执行,用的参数列表【" + Arrays.asList(args) + "】");
                Object result = method.invoke(calculator, args);
                System.out.println("【" + method.getName() + "】方法执行完成,计算结果是:" + result);
                //返回值必须返回出去外界才能拿到真正执行后的返回值
                return result;
            }
        };
        //没代理对象实现的接口
        Class<?>[] interfaces = calculator.getClass().getInterfaces();
        //被代理对象的类加载器
        ClassLoader loader = calculator.getClass().getClassLoader();

        //Proxy为目标对象创建代理对象
        Object proxy= Proxy.newProxyInstance(loader, interfaces, h);
        return (Calculator) proxy;
    }
}

package com.kkkoke.test;

import com.kkkoke.impl.MyMathCalculator;
import com.kkkoke.inter.Calculator;
import com.kkkoke.proxy.CalculatorProxy;
import org.junit.Test;

public class AOPTest {
    @Test
    public void test01() {
        Calculator calculator = new MyMathCalculator();
        int res = calculator.add(1, 2);
        System.out.println(res);
    }

    @Test
    public void test02() {
        MyMathCalculator myMathCalculator = new MyMathCalculator();
        int res = myMathCalculator.mul(3,4);
        System.out.println(res);
    }

    @Test
    public void test03() {
        System.out.println("=============");
        //如果是拿到了这个对象的代理对象,代理对象执行加减乘除。
        Calculator calculator = new MyMathCalculator();
        Calculator proxy = CalculatorProxy.getProxy(calculator);
        proxy.add(2,4);
    }
}

有了动态代理,日志记录可以做的非常强大,而且与业务逻辑解耦。
可以使用动态代理来将日志代码动态地在目标方法执行前后先执行。

动态代理的问题:
1)写起来比较困难。
2)jdk默认的动态代理,如果目标对象没有实现任何接口的话,是无法为它创建动态代理的。
代理对象与被代理对象唯一能产生的关联就是实现了同一个接口。

Spring动态代理难,Spring实现了AOP功能,底层就是动态代理。
1)可以利用Spring一句代码都不去写的去创建动态代理。
2)实现简单,而且没有强制要求目标对象必须实现接口。

AOP简单总结;
将某段代码动态地切入到指定方法的指定位置进行运行的这种编程方式。

AOP使用步骤:
1)导包

2)写配置
1)将目标类和切面类(封装了通知方法(在目标方法前后执行前后执行的方法))加 入到IOC容器中。
2)还应该告诉Spring到底哪个是切面类。添加一个注释@Aspect
3)告诉Spring,切面类里面的方法都是何时何地运行。
@Before:在目标方法运行之前执行。前置通知。
@After:在目标方法运行结束之后。后置通知。
@AfterReturning:在目标方法正常返回之后。返回通知。
@AfterThrowing:在目标方法抛出异常之后运行。异常通知。
@Around:环绕。环绕通知。

切入点表达式:
execution(返回权限符,返回值类型,方法签名)
4)开启基于注解的AOP模式。
3)测试

AOP细节一:

注意:
1.从ioc容器中拿到目标对象;注意:如果想要用类型,一定要用他的接口类型,不要 用它本类。
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1, 2);
2.没有实现接口,获取的时候就是用的本类类型。
cglib帮我们创建好了代理对象。
MyMathCalculator bean = ioc.getBean(MyMathCalculator.class);
bean.mul(2, 4);

补充:cglib是一个专门搞代理的一个组织。

AOP的底层就是代理,容器中保存的组件是他的代理对象。
接口一般不加载到容器中。

AOP细节二:

切入点表达式的写法:
固定格式:execution(访问权限符 返回值类型 方法全签名(参数表))
通配符:

1)匹配一个或者多个字符。
2)匹配任意一个参数。第一个参数是int类型,第二个参数任意类型。(匹配两个参 数)
@AfterReturning(“execution(public int com.kkkoke.impl.MyMath
.(int, ))”)
3)匹配任意多个参数,任意类型参数。
@AfterReturning(“execution(public int com.kkkoke.impl.MyMath
.
(…))”)
4)匹配任意多层路径。
execution(public int com.kkkoke…MyMath*.(…))
5)权限位置是不能用
代替的。
两种匹配方式:
1.最精确的。execution(public int com.kkkoke.impl.MyMathCalculator.add(int, int)
2.最模糊的:execution(* .(…)):千万别写

切入点表达式还能使用:&&, ||, !

AOP细节三:

通知方法的执行顺序:

正常执行 :@Before(前置通知)@After(后置通知)@AfterReturning(正常返回)

异常执行:@Before(前置通知)@After(后置通知)@AfterThrowing(方法异常)

AOP细节四:

可以在通知方法运行的时候,拿到目标方法的详细信息。
1)只需要为通知方法的参数列表上写一个参数。
JoinPoint joinPoint: 封装了当前目标方法的详细信息。

@Before("execution(public int com.kkkoke.impl.MyMathCalculator.add(int, int)")
public static void logStart(JoinPoint joinPoint) {
//获取目标方法运行时使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
	String name = signature.getName();
    System.out.println("【" + name + "】方法开始执行,用的参数列表【" + Arrays.asList(args) + "】");
}

AOP细节五:

throwing、Returning来指定哪个参数用来接收异常、返回值。

//在获取返回值的时候,要告诉Springresult是用来接收返回值的。

@AfterReturning(value="execution(public int com.kkkoke.impl.MyMathCalculator.*(int, int)" returning=”result”)
public static void logReturn(JoinPoint joinPoint, Object result) {
	Signature signature = joinPoint.getSignature();
	String name = signature.getName();
	System.out.println(“【” + name + “】方法正常执行完成,计算结果是:+ result);
}

接收异常:throwing=”exception”

AOP细节六:

Spring对通知方法的约束(参数表一定正确)
Spring对通知方法的要求不严格,唯一有要求的是方法的参数列表一定不能乱写。
通知方法是Spring利用反射调用的,每次方法调用得确定这个方法的参数表的值。
参数表上的每一个参数,Spring都得知道是什么。
不知道的参数一定要告诉Spring是什么。
Exception exception 及其 result都可以往大了写,这样就能接受范围更加广的异常和结果。
类似于Ajax接受服务器数据。

AOP细节七:

抽取可重用的切入点表达式:
1.随便声明一个没有实现的返回void的空方法。
public void hahaMyPoint(){};
2.给方法上标注@Pointcut注解
@Pointcut(“execution(public int com.kkkoke.impl.MyMathCalculator.add(…)”)
public void hahaMyPoint(){};
之后就可以直接使用这个方法的名代替
@After(value=”hahaMyPoint()”, returning=”result”)

事务:
操作数据库:
Spring提供了JdbcTemplate能快捷地操作数据库。
JdbcTemplate和QueryRunner的使用比较相似。

实验1:测试数据源

jdbc.user=root
jdbc.password=123456
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/mysql
##这里的写法可以类比http://localhost:8080/bookstore
##这里是jdbc协议
jdbc.driverClass=com.mysql.jdbc.Driver


<bean id="comboPooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.user}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>

实验2:修改数据

@Test
public void test02() {
	String sql =UPDATE employee SET salary=? WHERE emp_id=?;
	int update = idbcTemplate.update(sql, 1300.00, 5);
	System.out.println(“更新员工:” + update);
}

实验3:批量插入
SQL语句 INSERT INFO employee(emp_name, salary) VALUES(?, ?)

@Test
public void test03() {
	String sql =INSERT INFO employee(emp_name, salary) VALUES(?, ?);
	//List<Object[]>
	//List的长度就是SQL语句要执行的次数
	List<Object> batchArgs = new ArryayList<Object[]>();
	jdbcTemplate.batchUpdate(sql, batchArgs);
	batchArgs.add(“张三”, 12345);
	batchArgs.add(“李四”, 23456);
}

Spring和JavaWeb整合使用:
1)Spring来控制事务(dao–JdbcTemplate)
2)所有的组件Autowired
3)管理数据库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值