Spring-学习笔记
1.1.简介
Spring是一个轻量级控制反转(IOC)和面向切面编程(AOP)的容器框架
Spring理念:使现有的技术更加容易使用
IOC(Inverse Of Control)
AOP(Aspect Oriented Programming)
-
SSH(早期):Struct2 + Spring + Hibernate
-
SSM(现在):SpringMVC + Spring + Mybatis
https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/
官网:https://spring.io/projects/spring-framework#overview
官方下载地址:
GitHub:https://github.com/spring-projects/spring-framework/releases/tag/v5.2.8.RELEASE
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
1.2、优点
-
Spring是一个开源的免费的框架(容器)
-
Spring是一个轻量级的、非入侵式(导入后不影响原来项目)的框架
-
控制反转(IOC)、面向切面编程(AOP)
-
支持事务的处理,对框架整合的支持
总结:Spring是一个轻量级控制反转(IOC)和面向切面编程(AOP)的容器框架
1.3、组成
1.4、拓展
现代化Java开发:基于Spring的开发
-
Spring Boot:构建一切
-
Spring Cloud:协调一切
-
Spring Cloud Data Flow:连接一切
-
Spring Boot(前提:必须完全掌握Spring和SpringMVC)
-
一个快速开发的脚手架
-
基于SpringBoot可以快速的开发单个微服务
-
约定大于配置
-
-
Spring Cloud
-
基于Spring Boot实现
-
弊端:配置繁琐
1.5、Spring优势
-
方便解耦,简化开发
-
AOP编程的支持
-
声明式事务的支持(通过配置的方式实现事务控制)
-
方便程序的测试
-
方便集成各种优秀框架
-
降低JavaEE API的使用难度
-
其java源码是经典的使用案例
2、IOC理论推导
之前的开发流程:UserDao-->UserDaoImpl-->UserService-->UserServiceImpl
该流程十分繁琐,耦合性过高。假如当用户要求用Mysql实现,我们需要为UserDao新增一个Mysql的实现类,并且在Service层进行调用,需要修改源代码,增一个改一次,十分不科学。由此,我们可以在Service层使用接口注入的方式指定使用的UserDao的实现类的类型,该类型由用户指定即可
如:在Service层调用Dao层时使用以下方法指定类型
private UserDao userDao;
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
即
-
之前,程序主动创建对象(Service层自己创建Dao对象),控制权在程序员手上
-
使用了set注入后,程序不再具有主动性,而是变成了被动的接受对象
这种思想,从本质上解决了问题,程序员不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注在业务的实现上,这是IOC的原型
IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
IOC作用:削减计算机程序的耦合(解除代码中的依赖关系)
3、HelloSpring
xml文件框架:
实体类:
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
配置文件:
测试:
思考:
-
Hello 对象是谁创建的 ? hello 对象是由Spring创建的
-
Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
-
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
-
反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用set方法来进行注入的.
IOC是一种编程思想,由主动的编程变成被动的接收
可以通过new ClassPathXmlApplicationContext去浏览一下底层源码
至此,我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !
核心容器相关(heima)
ApplicationContext 的三个常用实现类
①ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话无法加载(相对于②更常用)
②FileSystemXmlApplicationContext:它可以记载磁盘任意路径下的配置文件(必须有访问权限)
③AnnotationConfigApplicationContext:它是通过读取注解创建容器的
核心容器的两个接口引发出的问题
ApplicationContext :(单例对象适用)(实际开发中通常采用此接口定义容器对象)
它在构建核心容器时,创建对象采取的策略是采用立即加载的方式,即,一读取完配置文件马上就创建配置文件中配置的对象
BeanFactory:(多例对象适用)
它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式,即,什么时候根据id获取对象了,什么时候才真正的创建对象
spring对bean的管理细节
1.创建bean的三种方式
①使用默认构造函数创建:在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
如:
<bean id="accountService" class="com.liu.service.AccountServiceImpl"></bean>
②使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
如:
<bean id="instanceFactory" class="com.liu.factory.InstanceFactory"/>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>
③使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
如: <bean id="accountService" class="com.liu.factory.StaticFactory" factory-method="getAccountService"/>
2.bean对象的作用范围
bean的作用范围调整:
bean标签的scope属性
作用:用于指定bean的作用范围
取值:常用单例和多例
singleton:单例的(默认)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
3.bean对象的生命周期
单例对象:
出生:当容器创建时对象出生(读取完配置文件)
活着:只要容器存在,对象一直活着
死亡:容器销毁,对象死亡
总结:单例对象的生命周期和容器相同
多例对象:
出生:当我们使用对象时,spring框架为我们创建
活着:对象只要在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
Spring的IOC容器:Map结构(key是String类型,value是Object类型)
4、IOC创建对象的方式
1.使用无参构造创建对象【默认】
<bean id="hello" class="com.liu.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
2.使用有参构造创建对象
1.根据下标赋值
<!-- 第一种,下标赋值 -->
<bean id="user" class="com.liu.pojo.User">
<constructor-arg index="0" value="内工大"/>
</bean>
2.根据类型赋值
<!-- 第二种,通过类型创建,不建议使用 -->
<bean id="user" class="com.liu.pojo.User">
<constructor-arg type="java.lang.String" value="清华"/>
</bean>
3.根据参数名赋值
<!-- 第三种,直接通过参数名赋值 -->
<bean id="user" class="com.liu.pojo.User">
<constructor-arg name="name" value="北大"/>
</bean>
总结:在配置文件加载时,容器中管理的对象就已经初始化了
5、Spring配置
5.1、别名
<!-- 别名,如果添加了别名,也可以使用别名获取这个对象 --> <alias name="user" alias="userNew"/>
5.2、bean的配置
id:bean 的唯一标识符,相当于对象名
class:bean 对象所对应的类的全限定名(包名 + 类名)
name:也是别名,比alias更高级,可以同时取多个别名(多个别名间以 ,或 ;或空格间隔)
5.3、import
import一般用于团队开发使用,可以将多个配置文件导入合并为一个
使用时,直接使用总的配置就可以(applicationContext.xml)
heima(DI)
spring中的依赖注入
依赖注入:Dependency Injection
IOC的作用:降低程序间的耦合(依赖关系)
依赖关系的管理:都交给spring维护。即,在当前类中需要用到其他类的对象,由spring提供,我们只需要在配置文件中说明即可
依赖关系的维护,就称之为依赖注入
依赖注入:
能注入的数据有三类:
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型 / 集合类型
用于给List结构集合注入的标签有: list、array、set 用于给Map结构集合注入的标签有: map、props总结:结构相同,标签可以互换
注入的方式:有三种
①使用构造函数注入
使用的标签:constructor-arg 标签出现的位置:bean标签内部 标签中的属性: type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始 name:用于指定给构造函数中指定名称的参数赋值 【常用】 ===========以上三个用于指定给构造函数中哪个参数赋值============ value:用于提供基本类型和String类型的数据 ref:用于指定其他的bean类型数据(在spring的IOC核心容器中出现过的bean对象)优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法成功创建
弊端:
改变了bean对象的实例化方式,在创建对象时,如果用不到这些数据,也必须提供
②使用set方法注入 【常用】
使用的标签:property 标签出现的位置:bean标签内部 标签的属性: name:用于指定注入时所调用的set方法的名称 value:用于提供基本类型和String类型的数据 ref:用于指定其他的bean类型数据(在spring的IOC核心容器中出现过的bean对象)优势:
创建对象时,没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行
③使用注解注入
XML的配置方式
* <bean id="accountService" class="com.liu.service.AccountServiceImpl"
* scope="" init-method="" destroy-method="">
* <property name="" value="" | ref=""></property>
* </bean>
*
* 注解分类:
* 用于创建对象的
* 它们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
* @Component
* 作用:用于把当前类对象存入spring容器中
* 属性:
* value:用于指定bean的id,当我们不写时,它的默认值是当前类名,且首字母改小写(当注解属性中只有value一个属性时,value可以不写)
* @Controller:一般用于表现层
* @Service:一般用于业务层
* @Repository:一般用于持久层
* 以上三个注解的作用和属性与Component一模一样
* 是spring框架为我们提供明确的三层使用的注释,使我们的三层对象更加清晰
*
* 用于注入数据的
* 它们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
* @Autowired:
* 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
* 如果IOC容器中没有任何bean的类型和要注入的变量类型相匹配,则报错
* 如果IOC容器中有多个bean类型和要注入的变量类型匹配,则进一步比较要注入的变量名和bean的id,如果没有相等的id,则spring容器无法注入
* 出现位置:
* 可以是变量上,也可以是方法上
* @Qualifier:
* 作用:在按照类型注入的基础之上,再按照名称注入。
* 它在给类成员注入时不能单独使用,但是在给方法参数注入时可以
* 属性:
* value:用于指定注入bean的id
* @Resource:
* 作用:直接按照bean的id注入,可以独立使用
* 属性:
* name:用于指定bean的id
* 注:以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
* 另外,集合类型的注入只能通过XML实现
*
* @Value:
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值,它可以使用spring中的SpEL(也就是spring中的el表达式)
* SpEL的写法:${表达式}
* 不同地方的EL表达式会去不同的地方取值(jsp中的el表达式会去四大域中取值;mybatis中的el表达式会去对应的位置取值;spring的el表达式会去spring指定的位置取值)
*
* 用于改变作用范围的
* 它们的作用就和在bean标签中使用scope属性实现的功能是一样的
* @Scope:
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton【默认】、prototype
*
* 和生命周期相关的 (了解)
* 它们的作用就和在bean标签中使用init-method和destroy-method属性的作用是一样的
* @PreDestroy:
* 用于指定销毁方法
* @PostConstruct
* 用于指定初始化方法
* 出现位置:方法上
- spring中的新注解
- @Configuration:
-
作用:指定当前类是一个配置类 -
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写 - @ConponentScan:
-
作用:用于通过注解指定spring在创建容器时要扫描的包 -
(首先要spring认为包下的类是配置类,spring才会扫描里面的注解,加上@Configuration注解即将该类标记为配置类,或者在注解创建容器对象时,将该类的字节码文件当作参数传入,或者使用@Import注解导入) -
属性: -
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包 -
使用此注解就等同于在xml文件中配置了:<context:component-scan base-package="com.liu"/> - @Bean
-
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中 -
属性: -
name:用于指定bean的id,当不写key时,默认key是当前方法的名称 - 细节:当使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
-
查找的方式和Autowried注解的作用是一样的(没有匹配、一个匹配、多个匹配) - @Import
-
作用:用于导入其他的配置类 -
属性: -
value:用于指定其他配置类的字节码文件 -
当使用了Import的注解之后,有Import注解的类就是主配置类(父配置类),而导入的都是子配置类 - @PropertySource
-
作用:用于指定properties文件的位置 -
属性: -
value:指定文件的名称和路径 -
关键字:classpath,表示类路径下(resources包中的资源在部署完成后都会放到类路径下)</pre>
spring整合junit
减少测试类中重复的编写创建容器对象及获取service对象的代码
* spring整合junit的配置 * 1、导入spring整合junit的jar包(坐标)----spring-test * 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 * @RunWith * 3、告诉spring的运行器,spring的ioc创建是基于xml还是基于注解的,并且说明位置 * @ContextConfiguration * 属性: * locations:指定xml文件的位置,加上classpath关键字,表示在类路径下 * classes:指定注解类所在的地方 * * 注:当我们使用spring 5.x 版本时,要求junit的jar必须是4.12及以上
6、DI(依赖注入)
6.1、构造器注入
前面说过
6.2、Set方式注入【重点】
-
依赖注入:Set注入
-
依赖:bean对象的创建依赖于容器
-
注入:bean对象中的所有属性由容器来注入
-
【环境搭建】
1.复杂类型
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2.真实测试对象
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
//get、set、toString
}
3.beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.liu.pojo.Student">
<!-- 第一种,普通值注入,value -->
<property name="name" value="张三"/>
</bean>
</beans>
4.测试类
public class MyTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getAddress());
}
}
完善注入信息:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.liu.pojo.Address">
<property name="address" value="呼和浩特"/>
</bean>
<bean id="student" class="com.liu.pojo.Student">
<!-- 第一种,普通值注入,value -->
<property name="name" value="张三"/>
<!-- 第二种,Bean注入,ref -->
<property name="address" ref="address"/>
<!-- 数组注入 -->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!-- List -->
<property name="hobbies">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!-- Map -->
<property name="card">
<map>
<entry key="身份证" value="123456"/>
<entry key="银行卡" value="258974"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>CF</value>
<value>WZRY</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--properties
key=value
-->
<property name="info">
<props>
<prop key="学号">20181018</prop>
<prop key="姓名">小明</prop>
<prop key="性别">男</prop>
</props>
</property>
</bean>
</beans>
6.3、扩展方式注入
可以使用p命名空间和c命名空间进行注入
需要在beans.xml文件导入xml约束
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
使用:
<?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:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入,可以直接注入属性的值:property -->
<bean id="user" class="com.liu.pojo.User" p:name="张三" p:age="18"/>
<!-- c命名空间注入,通过构造器注入:construct-args -->
<bean id="user2" class="com.liu.pojo.User" c:name="李四" c:age="20"/>
</beans>
测试:
@Test
public void testP(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
User user2 = context.getBean("user2", User.class);
System.out.println(user2);
}
6.4、bean的作用域
1.单例模式(Spring的默认机制)
如: <bean id="user2" class="com.liu.pojo.User" c:name="李四" c:age="20" scope="singleton"/>
2.原型模式:每次从容器中get的时候,都会产生一个新对象
如: <bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
3.其余的request、session、application,这些只能在web开发中使用
7、Bean的自动装配
-
自动装配是Spring满足bean依赖的一种方式
-
Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式:
1.在xml中显式的配置
2.在java中显式的配置
3.隐式的自动装配bean【重要】
7.1、测试
1.环境搭建
-
一个人有两个宠物(三个实体类)
-
将实体类注册到bean中,用的时候直接拿
7.2、byName自动装配
<bean id="cat" class="com.liu.pojo.Cat"/> <bean id="dog222" class="com.liu.pojo.Dog"/><!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanid
-->
<bean id="people" class="com.liu.pojo.People" autowire="byName">
<property name="name" value="张三"/>
</bean>
7.3、byType自动装配
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的beanid
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean,但是必须保证类型全局唯一
-->
<bean id="people" class="com.liu.pojo.People" autowire="byType">
<property name="name" value="张三"/>
</bean>
小结:
-
byName时,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法后面的值一致
-
byType时,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致
7.4、使用注解实现自动装配
jdk1.5开始支持注解,Spring2.5开始支持
要使用注解须知:
1.导入约束:context约束
xmlns:context="http://www.springframework.org/schema/context"
2.配置注解的支持:context:annotation-config/
<context:annotation-config/>
@Autowired
直接在属性上使用,也可以在set方法上使用
使用Autowired可以不用编写set方法,前提是自动装配的属性在IOC(Spring)容器中存在,且符合byType要求
注:如果自动装配环境比较复杂(如有多个bean时),可以与@Qualifier(value = "")合用,选择指定id的bean
public class People {
private String name;
//如果显式的定义了Autowired注解的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired
@Qualifier(value = "cat")
private Cat cat;
@Autowired
private Dog dog;
...
}
科普:
@Nullable:标记的属性为空可以不报错
public @interface Autowired {
boolean required() default true;
}
如:
//如果显式的定义了Autowired注解的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required=false)
Resource注解
java的注解 @Resource 也可以自动装配bean(还可以通过其 name 属性指定要装配的bean的id)
小结:
@Resource和@Autowired的区别
-
都是用来自动装配,都可以放在属性字段上
-
@Autowired默认通过byType的方式实现
-
@Resource默认通过byName的方式实现,如果找不到名字,则通过byType的方式实现
测试:
public class People {
private String name;
//如果显式的定义了Autowired注解的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
...
}
8、使用注解开发
在Spring4之后,要使用注解开发,必须要保证 aop 的包导入了
使用注解需要导入context约束,增加注解的支持
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.liu"/>
</beans>
1.bean
@Component:组件,放在类上,说明这个类被Spring管理了,就是bean
2.属性如何注入
@Value(""):放在属性上或属性的set方法上,当简单时可以使用
3.衍生的注解
@Component有几个衍生的注解,比如在web开发中,会按照MVC三层架构分层
-
dao:【@Repository】
-
service:【@Service】
-
controller:【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
4.自动装配
@Autowired @Qualifier(value = "") @Resource @Nullable
5.作用域
@Scope("")
//单例:singleton
//原型:prototype
6.小结
xml 与 注解:
-
xml 更加万能,适用于任何场合,维护简单方便
-
注解 不是自己的类使用不了(如ref引用),维护相对复杂
xml 与 注解的最佳实践:
-
xml 用来管理 bean
-
注解 只负责完成属性的注入
-
我们在使用的过程中,必须要注意一个问题,必须让注解生效,需要开启注解的支持
<context:annotation-config/> <!--指定要扫描的包,这个包下的注解就会生效--> <context:component-scan base-package="com.liu"/>
AOP(heima)
代理模式:
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* I.基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法:xxx.getClass().getClassLoader();
* Class[]:字节码数组
* 用于让代理对象和被代理对象有相同的方法(如两个实现同一个接口,则两个都有接口中的方法)。固定写法:xxx.getClass().getInterfaces();
* InvocationHandler:用于提供增强的代码
* 让我们写如何代理,一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类谁用谁写
*
* II.基于子类的动态代理
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 用于指定被代理对象的字节码
* Callback:用于提供增强的代码
* 写如何代理,通常情况下是匿名内部类,但不是必须的
* 此接口的实现类谁用谁写
* 一般写的都是该接口的子接口实现类:MethodInterceptor
*/
基于子类的动态代理:需要导入:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
代理模式好处:
-
可以使真实角色的操作更加纯粹,不用关注一些公共的业务
-
公共业务交给代理角色,实现了业务的分工
-
公共业务发生扩展的时候,方便集中管理
-
一个动态代理类代理的是一个接口,一般就是对应的一类业务
-
一个动态代理类可以代理多个类,只要是实现了同一个接口即可
缺点:
-
一个真实角色就会产生一个代理角色,代码量变多,开发效率变低
AOP:Aspect Oriented Programming(面向切面编程)
将程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强
AOP作用:
在程序运行期间,不修改源码对已有方法进行增强
优势:
-
减少重复代码
-
提高开发效率
-
维护方便
AOP的实现方式
使用动态代理技术
Spring中的AOP
选择准则:判断是否实现了接口
-
实现--基于接口的动态代理:要求被代理对象至少实现一个接口
-
未实现--基于子类的动态代理:要求被代理类不能是最终类
AOP相关术语:
Joinpoint(连接点):spring中指方法(如业务层接口中的方法),因为spring只支持方法类型的连接点
Pointcut(切入点):所有被代理类增强的方法
即:所有的切入点都一定是连接点,但反之不然
Advice(通知 / 增强):invoke中对被代理对象的方法进行增强(如给增删改方法增加事务管理)
通知的类型:
-
前置通知:在invoke方法中method.invoke之前执行的
-
后置通知:在invoke方法中method.invoke之后执行的
-
异常通知:在invoke方法中catch中执行的
-
最终通知:在invoke方法中finally中执行的
-
环绕通知:整个invoke方法在执行就是环绕通知
在环绕通知中,有明确的切入点方法调用
Target(目标对象):被代理对象
Proxy(代理):代理对象
Weaving(织入):增强被代理对象方法的过程(动态代理织入)
Aspect(切面):
Introduction(引介):不修改类代码,在运行期为类动态地添加一些方法或字段(了解)
导入坐标
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
</dependencies>
<!--Spring中基于XML的AOP配置步骤
1、把通知Bean也交给Spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标识
ref属性:指定通知类bean的Id
4、在aop:aspect标签的内部使用对应标签配置通知的类型
现在示例让printLog方法在切入点方法执行之前执行,所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
标准的表达式写法:
public void com.liu.service.AccountServiceImpl.saveAccount()
-->
基于xml配置:
如:day03第三个模块
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置Spring的IOC,把Service对象配置进来-->
<bean id="accountService" class="com.liu.service.AccountServiceImpl"/>
<!--Spring中基于XML的AOP配置步骤
1、把通知Bean也交给Spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标识
ref属性:指定通知类bean的Id
4、在aop:aspect标签的内部使用对应标签配置通知的类型
现在示例让printLog方法在切入点方法执行之前执行,所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名....类名.方法名(参数列表)
标准的表达式写法:
public void com.liu.service.AccountServiceImpl.saveAccount()
简化:
访问修饰符可以省略
即上面的语句可以写成:
void com.liu.service.AccountServiceImpl.saveAccount()
返回值可以写通配符:*,表示任意返回值:
* com.liu.service.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个 *. :
* *.*.*.AccountServiceImpl.saveAccount()
包名可以使用 .. 表示当前包及其子包:
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以用 * 实现通配:
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 如 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用 .. 表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法:
* com.liu.service.*.*(..)
-->
<!--配置Logger类-->
<bean id="logger" class="com.liu.utils.Logger"/>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.liu.service.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
基于注解配置:
package com.liu.utils;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;/**
-
用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {/**
- 注解配置切入点表达式
/
@Pointcut("execution( com.liu.service..(..))")
private void pt1(){}
/**
- 前置通知
*/
//@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知--Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
- 后置通知
*/
//@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知--Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
- 异常通知
*/
//@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知--Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
- 最终通知:finally中
*/
//@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知--Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
- 环绕通知
- 问题:
-
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了 - 分析:
-
通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知都有明确的切入点方法调用,而现在的代码中没有 - 解决:
-
Spring框架为我们提供了一个接口,ProceedingJoinPoint,该接口有一个方法proceed(),此方法相当于明确调用切入点方法 -
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用 - spring中的环绕通知:
-
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
*/
@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint point){
Object rtValue = null;
try{
Object[] args = point.getArgs();//得到方法执行所需的参数System.out.println("前置通知--Logger类中的aroundPrintLog方法开始记录日志了。。。前置"); rtValue = point.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("后置通知--Logger类中的aroundPrintLog方法开始记录日志了。。。后置"); return rtValue; }catch(Throwable t) { System.out.println("异常通知--Logger类中的aroundPrintLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("最终通知--Logger类中的aroundPrintLog方法开始记录日志了。。。最终"); }}
} - 注解配置切入点表达式
Spring中的JdbcTemplate[会用]
需要用 spring-jdbc-...jar 包和 spring-tx-...jar 包
JdbcTemplate 的作用
用于和数据库交互,实现对表的CRUD操作
如何创建该对象
手动创建: JdbcTemplate jt = new JdbcTemplate(DataSource dataSource);
对象中的常用方法
execute(); query(); queryForObject(); update();
可以通过继承 JdbcDaoSupport 类来实现减少 Dao 类中对 JdbcTemplate 的定义和set注入方法的代码,但是一旦继承了 JdbcDaoSupport 类,就很难通过注解配置,因为该类是jar包中的方法,不能修改(即只能通过XML配置)
Spring事务管理
基于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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--配置业务层对象-->
<bean id="accountService" class="com.liu.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置账户的持久层-->
<bean id="accountDao" class="com.liu.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mysql"/>
</bean>
<!--spring中基于XML的声明式事务控制配置
1.配置事务管理器:包含事务的提交和回滚
2.配置事务的通知:<tx-advice>
此时需要导入spring-tx.jar包以及tx和aop的空间约束
属性:
id:给事务通知起一个唯一的标识
transaction-manager:给事务通知提供一个事务管理器引用
3.配置aop中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性
在tx:advice标签中配置
-->
<!--配置事务管理器-->
<bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transaction">
<!--配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改选择REQUIRED;查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true,默认值是false,表示读写。
timeout:用于指定事务的超时时间。默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,表示任何异常都回滚。
-->
<tx:attributes>
<!--增删改一定有事务,可读写-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<!--查询根据情况判断是否有事务,只读-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="pt1" expression="execution(* com.liu.service.impl.*.*(..))"/>
<!--建立事务通知和切入点表达式的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
</aop:config>
</beans>
基于注解:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.liu"/>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mysql"/>
</bean>
<!--spring中基于注解的声明式事务控制配置
1.配置事务管理器:包含事务的提交和回滚
2.开启spring对注解事务的支持
3.在需要事务支持的地方使用@Transactional注解
-->
<!--配置事务管理器-->
<bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transaction"/>
</beans>
在Spring配置声明式事务时,推荐使用基于XML方式配置,配置一次即可在多个Service中有效;如果使用注解方式,则需要在每个Service中都配置
@EnableTransactionManagement //开启事务注解支持

浙公网安备 33010602011771号