1.简介
Spring给软件行业带来了春天
他的目的是解决企业应用开发的复杂性
他的应用范围是任何Java应用
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架
- 2002年,首次推出了Spring框架的雏形:interface21框架
- 2004年3月24号,发布了Spring框架1.0正式版
- Rod Johnson,是Spring Framework 的创始人,他的专业是音乐学
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的基础框架
- SSH:Struct2 + Spring + Hibernate
- SSM:SpringMVC + Spring + MyBatis
官网:https://spring.io/projects
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
GitHub地址:https://github.com/spring-projects/spring-framework
maven依赖,使用webmvc包,可以导入很多其他需要的包,比较方便
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.1</version>
</dependency>
1.1 优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架(不会影响原有的项目结构,代码情况)
- 控制反转(IOC),面向切面编程(AOP)。这是两大核心特点
- 支持事务的处理,对框架整合的支持
1.2 组成
Spring七大模块的组成
核心容器(Spring Core)
核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。
应用上下文(Spring Context)
Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。
Spring面向切面编程(Spring AOP)
通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
JDBC和DAO模块(Spring DAO)
JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。
对象实体映射(Spring ORM)
Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。
Web模块(Spring Web)
Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
MVC模块(Spring Web MVC)
MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。
1.3 扩展
现代化的Java开发,实际上就是基于Spring的Java开发
用SpringBoot构建一切,用SpringCloud协调一切,用SpringCloudDataFlow连接一切
- SpringBoot
- 一个快速开发的脚手架
- 基于SpringBoot可以快速的开发单个微服务
- 约定大于配置
- Spring Cloud
- Spring Cloud是基于SpringBoot实现的
学习Spring的前提,需要完全掌握Spring和SpringMVC,他们起到了承上启下的作用
Spring发展的时间太长,整合了太多的东西,于是配置变得十分繁琐,违背了它的理念。
2.IOC理论推导
在以前的模式中,我们需要用如下步骤来实现业务
- UserDao 接口
- UserDapImlpl 实现类
- UserService 业务接口
- UserServiceImpl 业务实现类
用户只接触 Service(业务)层,不接触dao层
按照原来的方法,实现三种业务,要如下结构
// UserDao
public interface UserDao {
void getUser();
}
// UserDaoImpl
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("默认获取用户的数据");
}
}
// UserMysqlImpl
public class UserMysqlImpl implements UserDao{
public void getUser(){
System.out.println("MySql获取用户数据");
}
}
// UserOracleImpl
public class UserOracleImpl implements UserDao{
public void getUser() {
System.out.println("Oracle获取用户数据");
}
}
// UserService
public interface UserService {
void getUser();
}
// UserServiceImpl
public class UserServiceImpl implements UserService{
// 通过DaoImpl实现用户操作
private UserDao userDao = new UserDaoImpl();
// 通过MysqlImpl实现用户操作
// private UserDao userDao = new UserMysqlImpl();
// 通过OracleImpl实现用户操作
// private UserDao userDao = new UserOracleImpl();
public void getUser() {
userDao.getUser();
}
}
// Test
@Test
public void test1(){
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
由此可以看出,当用户改变访问方式时,我们就要对应的去修改Service层中的UserServiceImpl实现类
于是有了利用 set 来动态实现值的注入
// UserServiceImpl
public class UserServiceImpl implements UserService{
private UserDao userDao;
public void setUserDao(UserDao userDao){
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
// Test
@Test
public void test1(){
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserOracleImpl());
userService.getUser();
}
这是一个革命性的变化
在之前的业务中,用户的需求可能会影响原来的代码,我们要根据用户的需求来不断的修改源代码,这样会破坏程序的完整性,而且修改的成本也十分昂贵
通过 set 注入,可以让用户控制自己想要实现需求的方式,
原来是程序主动创建对象,控制权在程序员手上,使用了set注入之后,程序不再有主动性,而是被动的接收对象。程序不必再进行修改,也能通过多种方式满足需求
这种 程序主动创建对象到被动接收对象的改变,就叫做控制反转,即获得依赖对象的方式反转了
这种思想,从本质上解决了问题,程序员不用再去管理对象怎么创建,系统的耦合性大大降低,这让我们可以更加专注在业务的实现上。这是IOC的原型
2.1 IOC本质
控制反转IOC( Inversion of Control )是一种设计思想,DI(依赖注入)是实现IOC的一种方法
IOC是Spring框架的核心内容
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入( Dependency Injection,DI )
DI并不是IOC,他只是实现IOC方式的一种
3.HelloSpring
先创建一个实体类 hello
再配置元数据,编写 beans.xml
再实例化容器:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
总体如下
// hello.java
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
<!-- 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">
<!-- 使用Spring来创建对象,在Spring中,这些都称为Bean
一个bean相当一一个对象,相当于 new Hello();
原来的句子为: Hello hello = new Hello();
交给bean之后,这里的id就相当于其中的变量名(即 hello)
class相当于要new的对象(即:Hello)
property相当于给对象中的属性设置值
-->
<bean id="hello" class="com.muzimu.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
// MyTest.java
public class MyTest {
public static void main(String[] args) {
// 获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 现在对象都在Spring中管理了,要使用,直接取出即可
Hello hello = (Hello)context.getBean("hello");
System.out.println(hello.toString()); // Spring
}
}
- Hello 对象是由Spring创建的
- Hello 对象的属性是由Spring容器设置的
这个过程就叫做控制反转:传统应用程序的对象是由程序本身控制创建的,即( new Hello() ),使用Spring后,是由Spring来创建的
反转:程序本身不创建对象,而变成被动的接收对象
依赖注入:本质是set注入,就是利用set方式来注入的(即:实体类中一定要有set方法,否则Spring不能在容器中给属性设置值)
IOC是一种编程思想,由主动的编程变成被动的接收
所谓的IOC:对象由Spring来创建,管理,装配
3.1 Spring实现IOC推导例子
用Spring来完成上述的IOC推导例子
不用对原有文件进行修改,只需要增加元配置文件
<!-- 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">
<!-- 使用Spring来创建对象,在Spring中,这些都称为Bean
一个bean相当一一个对象,相当于 new Hello();
原来的句子为: Hello hello = new Hello();
交给bean之后,这里的id就相当于其中的变量名(即 hello)
class相当于要new的对象(即:Hello)
property相当于给对象中的属性设置值
-->
<bean id="mysqlImpl" class="com.muzimu.dao.UserMysqlImpl">
</bean>
<bean id="daoImpl" class="com.muzimu.dao.UserDaoImpl">
</bean>
<bean id="oracleImpl" class="com.muzimu.dao.UserOracleImpl">
</bean>
<bean id="serviceImpl" class="com.muzimu.services.UserServiceImpl">
<!--
因为UserServiceImpl中的属性为userDao,不是基本类型
ref:引用Spring容器中创建好的对象
(这里选择MySql实现,所以引用了mysqlImpl)
value:具体的值,基本数据类型使用
-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
</beans>
// MyTest.java
public static void main(String[] args) {
ApplicationContext serviceImpl = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) serviceImpl.getBean("serviceImpl");
userServiceImpl.getUser();
// MySql获取用户数据
}
要修改实现方式,只要修改 beans.xml 中的 ref 即可
<bean id="serviceImpl" class="com.muzimu.services.UserServiceImpl">
<property name="userDao" ref="mysqlImpl"/>
</bean>
我们不需要再去修改程序了,只需要修改配置文件,就可以完成所有需要
4.IOC创建对象的方式
创建实体类User
public class User {
private String name;
/* public User(String name){ // 有参构造
this.name = name;
} */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("name=" + this.name);
}
}
-
使用无参构造创建对象,这是默认方式
-
假如要使用有参构造创建对象,就要在配置文件中修改创建方式
-
下标赋值
<bean id="user" class="com.muzimu.pojo.User"> <!-- index 表示有参构造函数中参数位置,从0开始 --> <constructor-arg index="0" value="蜡笔小新"/> </bean>
-
类型赋值(不建议使用)
<bean id="user" class="com.muzimu.pojo.User"> <!-- 基本类型可以写类型,如 int ,引用类型必须写 全限定名 --> <!-- 假设两个参数都是String,就会出错 --> <constructor-arg type="java.lang.String" value="蜡笔小新"/> </bean>
-
直接通过参数名来设置(使用最多的方式)
<bean id="user" class="com.muzimu.pojo.User"> <constructor-arg name="name" value="蜡笔小新"/> </bean>
-
Spring容器就类似于婚介网站,不管你用不用,注册的bean都会被实例化。他的内存中只有一份实例,在配置文件加载的时候,容器中管理的对象就已经初始化了
有参构造中使用了引用类型,bean的设置方式
// User.java
public User(Hello hello, String name) {
hello.setStr("hello");
this.name = name;
System.out.println(hello.toString());
}
<bean id="user" class="com.muzimu.pojo.User">
<constructor-arg name="hello" ref="hello"/>
<constructor-arg name="name" value="蜡笔小新"/>
</bean>
// MyTest.java
@Test
public void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User)context.getBean("user");
user.show();
// Hello{str='hello'}
// name=蜡笔小新
}
5.Spring配置
- ben
- alias
- beans
- description
- import
5.1 别名
alias
<alias name="user" alias="userAlias"></alias>
如果添加了别名,我们也可以使用别名获取到对象,原来的名称也依旧能获取到对象
User user = (User)context.getBean("user");
User user = (User)context.getBean("userAlias");
// 这两句都能获取到user对象
5.2 Bean的配置
bean中的关键词
-
id: bean 的唯一标识符,也就是相当于java中的对象名
-
class: bean对象所对应的全限定名,包名 + 类型
-
name: 也是别名,可以同时取多个别名
<bean id="user" class="com.muzimu.pojo.User" name="user1,user2,user3"> <!-- 给User取别名为 user1,user2,user3 三个别名 --> <!-- 可以通过 逗号,空格,分号 等分割 --> <constructor-arg name="hello" ref="hello"/> <constructor-arg name="name" value="蜡笔小新"/> </bean>
5.3 import
一般用于团队开发使用,它可以将多个配置文件,导入合并为一个
beans.xml 的正规全名为: applicationContext.xml
假设项目中有多个人开发,每个人开发的功能都自己编写一个bean文件
此时有三个bean文件:beans1.xml beans2.xml beans3.xml
最后整合项目的时候,要将所有的bean文件整合为一个总的,最后使用的时候直接使用总的配置,即:applicationContext.xml
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
如果三个文件中有重名现象,内容相同会被合并
这些只是一些最基本的配置
6.依赖注入( DI )
有三种方式
- 构造器注入
- set方式注入
- 拓展方式注入
6.1 构造器注入
在IOC创建对象方式中已经说明了
6.2 set方式注入(重点)
依赖注入的本质是 Set 注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器来注入
搭建测试环境
-
复杂类型
// Address public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } }
-
真实测试对象
// Student @Getter @Setter public class Student { private String name; private Address address; private String[] books; private List<String> hobbys; private Map<String,String> card; private Set<String> games; // 学生没有妻子,用来测试空值注入 private String wife; private Properties info; @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address.toString() + ", books=" + Arrays.toString(books) + ", hobbys=" + hobbys + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; } }
-
applicationContext.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.muzimu.pojo.Student"> <!-- 第一种,普通值注入,value --> <property name="name" value="蜡笔小新"/> </bean> </beans>
-
测试类
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student)context.getBean("student"); System.out.println(student.getName()); } }
-
完善学生类中所有数据的注入方式
<!-- applicationContext.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.muzimu.pojo.Student"> <!-- 第一种,普通值注入,value --> <property name="name" value="蜡笔小新"/> <!-- 第二种,bean注入(Address是引用类型),使用ref --> <property name="address" ref="address"/> <!-- 第三种,数组注入,使用ref --> <property name="books"> <array> <value>Java</value> <value>Spring</value> <value>Vue</value> </array> </property> <!-- List --> <property name="hobbys"> <list> <value>美食</value> <value>音乐</value> <value>电影</value> </list> </property> <!-- Map --> <property name="card"> <map> <!-- key:键 value:值 --> <entry key="卡号" value="aaa123456"/> <entry key="密码" value="12321"/> </map> </property> <!-- Set --> <property name="games"> <set> <value>英雄联盟</value> <value>COD</value> <value>守望先锋</value> </set> </property> <!-- null值注入 --> <property name="wife"> <null/> </property> <!-- Properties --> <property name="info"> <props> <prop key="学号">20170101</prop> <prop key="姓名">蜡笔小新</prop> <prop key="学院">软件学院</prop> </props> </property> </bean> <bean id="address" class="com.muzimu.pojo.Address"> <property name="address" value="春日部"/> </bean> </beans>
// MyTest public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Student student = (Student)context.getBean("student"); System.out.println(student.toString()); } } /* Student{name='蜡笔小新', address=Address{address='春日部'}, books=[Java, Spring, Vue], hobbys=[美食, 音乐, 电影], card={卡号=aaa123456, 密码=12321}, games=[英雄联盟, COD, 守望先锋], wife='null', info={姓名=蜡笔小新, 学院=软件学院, 学号=20170101}} */
6.3 拓展方式注入
6.3.1 p 命名空间
使用p命名空间需要在xml配置文件开头添加如下语句
xmlns:p="http://www.springframework.org/schema/p"
使用方式
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- p命名空间注入,可以直接注入属性的值,p就是 property 的意思 -->
<bean id="user" class="com.muzimu.pojo.User" p:name="蜡笔小新" p:age="5"/>
</beans>
@Test
public void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
User user = context.getBean("user", User.class);
System.out.println(user);
}
6.3.2 c 命名空间
使用c命名空间需要在xml配置文件开头添加如下语句
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命名空间注入,可以直接注入属性的值,p就是 property 的意思 -->
<bean id="user" class="com.muzimu.pojo.User" p:name="蜡笔小新" p:age="5"/>
<!-- c命名空间注入,通过有参构造器注入 -->
<bean id="user2" class="com.muzimu.pojo.User" c:age="5" c:name="蜡笔小新"/>
</beans>
注意:c 命名空间是通过有参构造器来注入的,所以要定义有参构造器,而定义了有参构造器之后,必须显式的定义无参构造器,不然Spring中其他语句也会出错
6.4 Bean的作用域
-
singleton:单例模式,也是默认的模式,两次创建相同的对象,那么只有一个对象
<bean id="..." class="..." scope="singleton"/> <!-- scope默认为singleton -->
@Test public void testUser(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user = context.getBean("user2", User.class); User user2 = context.getBean("user2", User.class); System.out.println(user==user2); // true }
-
prototype:原型模式,每次从容器中get的时候,都会产生一个新对象
<bean id="..." class="..." scope="prototype"/>
@Test public void testUser(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user = context.getBean("user2", User.class); User user2 = context.getBean("user2", User.class); System.out.println(user==user2); // false }
-
其余的 request、session、application 只能在web开发中使用
7.Bean的自动装配
自动装配是Spring满足bean依赖的一种方式
Spring会在上下文中自动寻找bean,并自动给bean装配属性
在Spring中有三种装配的方式
- 在XML中显式的配置
- 在java中显式的配置
- 隐式的自动装配bean(重要)
7.1 测试
搭建环境
一个人有两个宠物,每个宠物都有叫的方法
// Cat
public class Cat {
public void shout(){
System.out.println("喵喵");
}
}
// Dog
public class Dog {
public void shot(){
System.out.println("汪汪");
}
}
// People
public class People {
private Cat cat;
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
7.2 byName自动装配
原来的显式配置为:
<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="cat" class="com.muzimu.pojo.Cat"/>
<bean id="dog" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People">
<property name="name" value="蜡笔小新"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
通过 byName
<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="cat" class="com.muzimu.pojo.Cat"/>
<bean id="dog" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People" autowire="byName">
<property name="name" value="蜡笔小新"/>
</bean>
</beans>
byName 的原理:会自动在容器上下文查找,和自己对象 set 方法后面的值对应的 beanid
即:people 中有这样一个方法
public setCat(Cat cat){
this.cat = cat;
}
那么, beanid 对应的就是 set 后面的内容 Cat
如果这个方法是这样的
public setCat23333(Cat cat){
this.cat = cat;
}
那么,xml 中的 beanid 就要改成如下
<bean id="cat23333" class="com.muzimu.pojo.Cat"/>
这样,才能通过 byName 自动装配成功
7.3 byType自动装配
byType会自动在容器上下文中查找,和自己对象属性类型相同的bean
byType需要保证类型全局唯一,才能自动装配
byType可以省略id
<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="cat" class="com.muzimu.pojo.Cat"/>
<bean id="dog" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People" autowire="byType">
<property name="name" value="蜡笔小新"/>
</bean>
<!--
<bean class="com.muzimu.pojo.Cat"/>
<bean class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People" autowire="byType">
<property name="name" value="蜡笔小新"/>
</bean>
-->
</beans>
7.4 小结
在使用 byName需要保证所有 bean 的 id 唯一,并且这个bean需要和自动注入的属性的set方法的值一致
byType 的时候,需要保证所有 bean 的 class 唯一,并且这个bean需要和自动注入的属性的类型一致
7.5 使用注解实现自动装配
使用注解开发是比使用xml方便的。
使用步骤
-
导入约束。context约束
-
配置注解的支持,< context:annotation-config/ >
<?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/> </beans>
导入约束的小技巧:把对应的关键字改成想要导入约束的关键字即可,比如下面是一个最简单的bean约束
<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">
我们要使用注解约束,只需要把beans改成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">
需要注意的是,schemaLocation 中只能有一组引号
7.4.1 @Autowired 与 @Qualifier
@Autowired:直接在属性上使用即可,使用它可以将 set 方法也去掉,前提是你这个自动装配的属性在IOC容器中存在,并且符合 byType (每种类型只有一组对应的bean)
<?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/>
<bean id="cat" class="com.muzimu.pojo.Cat"/>
<bean id="dog" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People"/>
</beans>
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
@Nullable:字段标记了这个注解,说明这个字段可以为 null
public void setName(@Nullable String name){
this.name = name;
}
在@Autowired中,有一段源码是这样的
public @interface Autowired {
boolean required() default true;
}
他有一个 required 属性,默认为 true
如果显式的定义了Autowired的required属性为false,那说明这个属性可以不在bean中存在,如果注入的时候,不存在则不报错,存在则跳过
即:当你在一个属性上使用了 @Autowired 的时候,那么在xml中则必须要在bean中存在这个属性,如
<bean id="cat" class="com.muzimu.pojo.Cat"/>
如果不存在,则会报错。我们可以通过如下语句来避免报错
@Autowired( required = false )
private Cat cat
这样,在xml中不存在上方的bean语句,也不会报错
@Autowired会自动选择 byName 或者 byType 来实现,默认是使用byType,当byType不满足,才会使用byName
比如:在xml中bean的配置是这样的
<context:annotation-config/>
<bean id="cat123" class="com.muzimu.pojo.Cat"/>
<bean id="dog321" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People"/>
public class People{
@Autowired
private Cat cat;
@Autowired
private Dog dog;
}
这样也是能正常运行的。
但是,当bean中存在多个相同属性但是不同 id 的片段,则会出错,比如
<context:annotation-config/>
<bean id="cat123" class="com.muzimu.pojo.Cat"/>
<bean id="cat111" class="com.muzimu.pojo.Cat"/>
<bean id="dog321" class="com.muzimu.pojo.Dog"/>
<bean id="dog333" class="com.muzimu.pojo.Dog"/>
<bean id="people" class="com.muzimu.pojo.People"/>
这个时候,可以通过 @Autowired 和 @Qualifier 搭配使用来解决问题
public class People{
@Autowired
@Qualifier( value = "cat123" )
private Cat cat;
@Autowired
@Qualifier( value = "dog333" )
private Dog dog;
}
7.4.2 @Resource
通过@Resource 实现自动装配,使用它需要导入依赖
<!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
@Resoutce 是java的注解
public class People{
@Resource
private Cat cat;
@Resource
private Dog dog;
}
@Resource 会根据名字和类型来寻找,如果 bean 中存在多个不同id 但是同类型的语句,会使用 byName方式,如果每种类型都只有一个,则不审查名字,直接使用该配置,默认使用 byName 方式
当然,@Resource 也可以通过名字来装配
public class People{
@Resource( name = "cat123" )
private Cat cat;
@Resource( name = "dog333" )
private Dog dog;
}
@Rsource 的功能要强大一些,但是实际开发中一般都是使用 @Autowired
7.4.3 小结
@Resource 和 @Autowired 的相同点和不同点
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired 默认使用 byType 方式进行装配,不满足则使用 byName 方式,而且必须要求这个对象在bean中存在
- @Resource 默认使用 byName 方式进行装配,不满足则使用 byName 方式,两个都不满足则报错,并且也要求使用了@Resource的对象在bean中存在
- 他们的默认执行顺序是不同的
8.使用注解开发
在Spring4之后,要使用注解开发,必须要保证AOP的包导入了
使用注解需要导入context约束,增加注解支持
扫描指定包下的注解,使其生效
<!-- 扫描指定的包,这个包下的注解就会生效 -->
<context:component-scan base-package="com.muzimu.pojo"/>
-
bean
// 等价于 <bean id="user" class="com.muzimu.pojo.User"/> // @Component 组件 @Component public class User { public String name = "蜡笔小新"; }
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = context.getBean("user", User.class); System.out.println(user.name); }
-
属性注入
@Component public class User { // 相当于 <property name="name" value="蜡笔小新"/> @Value("蜡笔小新") public String name; }
-
衍生的注解
@Component 有几个衍生注解,在web开发中,会按照MVC三层架构分层
- dao :@Repository
- service :@Service
- controller :@Controller
@Component,@Repository,@Service,@Controller 这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean,只是在对应的层中一般用对应的注解名
-
自动装配
@Autowired :自动装配,先通过 byType,再使用 byName 如果@Autowired想要装配指定的对象,就要配合 @Qualifier( value = "XXX" ) @Nullable :字段添加了这个注解表示这个字段可以为空 @Resource : 自动装配,先通过 byName,再使用 byType
-
作用域
@Scope( “XXX” )
-
小结
XML与注解:
- xml 是万能的,适用于任何地方,维护也更加简单方便
- 注解只能使用自己的类,不能使用别的类,维护非常复杂
XML与注解的最佳实践
- xml 用来管理 bean
- 注解只负责完成属性的注入
使用注解一定要记得开启注解的支持
<context:annotation-config/>
9.使用Java的方式配置Spring
不适用Spring的xml配置,只使用java来完成
JavaConfig是Spring的一个子项目,在Spring4之后,他成为了一个核心功能
搭建环境:
- 编写实体类
- 编写配置类 XXXConfig
- 进行测试
测试结构如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YIIM5gms-1607930576842)(D:\专业课相关资料\java\图片\Spring\Config配置环境.png)]
// User.java
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("蜡笔小新")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
// MyConfig.java
// @Configuration 代表这是一个配置类,和beans.xml是一样的
// Configuration 本质上也是一个 Component
// 本身就是一个配置,也会被Spring容器托管
@Configuration
// 扫描包
@ComponentScan("com.muzimu.pojo")
// 相当于整合多个bean文件时的 import
@Import(MyConfig2.class)
public class MyConfig {
// 注册一个bean,相当于之前的bean标签
// 这个方法的名字,就相当于bean标签中的id属性
// 这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User getUser(){
// 就是返回要注入到bean的对象
return new User();
}
}
// MyTest.java
public static void main(String[] args) {
// 如果完全使用了配置类方式去做,我们就只能通过AnnotationConfigApplicationContext来获取容器,通过配置类的class来加载
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}
在SpringBoot中,这种纯java的配置方式随处可见
10.代理模式
代理模式就是 SpringAOP 的底层,SpringAOP 与 SpringMVC 是面试必问
代理模式的分类
- 静态代理
- 动态代理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aB61xegY-1607930576845)(D:\专业课相关资料\java\图片\Spring\代理模式.png)]
10.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理后,一般会做一些附属操作(比如中介收手续费)
- 客户:访问代理对象的人
例子:租房的人通过中介来租房,房东通过中介出租房子
// RentHouse.java
// 租房
public interface RentHouse {
void rent();
}
// HouseOwner.java
// 房东,真实角色
public class HouseOwner implements RentHouse{
public void rent() {
System.out.println("我要出租这套房子");
}
}
// Proxy.java
// 中介
// 代理角色
public class Proxy implements RentHouse {
private HouseOwner houseOwner;
public Proxy() {
}
public Proxy(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public void rent() {
// 先看房
seeHouse();
// 签合同
contract();
// 收中介费
fee();
// 房东
houseOwner.rent();
}
// 中介带看房
public void seeHouse() {
System.out.println("中介带你寻找满意的房子");
}
// 收中介费
public void fee(){
System.out.println("我要收取应交的中介费");
}
// 签合同
public void contract(){
System.out.println("签订租房合同");
}
}
// You.java
// 租房子的人,真实角色
public class You {
public static void main(String[] args) {
// 房东要出租房子
HouseOwner houseOwner = new HouseOwner();
// 中介帮房东出租房子
Proxy proxy = new Proxy(houseOwner);
// 中介有一些属于自己的操作
// 代理角色可以拥有自己的附属操作
proxy.rent();
// 你并没有接触房东,只是接触了中介
}
}
代理模式的好处:
- 使真实角色的操作更加简单,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
缺点
- 一个真实角色会产生一个代理角色,代码量会翻倍,开发效率变低
AOP的实现机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VyBDX6N0-1607930576848)(D:\专业课相关资料\java\图片\Spring\AOP的实现机制.png)]
10.2 动态代理
- 动态代理的底层都是反射
- 动态代理和静态代理的角色是一样的(抽象角色,真实角色,代理角色)
- 动态代理的代理类是动态生成的,不是由我们编写的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口:JDK动态代理
- 基于类:cglib
- java字节码实现:javassist
动态代理需要了解两个类:Proxy(代理) 、 invocationHanlder(调用处理程序并返回一个结果)
测试
// RentHouse.java
// 租房
public interface RentHouse {
void rent();
}
// HouseOwner.java
// 房东,真实角色
public class HouseOwner implements RentHouse{
public void rent() {
System.out.println("我要出租这套房子");
}
}
// ProxyInvocationHandler.java
// 代理调用处理程序
// 用这个类自动生成代理类,执行真正要执行的方法
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
// 这里限制死了接口,可以通过 private Object object 来不限制类型
private RentHouse rentHouse;
public void setRentHouse(RentHouse rentHouse) {
this.rentHouse = rentHouse;
}
// 生成代理对象
public Object getProxy() {
// 这句代码是固定的,只需要修改 rentHouse 为自己想要生成对应代理对象
// 第一个参数表示类加载器
// 第二个参数表示要代理的接口是哪个接口
// 第三个参数表示自己这个InvocationHandler
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rentHouse.getClass().getInterfaces(), this);
}
// 处理代理实例并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质就是使用反射机制实现
seeHouse();
Object result = method.invoke(rentHouse, args);
contract();
fee();
return result;
}
// 附加操作
// 看房
public void seeHouse() {
System.out.println("中介带你看房");
}
// 收中介费
public void fee() {
System.out.println("我要收取应交的中介费");
}
// 签合同
public void contract() {
System.out.println("签订租房合同");
}
}
// You.java
public class You {
public static void main(String[] args) {
// 真实角色
HouseOwner houseOwner = new HouseOwner();
// 代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 通过调用程序处理角色来处理我们要调用的接口对象即可
pih.setRentHouse(houseOwner);
// 这里就是动态生成 proxy 代理类,我们并没有编写代理类
RentHouse proxy = (RentHouse)pih.getProxy();
// 这里其实就自动调用了 invoke 方法
proxy.rent();
}
}
我们并没有再去创建代理类,而是通过Proxy和InvocationHanlder来动态生成代理类
可以在invoke方法中通过 method 来获得当前执行方法的信息,比如 proxy.rent(); 那么method就是 rent 方法
查看源码分析动态代理过程(为什么会自动调用 invoke 方法, 在哪里调用了 invoke 方法呢):
Proxy.newProxyInstance(ClassLoader loader, Class<?>[ ] interfaces, InvocationHandler h)做了以下几件事.
-
根据我们传入的类加载器和接口,动态生成了指定的 $Proxy0 代理类
Class<?> cl = getProxyClass0(loader, intfs);
-
获得$Proxy0 的构造方法,传入参数类型
final Constructor<?> cons = cl.getConstructor(constructorParams); // 其中的 constructorParams是参数类型, 表示 InvocationHandler 对象
private static final Class<?>[] constructorParams = { InvocationHandler.class }; // 这里返回了 $Proxy0 的构造器,参数是 InvocationHandler
-
根据构造方法返回代理对象
return cons.newInstance(new Object[]{h}); // 通过反射返回 $Proxy0 的实例,即代理对象,从而 Invocationhandler 实例从代理类赋值到父类 // 其中的 h 就是在调用 newProxyInstance 函数的时候传入的 第三个参数
我们可以通过在主函数中添加
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
来生成代理对象类的文件$Proxy0.class
import com.example.springboot.codedemo.JDKDynamicProxyDemo.Iservice;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
final class $Proxy0 extends Proxy implements Iservice {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
// 构造函数的参数为InvocationHandler 类实例,但是实际上的构造方法为父类Proxy 的构造方法
/*
Proxy 对应的构造方法
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
*/
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
// 与我们定义的接口中相同方法全名的方法,在这里就调用了 invoke 方法
public final void rent() throws {
try {
// 调用父类的 InvocationHandler 类实例 h
// 再调用 invoke 方法
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.example.springboot.codedemo.JDKDynamicProxyDemo$Iservice").getMethod("sayHello");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
万能动态代理
public class ProxyInvocationHandler implements InvocationHandler {
private Object object;
public void setObject(Object object) {
this.object = object;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), object.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(object, args);
return result;
}
}
动态代理的好处
- 使真实角色的操作更加简单,不用去关注一些公共的业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展的时候,方便集中管理
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
11.AOP
AOP(Aspect Oriented Programming),即:面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续 ,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
11.1 Aop在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点,比如:日志,安全,缓存,事务等等
- 切面(ASPECT):横切关注点被模块化的特殊对象,它是一个类
- 通知(Advice):切面必须要完成的工作,它是类中的一个方法
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut):切面通知执行的“地点”的定义
- 连接点(JointPoint):与切入点匹配的执行点
在SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | org.springframework.aop.MethodBeforeAdvice |
后置通知 | 方法后 | org.springframework.aop.AfterReturningAdvice |
环绕通知 | 方法前后 | org.aopalliance.intercept.MethodInterceptor |
异常抛出通知 | 方法抛出异常 | org.springframework.aop.ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | org.springframework.aop.IntroductionInterceptor |
AOP在不改变原有代码的情况下,去增加新的功能
11.2 Spring实现AOP
使用AOP织入,需要导入一个依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
11.2.1 方式一:使用Spring的API接口
项目结构如下
// UserService.java
public interface UserService {
void add();
void delete();
void update();
void query();
}
// UserServiceImpl.java
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void query() {
System.out.println("查询了一个用户");
}
}
// Log.java
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
// method:要执行的目标对象的方法
// args:参数
// target:目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
// AfterLog.java
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为:" + returnValue);
}
}
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns: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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
<bean id="userService" class="com.muzimu.service.UserServiceImpl"/>
<bean id="log" class="com.muzimu.log.Log"/>
<bean id="afterLog" class="com.muzimu.log.AfterLog"/>
<!-- 方式一:使用原生的API接口 -->
<!-- 配置aop,需要导入aop的约束 -->
<aop:config>
<!-- 切入点,expression是表达式,一定要写一个 execution() -->
<!-- execution(要执行的位置) -->
<aop:pointcut id="pointcut" expression="execution(* com.muzimu.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增强 -->
<!-- advisor:环绕 advice-ref:advice引用自哪里 pointcut-ref:引用哪个切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
// MyTest.java
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
/* com.muzimu.service.UserServiceImpl的add被执行了
增加了一个用户
执行了add方法,返回结果为:null */
}
}
注意点:
-
xml中的 aop:config 中的编写
-
表达式中一定要有 execution(),括号中是 修饰词 返回值 类名 方法名 参数,* 代表任意
-
* com.muzimu.service.UserServiceImpl.*(..)) /* 第一个 * 代表任意 com.muzimu.service.UserServiceImpl 代表UserServiceImpl类 UserServiceImpl.* 代表该类下面的所有方法 (..) 代表任意个参数 */
-
-
在测试类中,一定要注意动态代理的是接口,所以 getBean 的时候虽然bean中代表的是 Impl 实现类,所以返回类型应该是接口类型
11.2.2 方式二:自定义类实现AOP
这种方式主要点在于切面的定义,也是比较推荐使用的方式
自定义一个类,放在 diy 包下
// DiyPointCut.java
public class DiyPointCut {
public void methodBeforeAdvice(){
System.out.println("=============方法执行前===============");
}
public void afterRunningAdvice(){
System.out.println("=============方法执行后===============");
}
}
在xml文件中进行配置
<bean id="userService" class="com.muzimu.service.UserServiceImpl"/>
<bean id="log" class="com.muzimu.log.Log"/>
<bean id="afterLog" class="com.muzimu.log.AfterLog"/>
<!-- 方式二:自定义类 -->
<bean id="diy" class="com.muzimu.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面,ref为要引用的类 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.muzimu.service.UserServiceImpl.* (..))"/>
<!-- 通知 -->
<aop:before method="methodBeforeAdvice" pointcut-ref="point"/>
<aop:after-returning method="afterRunningAdvice" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
进行测试,测试类不变,测试结果如下
/*
=============方法执行前===============
查询了一个用户
=============方法执行后===============
*/
11.2.3 方式三:注解实现AOP
使用注解实现AOP要导入依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
<!-- http://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.6</version>
</dependency>
在diy包下编写一个使用注解的类
// AnnotataionPointCut.java
// 使用注解方式实现AOP
// 标注这个类是一个切面
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.muzimu.service.UserServiceImpl.* (..))")
public void methodBeforeAdvice(){
System.out.println("==========方法执行前==========");
}
@AfterReturning("execution(* com.muzimu.service.UserServiceImpl.* (..))")
public void methodAfterRetutnning(){
System.out.println("==========方法执行后==========");
}
@Around("execution(* com.muzimu.service.UserServiceImpl.* (..))")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
// 获得签名
Signature signature = pjp.getSignature();
System.out.println(signature);
// 执行方法
Object proceed = pjp.proceed();
System.out.println("环绕后");
/*
需要关注的就三句
System.out.println("环绕前");
// 执行方法
Object proceed = pjp.proceed();
System.out.println("环绕后");
*/
}
}
<bean id="userService" class="com.muzimu.service.UserServiceImpl"/>
<bean id="log" class="com.muzimu.log.Log"/>
<bean id="afterLog" class="com.muzimu.log.AfterLog"/>
<bean id="annotationPointCut" class="com.muzimu.diy.AnnotationPointCut"/>
<!-- 开启注解支持 -->
<aop:aspectj-autoproxy/>
其他类不进行变动,测试输出结果如下
/*
环绕前
void com.muzimu.service.UserService.query()
==========方法执行前==========
查询了一个用户
==========方法执行后==========
环绕后
*/
代理模式有基于接口(JDK动态代理)和基于类(cglib)两种,默认是使用JDK实现的
<aop:aspectj-autoproxy/>
<!-- 他有如下属性 -->
<aop:aspectj-autoproxy proxy-target-class="false"/>
<!-- false 是默认的,表示使用 JDK,如果设置为 true ,则会使用 cglib -->
12.整合Mybatis
步骤:
- 导入相关依赖
- 编写配置文件
- 测试
需要使用的依赖有:
- junit
- mysql数据库
- mybatis
- spring相关
- aop织入
- mybatis-config【重点】,官网:https://mybatis.org/spring/index.html
Spring操作数据库还要使用一个包,spring-jdbc
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!-- mybatis-config -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
12.1 回忆mybatis
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写Mapper.xml
- 测试
12.2 MyBatis-Spring
使用它需要导入依赖
<!-- mybatis-config -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
- 编写数据源配置
- sqlSessionFactory
- sqlSessionTemplate
- 编写接口的实现类【重要!】
- 将编写的实现类注入到spring
- 测试
SqlSessionTemplate是 mybatis-spring 的一个核心,使用它可以无缝代替以前的sqlSession,并且它是线程安全的
项目结构如下
先编写User实体类,再编写接口
// User.java
public class User {
private int id;
private String name;
private String pwd;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
// UserMapper.java
public interface UserMapper {
User getUser(@Param("id") int id);
}
编写Mapper.xml文件
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.muzimu.mapper.UserMapper">
<select id="getUser" resultType="user">
select * from mybatis.user where id = #{id};
</select>
<select id="getAllUser" resultType="user">
select * from user;
</select>
</mapper>
在以往用mybatis实现中,我们需要编写mybatis核心配置文件,mapper.xml文件以及utils中的sqlSessionFactory工具类,现在我们可以舍弃许多文件了,只在spring核心配置文件中编写即可
编写mybatis-config.xml 核心配置文件,以及 applicationContext.xml 核心配置文件
<!-- mybatis-config.xml -->
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.muzimu.pojo"/>
</typeAliases>
</configuration>
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:annotation-config/>
<!-- DataSource:使用spring的数据源替换mybatis的配置。也可以用 c3p0 dbcp druid
这里使用spring提供的jdbc:org.springframework.jdbc.datasource.DriverManagerDataSource
使用它需要导入 spring-jdbc 依赖
在这里配置了数据源,mybatis-config 核心配置文件中则不需要再进行配置了
-->
<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/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 绑定mybatis配置文件
可以在这里设置任何需要在 mybatis-config 中设置的内容
一般就在核心配置文件中留下别名和设置
-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/muzimu/mapper/*.xml"/>
</bean>
<!-- SqlSessionTemplate 就是我们使用的 sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入 sqlSessionFactory ,因为他没有set方法 -->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="com.muzimu.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
再编写UserMapperImpl实现类
// UserMapperImpl.java
public class UserMapperImpl implements UserMapper{
// 以前的所有操作都是使用 sqlS|ession 来执行,现在使用 sqlSessionTemplate,实际上是一个东西
private SqlSessionTemplate sqlSession;
// 一定要有一个set方法来注入
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(int id) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUser(1);
}
}
最后进行测试
// MyTest.java
@Test
public void getUser() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
System.out.println(userMapper.getUser(1));
}
注意点:我们可以看出,mybatis-config 核心配置文件已经几乎没有内容了,那是因为我们在 applicationContext.xml 核心文件中,配置了 mybatis-config.xml 需要配置的信息。
在 mybatis-spring 中,我们使用 SqlSessionFactoryBean 来创建 SqlSessionFactory ,而使用 SqlSessionFactoryBean 则需要一个 DataSource 数据源,在这里我们使用了 spring提供的 jdbc 数据源,这个数据源完成了原来 mybatis-config 中配置数据库的操作,使用它需要导入 spring-jdbc 依赖
<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/mybatis?serverTimezone=Asia/Shanghai&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
使用 SqlSessionFactoryBean 来创建 SqlSessionFactory
<!-- sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- ref 引用数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 绑定mybatis配置文件
可以在这里设置任何需要在 mybatis-config 中设置的内容
一般就在核心配置文件中留下别名和设置
-->
<!-- 绑定 mybatis-config.xml 的地址 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 注册映射器 -->
<property name="mapperLocations" value="classpath:com/muzimu/mapper/*.xml"/>
</bean>
绑定 mybatis-config 配置文件就可以在spring配置mybatis中所有的信息了,一般我们只在 mybatis-config 留下 typeAliases 和 Settiings 两个配置
创建了SqlSessionFactory之后,就可以创建sqlSession了。使用 SqlSessionTemplate 来创建 SqlSession,Template表示模板
<!-- SqlSessionTemplate 就是我们使用的 sqlSession -->
<!-- 按照我们的使用习惯,命名为 sqlSession ,官方建议命名为 sqlSessionTemplate-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入 sqlSessionFactory ,因为他没有set方法 -->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
编写实现类,虽然多一个类比较麻烦,但是也有它的好处。因为用了Spring之后,不能直接用Mapper.class,所以要编写一个实现类,实现类中把 sqlSession私有,然后通过set方法注入。
编写完实现类之后,在spring中注入
<bean id="userMapper" class="com.muzimu.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
最后进行测试
@Test
public void getUser() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 调用实现类进行实现
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
System.out.println(userMapper.getUser(1));
}
12.3 SqlSessionDaoSupport
这种操作更为方便,了解即可
它相比较上面这种方法,需要继承一个类 SqlSessionDaoSupport ,通过它的getSession方法来获取SqlSession。
编写实现类
// UserMapperImpl2.java
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public User getUser(int id) {
return getSqlSession().getMapper(UserMapper.class).getUser(1);
}
public List<User> getAllUser() {
return getSqlSession().getMapper(UserMapper.class).getAllUser();
}
}
在Spring中注册
<bean id="userMapper2" class="com.muzimu.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
可以看出,它和第一种方式相比,不再需要SqlSession,而只需要SqlSessionFactory
13.声明式事务
13.1 回顾事务
事务的特性
- 要么都成功,要么都失败
- 事务在项目开发中十分重要,涉及到数据的一致性问题
- 确保数据的完整性和一致性
事务的ACID原则:
- A:原子性,整个事务中的所有操作,要么全部完成,要么全部不完成
- C:一致性,事务前后数据的完整性必须保持一致
- I:隔离性,多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- D:持久性,一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
13.2 测试事务
搭建环境,进行事务测试,只使用user类来测试。
在接口中声明 查询所有用户,增加一个用户,删除一个用户 三个操作
// UserMapper.java
public interface UserMapper {
List<User> getAllUser();
boolean addUser(User user);
boolean deleteUser(@Param("id") int id);
}
编写Mapper.xml文件,注意:此处故意写错删除语句,用来进行测试
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.muzimu.mapper.UserMapper">
<select id="getAllUser" resultType="user">
select * from user;
</select>
<insert id="addUser" parameterType="user">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser">
<!-- 把 delete 写成 deletes ,让这句语句错误 -->
deletes from user where id = #{id}
</delete>
</mapper>
在实现类里的查询方法中,进行一组事务
public List<User> getAllUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User(5, "樱桃小丸子", "1231231");
mapper.addUser(user);
mapper.deleteUser(1);
System.out.println("操作完成");
return mapper.getAllUser() ;
}
可以看到,在查询方法前,我们先增加了一个用户,然后删除了一个用户。由于删除用户的sql语句是错误的,那么它会怎么样运行呢?
进行测试
// MyTest.java
@Test
public void getAllUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapper", UserMapper.class);
for (User user : mapper.getAllUser()) {
System.out.println(user);
}
}
在程序运行时,出现错误停止运行,但是,数据成功的插入进了数据库,这很显然不满足原子性,是错误的!
13.3 Spring中的事务管理
Spring中的事务管理分为两类
- 声明式事务:AOP的应用,代码是横切进去的,不影响原来的代码(最常使用方式)
- 编程式事务:需要在代码中,进行事务的管理
使用声明式事务:
要开启Spring的事务处理功能,要在Spring的配置文件中创建一个 DataSourceTransactionManager 对象
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource"/>
<!-- 或者使用如下方式 -->
<!-- <property name="dataSource" ref="dataSource"> -->
</bean>
开启Spring事务处理之后,编写配置文件,结合AOP实现事务的织入
<!-- 结合AOP实现事务的织入 -->
<!-- 步骤一:配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务 -->
<!-- 配置事务的传播特性:propagation,默认的是 REQUIRED -->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<!-- 表示query开头的方法,都是只读的,不能对数据库进行增删改 -->
<tx:method name="query" read-only="true"/>
<!-- 表示所有方法,一般只设置这一个即可,上面是为了参考 -->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 步骤二:配置事务切入 -->
<aop:config>
<!-- 表示mapper包下的所有方法都会被编织上 txAdvice 这个事务 -->
<aop:pointcut id="txPointCut" expression="execution( * com.muzimu.mapper.*.*(..))"/>
<!-- 切入 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
其中,使用 tx 需要导入约束
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
Spring中的七个事务传播特性
- 1.PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- 2.PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
- 3.PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
- 4.PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
- 5.PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- 6.PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
- 7.PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
在实现事务织入的时候,分为了两步实现,第一步是配置事务通知,第二步是配置事务切入。
配置完之后,依旧去运行原来的测试类,发现报错之后,不会在出现插入成功的情况了。
为什么需要事务:
- 如果不配置事务,可能存在数据提交不一致的情况
- 除了在Spring中配置事务,我们也可以在代码中手动配置事务
- 事务在项目的开发中十分重要,涉及到数据安全