SSM框架:Spring
文章目录
前言
狂神说
一、Spring
1. 简介
- Spring:春天------>给软件行业带来了春天!
- 2002,首次推出了Spring框架的雏形:interface21框架!
- 2004年3月24号,诞生:Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵,于2004年3月24日发布了1.0正式版。
- Rod Johnson,Spring Framework创始人,著名作者。很难想象Rod Johnson的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!【简化服务器的开发】
- SSH:Struct2 + Spring + Hibernate!
- SSM:SpringMVC + Spring + Mybatis! 推荐
- (Hibernate全自动框架,Mybatis半自动)
官网:https://spring.io/projects/spring-framework#overview
官方下载地址:https://repo.spring.io/release/org/springframework/spring/
GitHub:https://github.com/spring-projects/spring-framework
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
https://docs.spring.io/spring-framework/docs/
https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#spring-core
2. 优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的框架 , 非侵入式的
- 控制反转(IOC),面向切面(AOP)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个 轻量级 的 控制反转(IOC) 和 面向切面编程(AOP) 的框架!
3. 组成
Spring有七大功能模块:
1、Spring Core
Core模块是Spring的核心容器,它采用工厂模式实现了IoC模式容器(即依赖注入),提供了Spring框架的基础功能。主要组件是BeanFactory,负责对JavaBean的配置与管理。
2、Context模块
继承BeanFactory类,向框架提供上下文信息。提供了事件处理、国际化、资源装载、透明装载、数据校验等功能,还有其它企业服务功能如JNDI访问、支持EJB、远程调用、集成模板框架、E-mail、定时任务调度等功能。
其它程序可通过Context访问Spring的Bean资源(相当于资源注入)
3、AOP模块
集成了所有AOP功能,通过事务管理可使任何Spring管理的对象AOP化。提供了常用的拦截器,供用户自定义和配置。
Aspect Oriented Programming:面向切面(方面)编程
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等。
4、DAO模块
提供了JDBC的抽象层,提供了有意义的异常层次结构,简化了数据库厂商的异常错误处理,大幅减少代码量,提供对声明式和编程式事务的支持。
5、ORM映射模块
提供对现有ORM框架的管理和支持,本身并不对ORM进行实现,仅对常见ORM框架进行封装并进行管理。
6、Web模块
建立在Spring Context基础上,提供了Servlet监听器的Context和Web应用的上下文。
对现有的Web框架提供了集成管理,能将Spring的资源注入给这些框架,并在这些框架前后插入拦截器。
简化了处理多部分请求,以及将请求参数绑定到域对象的工作。
7、MVC模块
建立在Spring核心功能上,拥有Spring框架的所有特性,适应多种模板技术,多视图,国际化和验证服务,实现控制逻辑和业务逻辑的清晰分离。是一个全功能的构建Web应用的MVC实现。
通过策略接口,MVC框架变得高度可配置,容纳了大量视图技术,包括JSP,Velocity,Tiles,iText和POI等。
Spring三大核心思想:
1、控制反转
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法
2、依赖注入
(常用的注入方式有四种:
1.属性注入
2.构造方法注入
3.工厂方法注入
4.注解注入
3、面向切面编程
Spring最核心,最基础的概念是什么?
将spring类比java,java最核心,最基础的概念就是object了。java中,所有的操作都是针对object的(基础类型除外),java中,一切皆对象,一切都是object。类比下来,spring中最基础的概念是bean。在spring中,所以的类都可以认为是一个bean。
在spring中的所有文件,都可以认为是注册了的bean和未注册的bean。 spring中所有操作都是针对bean的操作。自然,spring的三大核心思想中操作的对象,也是bean
4. 拓展
在Spring的官网有这个介绍:现代化的Java开发!说白了就是基于Spring的开发
(学习思路)
构建 --> 协调 --> 连接
Spring Boot 构建
Spring Cloud 协调
Spring Cloud Data Flow 连接
- Spring Boot
- 一个快速开发的脚手架。
- 基于SpringBoot可以快速的开发单个微服务。
- 约定大于配置。
- Spring Cloud
- SpringCloud是基于SpringBoot实现的。
因为现在大多数公司都在使用 SpringBoot 进行快速开发,学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC !承上启下的作用!
弊端:发展了太久之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱!”
二、IOC理论推导
1. IOC原型引入
1.UserDao 接口
2.UserDaoImpl 实现类
3.UserService 业务接口
4.UserServiceImpl 业务实现类
新建一个项目
导包
删除src目录
检查导包是否成功
1、先写一个UserDao接口
public interface UserDao {
public void getUser();
}
2、再去写Dao的实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
3、然后去写UserService的接口
public interface UserService {
public void getUser();
}
4、最后写Service的实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
(继承加组合的概念)
5、测试一下
public static void main(String[] args) {
UserService service = new UserServiceImpl();
service.getUser();
}
6、当用户需求增加或修改
7、把Userdao的实现类增加一个
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
8、去service实现类里面修改对应的实现
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
9、用户在增加需求
10、再增加一个Userdao的实现类
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Oracle获取用户数据");
}
}
要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了.每次变动 , 都需要修改大量代码 . 这种设计的耦合性太高了, 牵一发而动全身 .
缺点:程序受不了用户的变更,修改时需要不断修改太多代码(数据库操作层 --> 业务层)
12、解决方法:
利用set进行动态实现值的注入!
从 由我们手动修改业务层 到 由客户动态选择操作
不用改源码,解耦合
在需要用到它的地方 , 不去实现它 , 而是留出一个接口 , 利用set进行动态实现值的注入
13、修改UserService实现类
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现(留出一个接口)
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
14、测试类里 , 进行测试
public static void main(String[] args) {
UserServiceImpl service = new UserServiceImpl();
service.setUserDao( new UserDaoMySqlImpl() );
service.getUser();
//那我们现在又想用Oracle去实现呢
service.setUserDao( new UserDaoOracleImpl() );
service.getUser();
}
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
之前:主动权在程序员(程序是主动创建对象!控制权在程序员手上)
使用set注入后,程序不再具有主动性,而是变成了被动的接受对象
现在:主动权在用户
我们使用一个Set接口实现,已经发生了革命性的变化!
核心代码:
private UserDao userDao;
//利用set进行动态实现值的注入!
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上!这是IOC的原型!【控制反转】
2. IOC本质(基本思想)
之前:主动权在程序员
现在:主动权在用户
程序员:提供者;用户:调用者
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。
三、HelloSpring(Spring的应用)
1、新建一个maven项目,编写实体类
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 + '\'' +
'}';
}
}
2、编写xml配置文件 beans.xml(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">
<!--使用Spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名
class = new的对象
property 相当于给对象中的属性设置一个值!
-->
<bean id="hello" class="com.qia.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
3、测试
import com.qia.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//获取Spring的上下文对象! //ClassPathXmlApplicationContext,用xml加载时必写的,可以传多个配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们需要使用,直接去里面取出来就可以!
Hello hello = (Hello) context.getBean("hello");//原来用new取对象,现在context.getBean可以取一切对象
System.out.println(hello.toString());
}
}
思考问题?
- Hello对象是谁创建的?
Hello对象是由Spring创建的。 - Hello对象的属性是怎么设置的?
Hello对象的属性是由Spring容器设置的。
这个过程就叫控制反转:
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
反转:程序本身不创建对象,而变成被动的接收对象。
依赖注入:就是利用set方法来进行注入的。
IOC是一种编程思想,由主动的编程变成被动的接收。
可以通过new ClassPathXmlApplicationContext去浏览一下底层源码。
OK,到了现在,我们彻底不用在程序中去改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象由Spring来创建,管理,装配!
配置后出现左侧小叶子,点击跳转beans.xml里的bean容器(实例化容器)
Spring代码 不断继承
所以ClassPathXmlApplicationContext类路径加载xml只是继承他的某一个类,也说明了DI依赖并不直接等于IOC,他也是继承的其中之一的一种方式
举个例子:
原来是,你来餐厅吃饭,点菜单上的菜(菜单上的菜:固定好的,只能吃这些)
现在是,你来餐厅吃饭,你告诉餐厅你要吃什么菜(由用户选择)
主动权置换
修改项目1试试
1、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在容器初始时创建,随用随拿 -->
<bean id="mysqlImpl" class="com.qia.dao.UserDaoMysqlImpl"/>
<bean id="oracleImpl" class="com.qia.dao.UserDaoOracleImpl"/>
<bean id="sqlserverImpl" class="com.qia.dao.UserDaoSqlserverImpl"/>
<bean id="UserServiceImpl" class="com.qia.service.UserServiceImpl">
<!-- ref:引用Spring容器中创建好的对象 -->
<!-- value:具体的值,基本数据类型和String -->
<property name="userDao" ref="mysqlImpl"/>
</bean>
</beans>
从改程序变成了改配置,改配置比该程序好,因为程序之间有调用关系改起来很麻烦
且配置不需要编译,改了就能用,源码需要编译才能运行
四、IOC创建对象的方式
定义了有参构造,没定义无参构造
没有无参构造,底层反射就不好直接new对象了,所以会报错
https://docs.spring.io/spring-framework/docs/
https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html#spring-core
1、使用无参构造创建对象,默认!
2、假设我们要使用有参构造创建对象。
1.下标赋值
2.类型
3.参数名
<?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">
<!-- 这种情况下依旧走的是无参构造,name能赋值为兔兔,但并没有走有参构造 -->
<!-- <bean id="user" class="com.qia.pojo.User">-->
<!-- <property name="name" value="兔兔"/>-->
<!-- </bean>-->
<!-- 第一种方式:下标赋值 走了有参构造,没走无参构造 -->
<!-- <bean id="user" class="com.qia.pojo.User">-->
<!-- <constructor-arg index="0" value="狂神说Java"/>-->
<!-- </bean>-->
<!-- 第二种方式:通过类型的创建,不建议使用
(当多个属性都是类型相同或且数量类型一致的构造函数,将不能被重载)
eg. public User(String name,String age)
public User(String address,String email)
构造器中的参数如果是有多个类型相同的,会按照xml从上到下的顺序来依次赋值
基本类型可以直接用,引用类型必须完全限定名
走了有参构造,没走无参构造 -->
<bean id="user" class="com.qia.pojo.User">
<constructor-arg type="java.lang.String" value="tutu"/>
<!-- 试试在实体类中添加属性age,数据类型设置int或String进行测试 -->
<!-- <constructor-arg type="java.lang.String" value="3"/>-->
<!-- <constructor-arg type="int" value="3"/>-->
</bean>
<!-- 第三种方式:直接通过参数名来设置 -->
<bean id="user" class="com.qia.pojo.User">
<constructor-arg name="name" value="李发"/>
</bean>
</beans>
import com.qia.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//以前
// User user = new User();//User的无参构造
//对比
//现在
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");//User的无参构造//获取spring上下文的时候,所有的对象在容器中就已经创建好了
user.show();
// System.out.println(user);
}
}
写一个UserTwo实体类进行测试
package com.qia.pojo;
public class UserTwo {
private String name;
public UserTwo() {
System.out.println("User2的无参构造");
}
public UserTwo(String name) {
this.name = name;
System.out.println("User2的有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name="+name);
}
}
<?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="user" class="com.qia.pojo.User">
<constructor-arg name="name" value="狂神"/>
</bean>
<bean id="userTwo" class="com.qia.pojo.UserTwo">
<constructor-arg name="name" value="狂神2"/>
</bean>
</beans>
import com.qia.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");//User的无参构造//获取spring上下文的时候,所有的对象在容器中就已经创建好了
user.show();
// System.out.println(user);
/*
bean容器放了UserTwo后,程序执行结果变成了
User的有参构造
User2的有参构造
name=狂神
所以,在获取ApplicationContext对象,即获取上下文环境时,Spring容器中的所有对象都会被创建
*/
System.out.println("==========");
User user2 = (User) context.getBean("user");
System.out.println(user == user2);//true,同一个类只会被加载一次 //scope=prototype就是false了
}
}
总结:在配置文件加载的时候,容器中管理的对象就已经初始化了!
五、Spring配置说明
bean
alias
beans
description
import
1、别名 alias
方式一
<!--别名,如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="user" alias="userAlias"/>
import com.qia.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// User user = (User) context.getBean("user");
User user = (User) context.getBean("userAlias");
user.show();
}
}
方式二
<?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">
<!--
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的全限定名:包名+类名
name:也是别名,而且name可以同时取多个别名 空格,逗号,分号,隔开的别名都可以使用
-->
<bean id="userTwo" class="com.qia.pojo.UserTwo" name="user2 u2,u3;u4">
<property name="name" value="狂神"/>
</bean>
</beans>
import com.qia.pojo.User;
import com.qia.pojo.UserTwo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// UserTwo user2 = (UserTwo) context.getBean("userTwo");
// UserTwo user2 = (UserTwo) context.getBean("user2");
// UserTwo user2 = (UserTwo) context.getBean("u2");
// UserTwo user2 = (UserTwo) context.getBean("u3");
UserTwo user2 = (UserTwo) context.getBean("u4");
user2.show();
//以上别名都可以使用,程序都能正常运行
}
}
2、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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean对象所对应的全限定名:包名+类名
name:也是别名,而且name可以同时取多个别名 空格,逗号,分号,隔开的别名都可以使用 默认单例
-->
<bean id="userTwo" class="com.qia.pojo.UserTwo" name="user2 u2,u3;u4">
<property name="name" value="狂神"/>
</bean>
</beans>
3、import
- 这个import。一般用于团队开发使用,它可以将多个配置文件,导入合并为一个。
- 假设,现在项目中有多个人开发,这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!
如:
张三
李四
王五
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">
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
</beans>
使用的时候,直接使用总的配置就可以了。
根据import的顺序,同id名的bean相当于同一个变量,会后面的覆盖前面的
六、依赖注入
1. 构造器注入
前面已经介绍过了
参考 四、IOC创建对象的方式
2. Set方式注入(重点)
1、DI 依赖注入 环境搭建
- 依赖注入:本质,Set注入
- 依赖:bean对象的创建依赖于容器!
- 注入:bean对象中的所有属性,由容器来注入!
【环境搭建】
1、复杂类型
基本数据类型value赋值,引用类型ref赋值
package com.qia.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
2、真实测试对象
package com.qia.pojo;
import lombok.*;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name; //String
private Address address; //类对象数型
private String[] books; //数组
private List<String> hobbies; //List
private Map<String,String> card;//Map
private Set<String> games; //Set
private String wife; //有没有妻子,可以设置成空指针
private Properties info; //配置类
}
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.qia.pojo.Student">
<!-- 第一种:普通值注入,value -->
<property name="name" value="兔兔"/>
</bean>
</beans>
4、测试类
import com.qia.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
System.out.println(student);
}
}
2、依赖注入之Set注入
5、完善注入信息
注入属性的值有多种类型可以设置
<?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.qia.pojo.Address">
<property name="address" value="中国"/>
</bean>
<bean id="student" class="com.qia.pojo.Student">
<!-- 第一种:普通值注入,value -->
<property name="name" value="兔兔"/>
<!-- 第二种: Bean注入 ref -->
<property name="address" ref="address"/>
<!-- 第三种: 数组 array -->
<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="111122223333"/>
<entry key="银行卡" value="123123123123"/>
</map>
</property>
<!-- 第六种:set -->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!-- null 空字符串“”和空值null不一样;不设也是为空
<property name="name" value=""/> 是值为空字符串 -->
<property name="wife">
<null/>
</property>
<!-- Properties Properties继承了HashTable -->
<property name="info">
<props>
<prop key="driver">jdbc</prop>
<prop key="url">mysql</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
import com.qia.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
// System.out.println(student.getName());
System.out.println(student);
/*
Student(
name=兔兔,
address=Address(address=中国),
books=[红楼梦, 西游记, 水浒传, 三国演义],
hobbies=[听歌, 敲代码, 看电影],
card={身份证=111122223333, 银行卡=123123123123},
games=[LOL, COC, BOB],
wife=null,
info={password=123456, url=mysql, driver=jdbc, username=root}
)
*/
}
}
3. 拓展注入(c命名和p命名空间注入)
我们可以使用p命名空间和c命名空间进行注入
官方解释:
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 对应set注入,需要有无参构造和set方法-->
<bean id="user" class="com.qia.pojo.User" p:name="兔兔" p:age="18"/>
<!--c命名空间注入,通过构造器注入:constructor-args 对应构造器注入,需要有有参构造-->
<bean id="user2" class="com.qia.pojo.User" c:name="狂神" c:age="22"/>
</beans>
测试:
import com.qia.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
// User user = context.getBean("user", User.class);//p
User user = context.getBean("user2", User.class);//c
//上下两种方式等价
// User user = (User) context.getBean("user");
System.out.println(user);
}
}
注意点:p命名和c命名空间不能直接使用,需要导入xml约束!
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
4. Bean的作用域(Beans Scopes)
1、单例模式 Singleton(Spring默认机制)
默认是单例
单例作用域
仅管理单例 Bean 的一个共享实例,并且所有对 ID 或 ID 与该 Bean 定义匹配的 Bean 的请求都会导致 Spring 容器返回该特定 Bean 实例。
换句话说,当你定义一个bean定义并且它的范围是一个单例时,Spring IoC容器只创建由该bean定义定义的对象的一个实例。此单个实例存储在此类单例 Bean 的缓存中,并且对该命名 Bean 的所有后续请求和引用都将返回缓存的对象。下图显示了单例作用域的工作原理:
public class MyTest {
public static void main(String[] args) {
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
}
}
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
默认就是单例作用域,也可以显式的定义单例作用域
设置 singleton=false 的bug被修复了
2、原型模式 Prototype
:每次从容器中get的时候,都会产生一个新对象!
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="user2" class="com.qia.pojo.User" c:name="狂神" c:age="22" scope="prototype"/>
System.out.println(user == user2);//false
3、其它
其余的request、session、application、这些只能在web开发中使用到!
4、可能导致的问题
Singleton:并发时可能会有问题
Prototype:可能会浪费内存
尽量使用单例
七、Bean的自动装配
前面都是手动装配
- 自动装配是Spring满足bean依赖的一种方式!
- Spring会在上下文中自动寻找,并自动给bean装配属性!
在Spring中有三种装配的方式:
- 在xml中显式的配置;
- 在java中显式配置;
- 隐式的自动装配bean【重要】
1. 测试原来的手动装配
环境搭建:创建项目,一个人有两个宠物!
public class Cat {
public void shout(){
System.out.println("miao~");
}
}
public class Dog {
public void shout(){
System.out.println("wang~");
}
}
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
private String name;
private Cat cat;
private Dog dog;
}
<bean id="cat" class="com.qia.pojo.Cat"/>
<bean id="dog" class="com.qia.pojo.Dog"/>
<bean id="people" class="com.qia.pojo.People">
<property name="name" value="狂神"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
people.toString();
people.getCat().shout();
people.getDog().shout();
}
2. ByName自动装配
<!--
byName:会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean id!
例如:setDog、setCat、setAge..
然后查找有无 id= dog、cat、age的值
此处若dog实体类的bean改成id=dogOther,就会报空指针错误,因为找不到id=dog的值 -->
<bean id="people" class="com.qia.pojo.People" autowire="byName">
<property name="name" value="狂神"/>
</bean>
3. ByType自动装配
<!--
byType:会自动在容器上下文中查找,和自己对象属性类型相同的bean! -->
<bean id="people" class="com.qia.pojo.People" autowire="byType">
<property name="name" value="狂神"/>
</bean>
小结:
- ByName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!【要保证id全局唯一,否则报错,根据id属性】
- ByType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!【要保证类型全局唯一,否则报错,根据class属性】
- 和set息息相关
4. 使用注解实现自动装配
jdk1.5支持的注解,Spring2.5就支持注解了!
xml配置易于管理维护,注解配置较简洁
要使用注解须知:
- 导入约束 : context约束
- 配置注解的支持 : < context:annotation-config/> 【重要!】(别忘了)
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
<?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>
@Autowired
@Autowired是先通过Type注入,后通过Name注入的
直接在属性上使用即可(就可以不写set方法了)! 也可以在set方式上使用!这两种方式效果一样
使用@Autowired 我们可以不让用编写set方法了 , 前提是你这个自动装配的属性在IOC(Spring) 容器中存在 , 且符合名字 byname !
@Data
@AllArgsConstructor
@NoArgsConstructor
public class People {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
}
<?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.qia.pojo.Cat"/>
<bean id="dog" class="com.qia.pojo.Dog"/>
<bean id="people" class="com.qia.pojo.People"/>
</beans>
import com.qia.pojo.People;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
People people = context.getBean("people", People.class);
people.toString();
people.getCat().shout();
people.getDog().shout();
}
}
科普:实体类的值设为空
方法一:
@Nullable
字段标记了这个注解,说明这个字段可以为null;
方法二:
@Autowired(required=false)
说明:false,对象可以为null;true,对象必须存入值,不能为null。
public class People {
//如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
public @interface Autowired {
boolean required() default true;
}
修改bean进行以下测试
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
@Qualifier
如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置@Autowired的使用,指定一个唯一的bean对象注入!
private String name;
//如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空
@Autowired(required = false)
private Cat cat;
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
@Resource
借助了@Qualifier可以指定Bean的名称,@Resource同时指定了byname和bytype,如果两种都没有匹配,那么需要使用到name
@Resource注解
private String name;
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
小结
@Resource 和 @Autowired 的区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowired(属于spring规范)通过byType的方式实现,而且必须要求这个对象存在!【常用】
- @Resource(属于J2EE复返)默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
- 执行顺序不同:@Autowired先类型后名字(结合@Qualifier使用);@Resource先名字后类型
八、Spring注解开发
在Spring4之后,要使用注解开发,必须要保证aop的包导入了
使用注解需要导入约束,配置注解的支持!
<?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>
使用扫描机制,指定要扫描的包,这个包下的注解就会生效
<?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:component-scan base-package="com.qia.pojo"/>
<!--开启注解的支持-->
<context:annotation-config/>
<!--可以二选一-->
</beans>
1. bean
@Component
组件,放在类上,说明这个类被String管理了,就是bean!
等价于 < bean id=“user” class=“com.qia.pojo.User”/>
package com.qia.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//等价于 <bean id="user" class="com.qia.pojo.User"/>
//@Component 组件
@Component
public class User {
public String name = "兔兔";
}
2. 属性如何注入
@Value
相当于 < property name=“name” value=“兔兔”/>
package com.qia.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//等价于 <bean id="user" class="com.qia.pojo.User"/>
//@Component 组件
@Component
public class User {
//相当于 <property name="name" value="兔兔"/>
@Value("兔兔")
public String name;
}
所以 简单操作用注解,复杂的用xml配置
3. 衍生的注解
@Repository、@Service、@Controller
@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!
- dao 【@Repository】
- service 【@Service】
- controller 【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean
4. 自动装配
- @Autowired:自动装配通过类型,名字。如果Autowired不能唯一自动装配上属性,则需要通过@Qualifier(value = “xxx”)去配置。
- @Nullable 字段标记了了这个注解,说明这个字段可以为null;
- @Resource:自动装配通过名字,类型。
5. 作用域
@Scope
@Component
@Scope("singleton")
public class User {
//相当于 <property name="name" value="白莲"/>
@Value("兔兔")
public String name;
}
6. 小结
- xml与注解:
- xml更加万能,适用于任何场合!维护简单方便
- 注解不是自己类使用不了,维护相队复杂!
- xml与注解最佳实践:
- xml用来管理bean;
- 注解只负责完成属性的注入;
- 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持
九、使用Java的方式配置Spring
使用JavaConfig实现配置
我们现在要完全不使用Spring的xml配置了,全权交给Java来做!
(不使用xml后,这些东西还是要去做的,所以Config就来做了)
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!
过去CPX(类路径xml),现在AP(注解配置)
- 从(JavaBean(Service+Dao+entity)+JSP+Servlet)MVC
到 Mybatis(pojo+mapper接口+mapper.xml)
到 Spring(Bean)MVC - 从 万物皆Object 到 万物皆Bean
- 从 xml 到 注解
- 从 面向对象开发 到 面向(Bean)注解开发
package com.qia.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
// 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中
// 使用@Component声明的类会被@ComponentScan扫描到,并注册为@Bean,这样配置类里就不需要显式声明@Bean了
// 但是配置类中已经注册了@Bean,这里就可以不用写@Component了
// 二选一
//@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("兔兔")
public void setName(String name) {
this.name = name;
}
}
package com.qia.config;
import com.qia.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
// @SuppressWarnings("all")//忽略全部警告
// 这个也会Spring容器托管,注册到容器中,因为它本来就是一个@Component
// @Configuration代表这是一个配置类,就和我们之前看的beans.xml一样
@Configuration
@ComponentScan("com.qia.pojo")
@Import(QiaConfig2.class)
public class QiaConfig {
// 注册一个bean,就相当于我们之前写的一个bean标签
// 这个方法的名字,就相当于bean标签中id属性
// 这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User user(){
return new User(); // 就是返回要注入到bean的对象!
}
}
package com.qia.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QiaConfig2 {
}
import com.qia.config.QiaConfig;
import com.qia.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,我们就只能通过 AnnotationConfig 上下文来获取容器,通过配置类的class对象加载!
ApplicationContext context = new AnnotationConfigApplicationContext(QiaConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user.getName());
}
}
- @ComponentScan和@Component一起使用
- @Component和@Bean作用一样,用于创建bean
- @ComponentScan和@Configuration作用一样,用于开启注解
注册bean的两种方法:二选一即可
- 方法一:@Bean + 配置类的get方法
- 方法二:@Component 声明在类上
这种纯Java的配置方式,在SpringBoot中随处可见!
十、代理模式
相当于中介,黄牛等
为什么要学习代理模式?因为这就是SpringAOP的底层!【SpringAOP 和 SpringMVC】
代理模式的分类:
- 静态代理
- 动态代理
1. 静态代理模式
角色分析:
- 抽象角色(租房):一般会使用接口或者抽象类来解决
- 真实角色(房东):被代理的角色
- 代理角色(中介):代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户(买房的人):访问代理对象的人!
(本来两方能直接做这件事,但现在出现了一个第三方,这个第三方更擅长做这件事,所以两方现在都需要通过这个第三方来完成相对的目的)
(真实角色和代理角色都有相同的共性,因此这个共性就是程序的接口,所以可以这样子理解,房东和中介都要出租房屋,所以提取出租房这个接口)
代码步骤:
- 接口
package com.qia.demo01;
//租房的接口
public interface Rent {
public void rent();
}
- 真实角色
package com.qia.demo01;
//房东主人,继承接口
public class Host implements Rent{
public void rent() {
System.out.println("房东出租房子!");
}
}
- 代理角色
package com.qia.demo01;
//中介代理,组合的方式来组合房东,组合优于继承,合成复用原则 //可以理解为中介得有房东的联系方式 //继承是is a的关系,组合是has a的关系
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
host.rent();
seeHouse();
sign();
fee();
}
//看房
public void seeHouse(){
System.out.println("中介带着看房子!");
}
//签合同
public void sign(){
System.out.println("和中介签署租赁合同!");
}
//收费用
public void fee(){
System.out.println("中介收取费用!");
}
}
- 客户端访问代理角色
package com.qia.demo01;
//客户
public class Client {
public static void main(String[] args) {
//要先有卖房的人,然后才有中介,买房的人才有地方买
//房东要出租房子
Host host = new Host();
// host.rent();
//代理,中介帮房东出租房子,并且代理角色一般会有一些附属操作!
Proxy proxy = new Proxy(host);
//不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低~
2. 静态代理模式再理解(加深理解)
程序员有一个原则,尽量不去修改原来的代码,所以学会了不断地加一层,加一层
当业务增加或修改
原来
使用再写一个类去继承这个接口,组合原来的类(也实现了这个接口的一个类,这个类负责原来的业务),把新业务的方法去调用组合的类成员
现在
spring建议加set方法
springboot建议加构造器注入
代码:
1、接口
package com.qia.demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
2、原来的业务实现类
package com.qia.demo02;
//真实角色
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("查询了一个用户!");
}
}
3、新增业务,在调用原来的业务的同时,增加日志输出
以组合实现扩展业务
package com.qia.demo02;
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
//日志方法
public void log(String msg){
System.out.println("[Debug] 使用了一个"+msg+"方法");
}
}
开闭原则,对扩展开放,对修改关闭
4、客户端调用业务
package com.qia.demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.delete();
}
}
静态代理模式就是在不修改原来代码的基础上实现功能的扩展
拓展:
OOP七大原则
聊聊AOP
代理模式就是AOP,面向切面编程
3. 动态代理详解
- 动态代理和静态代理 角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!(静态是编译时生成,动态是运行时生成)
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口 — JDK动态代理【我们在这里使用】
- 基于类:cglib
- java字节码实现:javassist(JBoss )
需要了解两个类:
- Proxy:代理;
- InvocationHandler:调用处理程序。
1、Proxy 和 InvocationHandler
【InvocationHandler:调用处理程序】
处理代理实例上的方法调用并返回结果。
当在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
Object invoke(Object proxy, 方法 method, Object[] args);
//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):
- proxy是最终生成的代理对象;
- method 是被代理目标(真实对象)实例的某个具体方法;
- args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
测试:
房东还是房东,还是出租房这件事
代理角色的调用方法
代码步骤:
1、接口
//租房的接口
public interface Rent {
public void rent();
}
2、真实角色
//房东主人,继承接口
public class Host implements Rent {
public void rent() {
System.out.println("房东出租房子!");
}
}
3、代理角色
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们会用这个类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
// this.getClass().getClassLoader(), 获得这个类的类加载器
// rent.getClass().getInterfaces(), 获得这个接口的所有实现类 //getClass().getInterfaces() 返回的是一个接口数组
// this 这个类本身
// 代理关系也就组建好了
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是使用反射机制实现!
Object result = method.invoke(rent, args);
seeHose();
fee();
return result;
}
// ↓ ==== 中介自己的业务 ====
public void seeHose(){
System.out.println("中介带着看房子!");
}
public void fee(){
System.out.println("中介收取费用!");
}
}
3.1.生成实例化代理角色
组合的接口,我们需要set,然后需要拿到接口本身,去生成代理角色(确定要做的事(这个租房事件的接口))
3.2.处理代理角色业务
看要买谁的房子,可能不只有房东这个类在出租房,可能还有其他的rent接口实现类
然后可以有代理角色本身自己的其他业务
4、客户端
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色:现在没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象!
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy(); //这里的proxy就是动态生成的,我们并没有写
proxy.rent();
//1、房东找中介;2、生成业务了;3、客户端调用出租房业务
/*
房东出租房子!
中介带着看房子!
中介收取费用!
*/
}
}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
2、深化理解
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
我们使用动态代理,来实现一下,动态代理我们写的UserService!
代码:
//接口,这个相当于租房这件事本身
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
//真实角色:相当于房东
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("查询了一个用户!");
}
}
import com.qia.demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们会用这个 通用 类,自动生成代理类!
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());//其他业务
Object result = method.invoke(target, args);//真实对象(目标对象)
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
//相当于客户端调用
public class Client {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService) pih.getProxy(); //动态生成代理类!
proxy.delete();//这就是代理对象的invoke方法传的method
//先有了真实对象,代理对象,当真实对象找上代理,业务就准备好了,最后看你要执行哪个业务方法
}
}
invoke方法是实现InvocationHandler这个反射的接口,必须实现的方法
JAVA的反射是指,可以通过一个类名来探察这个类里面的信息,比如说类的属性名,属性名的修饰符,方法名,方法返回值,方法修饰符等等,反正除了方法体得不到,其他都可以用反射得到;反射还可以生成类的实例,通过这个实例定义属性,调用方法,特别是能调用私有的属性和私有的方法。
所以invoke方法可以调用真实对象的所有信息
学习方法:可以用debug走一遍代码
3、小总结
动态代理的好处:
- 静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情
- 公共的业务由代理来完成 . 实现了业务的分工
- 公共业务发生扩展时变得更加集中和方便
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类(接口的多个实现类),代理的是接口!
静态代理缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低~
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
静态代理与动态代理区别就是,静态代理只能代理一个接口,而动态代理能代理任意接口,也就意味着可以为任何接口的实现类增添功能
中介不再只可以处理出租房业务(静态),还可以做结婚包办等业务(动态)
IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
十一、AOP
AOP基于动态代理(动态代理本身的原理就是接口套娃,或者暴力继承)
1. 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2. AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
横切关注点就是我们想在当前类添加的一些业务功能,切面就是把这些业务抽象成一个类,通知就是类里面的方法
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。
(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)
(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知
例如:
<!-- bankService bean -->
<bean id="bankService" class="com.chenqa.springaop.example.service.impl.BCMBankServiceImpl"/>
<!-- 切面 -->
<bean id="myAspect" class="com.chenqa.springaop.example.aspect.MyAspect"/>
<!-- aop配置 -->
<aop:config>
<aop:aspect ref="myAspect">
<aop:pointcut expression="execution(* com.chenqa.springaop.example.service.impl.*.*(..))" id="pointcut"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/>
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
即AOP在不改变原有代码的情况下,去增加新的功能。
3. 使用Spring实现AOP
1、AOP实现方式一:使用Spring的API接口
【主要是SpringAPI接口实现】
1、依赖
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2、expression表达式
expression表达式的写法规则
- execution( 1 modifiers-pattern? 2 ret-type-pattern 3 declaring-type-pattern? 4 name-pattern 5 (param-pattern) 6 throws-pattern? )
- 这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- 1 modifiers-pattern:方法的可见性(访问修饰符),如public,protected;
- 2 ret-type-pattern:方法的返回值类型,如int,void等;
- 3 declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
- 4 name-pattern:方法名类型,如buisinessService();
- 5 param-pattern:方法的参数类型,如java.lang.String;
- 6 throws-pattern:方法抛出的异常类型,如java.lang.Exception;
- 这里问号表示当前项可以有也可以没有,其中各项的语义如下:
- execution表达式的语法:
- execution(方法访问修饰符匹配 方法返回值类型 类名 方法名 参数)
- ret-type-pattern , name-pattern(param-pattern) :是必须的.
- ret-type-pattern:标识方法的返回值,需要使用全路径的类名如java.lang.String,也可以为*表示任何返回值;
- name-pattern:指定方法名,*代表所有,例如 set *,代表以set开头的所有方法.
- param-pattern:指定方法参数(声明的类型),(. .)代表所有参数,(* )代表一个参数,( * ,String)代表第一个参数为任何值,第二个为String类型.
- execution(方法访问修饰符匹配 方法返回值类型 类名 方法名 参数)
表达式例子
execution(public * *(..)):任意公共方法的执行
execution(* set*(..)):任何一个以“set”开始的方法的执行
execution(* com.xyz.service.AccountService.*(..)):AccountService 接口的任意方法的执行
execution(* com.xyz.service.*.*(..)):定义在service包里的任意方法的执行
execution(* com.xyz.service..*.*(..)):定义在service包和所有子包里的任意类的任意方法的执行
execution(* com.test.spring.aop.pointcutexp..JoinPointObjP2.*(..))"):定义在pointcutexp包和所有子包里的JoinPointObjP2类的任意方法的执行
在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非
<aop:config>
<aop:pointcut id="pointcut" expression="(execution(* com.ccboy.dao..*.find*(..))) or (execution(* com.ccboy.dao..*.query*(..)))"/>
<aop:advisor advice-ref="jdbcInterceptor" pointcut-ref="pointcut" />
</aop:config>
通配符说明
- 第一个* 表示任意返回值类型
- 第二个* 表示以任意名字开头的package. 如 com.xx.
- 第三个* 表示以任意名字开头的class的类名 如TestService
- 第四个* 表示 通配 *service下的任意class
- 最后二个 . . 表示通配 方法可以有0个或多个参数
3、代码
1、导入依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2、接口(抽象对象)
//接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
3、接口实现类(真实对象)
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("查询了一个用户!");
}
}
4、其他业务(代理对象的其他业务)
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
//method: 要执行的目标对象的方法
//args:参数
//target:目标对象
public void before(Method method, Object[] agrs, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
//returnValue: 返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
5、applicationContext.xml(beans.xml)(代理对象)(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"
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">
<!--注册bean-->
<bean id="userService" class="com.qia.service.UserServiceImpl"/>
<!-- ========== 配置代理 ========== -->
<!--方式一:使用原生Spring API接口-->
<!--配置aop:需要导入aop的约束-->
<bean id="log" class="com.qia.log.Log"/>
<bean id="afterLog" class="com.qia.log.AfterLog"/>
<aop:config>
<!--切入点 pointcut:expression:表达式,execution(要执行的位置!* * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.qia.service.UserServiceImpl.*(..))"/>
<!-- AspectJ语法:execution(方法访问修饰符匹配 方法返回值类型 类名 方法名 参数) -->
<!-- 执行环绕增加!-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
解析这个表达式
<aop:pointcut id="pointcut" expression="execution(* com.qia.service.UserServiceImpl.*(..))"/>
此处省略了方法访问修饰符;
第一个是返回值类型,此处是*;
第二个是类的全路径名;
第三个是 .方法名,此处是 .*,即全部方法;
第四个(. .)代表所有参数;
省略了抛出异常类型
意思:切入点(也可以理解为插入)在 com.qia.service.UserServiceImpl 这个类下的所有方法
6、测试类,客户端调用
import com.qia.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理 代理的是接口:注意点
UserService userService = (UserService) context.getBean("userService");
userService.add();
// userService.select();
}
}
2、AOP实现方式二:自定义来实现AOP
【主要是切面定义】
1、在diy包下定义自己的DiyPointCut切入类
package com.qia.diy;
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前======");
}
public void after(){
System.out.println("======方法执行后======");
}
}
2、去spring中配置文件
<?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">
<!--注册bean-->
<bean id="userService" class="com.qia.service.UserServiceImpl"/>
<!-- ========== 配置代理 ========== -->
<!--方式二:自定义类-->
<bean id="diy" class="com.qia.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面,ref 要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.qia.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
3、测试类
一样
3、AOP实现方式三:使用注解实现AOP
1、在diy包下定义注解实现的AnnotationPointCut增强类
package com.qia.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
//声明式事务!
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.qia.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("====方法执行前====");
}
@After("execution(* com.qia.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("====方法执行后====");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点;
@Around("execution(* com.qia.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
Signature signature = jp.getSignature();// 获得签名
System.out.println("signature:"+signature);
Object proceed = jp.proceed(); //执行方法
System.out.println("环绕后");
System.out.println(proceed);
}
}
2、在Spring配置文件中,注册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: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">
<!--注册bean-->
<bean id="userService" class="com.qia.service.UserServiceImpl"/>
<!-- ========== 配置代理 ========== -->
<!--方式三:使用注解-->
<bean id="annotationPointCut" class="com.qia.diy.AnnotationPointCut"/>
<!--也可以不用bean,用component,但是记得开启注解扫描包-->
<!--!下面这个开启注解支持! JDK(默认是 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy/>
<!-- JDK -->
<!-- <aop:aspectj-autoproxy proxy-target-class="false"/>-->
<!-- cglib -->
<!-- <aop:aspectj-autoproxy proxy-target-class="true"/>-->
</beans>
3、测试
一样
输出结果顺序,优先级别
XML --> DIY --> 环绕 --> 注解 --> 执行方法前 --> 执行方法 --> 执行方法后 --> 注解 --> 环绕 --> DIY --> XML
Spring版本不同,执行顺序不同
区别:JDK稚嫩恶搞代理接口实现类,而cglib需要的是类
<!--!下面这个开启注解支持! JDK(默认是 proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy/>
<!-- JDK -->
<!-- <aop:aspectj-autoproxy proxy-target-class="false"/>-->
<!-- cglib -->
<!-- <aop:aspectj-autoproxy proxy-target-class="true"/>-->
4、小总结
在不影响真实类方法的前提下(不改动源代码),进行动态的增强,去作横向切面,即横向扩展一些功能(横向开发~)
1、Aop原理
SpringAop 原理就是动态代理
对于实现接口的目标类使用的是jdk动态代理
对于没有实现任何接口的目标类,使用的是cglib的动态代理
代理类是程序在运行期间由JVM根据反射等机制动态生成的自动生成代理类和代理对象。
所谓动态就是指在程序运行前不存在代理类的字节码文件。
2、SpringAop的配置方式
三种配置方式
一:SpringAop 1.x 使用ProxyFactoryBean手动代理
二:SpringAop 2.x 基于命名控件的配置
三:Annotation 基于注解的配置
3、Advice类型
SpringAop支持五种类型的通知(增强(业务))
注意:多个Advice之间不允许有耦合,即多个Advice之间不允许有业务交叉。
4、SpringAop1.x 使用ProxyFactoryBean 手动代理
配置方式:
基本用法:
添加jar包
1: 配置增强类(继承Advice相关接口的类,MethodBeforeAdvice等),且注册bean
2: 配置目标类实例(目标对象,真实对象,业务接口实现类),且注册bean
3: 配置advicor,将Advice和pointCut结合 织入的过程
4: 在advicor里,配置切入点PoinCut 指定匹配哪些方法,直接写方法名
5: 在advicor里,指定增强类(通知advice)
6. 配置代理,使用ProxyFactoryBean配置代理
其中
定义切入点,配置位置信息,指定那些类的哪些方法需要被执行Aop。
使用NameMathodPointcutAdvisor根据方法名匹配切入点
Advisor是Pointcut和Advice的配置器,Pointcut+Advice=Advisor
例如:
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--2:配置目标类实例-->
<bean id="userServiceTarger" class="springaop02.service.impl.UserServlceImpl"/>
<!--1:配置增强类-->
<bean id="logAdvice" class="springaop02.advice.BeforeAdvice"/>
<!-- 3:配置advicor 将Advice和pointCut结合 织入的过程-->
<bean id="logAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<!-- 5:指定增强类(通知advice)-->
<property name="advice" ref="logAdvice"/>
<!-- 4: 配置切入点PoinCut 指定匹配哪些方法直接写方法名-->
<property name="mappedNames">
<list>
<value>login</value>
</list>
</property>
</bean>
<!--配置代理-->
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--目标类实例-->
<property name="target" ref="userServiceTarger"/>
<!--目标类接口列表-->
<property name="interfaces">
<list>
<value>springaop02.service.UserService</value>
</list>
</property>
<!--交叉业务-->
<property name="interceptorNames">
<list>
<value>logAdvisor</value>
</list>
</property>
</bean>
</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"
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">
<!--注册bean-->
<bean id="userService" class="com.qia.service.UserServiceImpl"/>
<bean id="log" class="com.qia.log.Log"/>
<bean id="afterLog" class="com.qia.log.AfterLog"/>
<!--方式一:使用原生Spring API接口-->
<!--配置aop:需要导入aop的约束-->
<aop:config>
<!--切入点 pointcut:expression:表达式,execution(要执行的位置!* * * * *)-->
<aop:pointcut id="pointcut" expression="execution(* com.qia.service.UserServiceImpl.*(..))"/>
<!-- AspectJ语法:execution(方法访问修饰符匹配 方法返回值类型 类名 方法名 参数) -->
<!-- 执行环绕增加!-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
十二、整合Mybatis
1. 回顾Mybatis
1、步骤:
1、导入相关jar包
-
junit
-
mybatis
-
mysql数据库
-
spring相关
-
aop织入器
-
mybatis-spring整合包【new 重点】在此还导入了lombok包。
https://mybatis.org/spring/zh/index.html -
配置Maven静态资源过滤问题!
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
2、编写配置文件
3、测试
2、回忆mybatis
1、编写pojo实体类
2、编写实现mybatis的配置文件
3、编写UserMapper接口
4、编写UserMapper.xml文件
5、测试
代码:
一、环境搭建
1、pom.xml
1.1、导入maven依赖
<!-- 导入依赖 -->
<dependencies>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
1.2、资源过滤
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
1.3、设置项目整体编码
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
2、Utils
package com.qia.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
//sqlSessionFactory --> sqlSession
// 读取配置文件把它加载成流!!!
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;//局部变量变成全局变量,提升作用域,方便调用
static{
try {
//使用Mybatis的第一步:获取sqlSessionFactory对象 -- 读取配置文件把它加载成流!!!
String resource = "mybatis-config.xml";//获取资源名称,即配置文件名称
InputStream inputStream = Resources.getResourceAsStream(resource);//把配置文件转成流的形式
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//把配置文件的流形式进行加载、构建
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
// SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
// 即 getSqlSession 这个方法用于 获取SqlSession
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
3、resource
3.1、db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
3.2、mybatis-config.xml
包含读取properties文件,给包一次性取别名(里面的所有类都默认有首字母小写的别名),mapper注册
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"></properties>
<typeAliases>
<package name="com.qia.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.qia.mapper"/>
</mappers>
</configuration>
二、编写业务
4、编写pojo实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String pwd;
}
5、编写UserMapper接口
package com.qia.mapper;
import com.qia.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
}
6、编写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.qia.mapper.UserMapper">
<!--sql-->
<select id="selectUser" resultType="user">
select * from user
</select>
</mapper>
三、测试
7、测试
import com.qia.mapper.UserMapper;
import com.qia.pojo.User;
import com.qia.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
public class MyTest {
@Test
public void selectUser() throws IOException {
// 1.以流的形式加载类得到SqlSession,可以封装成一个工具类
// String resource = "mybatis-config.xml";//获取配置文件的文件名
// InputStream inputStream = Resources.getResourceAsStream(resource);//以流的形式读取类
// SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//以流的形式加载类获得工厂SqlSessionFactory
// SqlSession sqlSession = sqlSessionFactory.openSession();//从工厂中打开一个sql会话SqlSession
//即下面这个
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 2.从SqlSession中,通过反射,获取将使用的mapper的类的所有信息
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 3.开始操作
List<User> userList = mapper.selectUser();
for (User user: userList){
System.out.println(user);
}
// 4.关闭sqlSession
sqlSession.close();
}
}
2. Mybatis-Spring
什么是MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。
它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
.
文档链接:http://mybatis.org/spring/zh/index.html
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
1、整合Mybatis方式一:SqlSessionTemplate
1、在 pom.xml 中加入以下代码即可:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
2、spring-dao.xml
2.1、引入
引入Spring配置文件spring-dao.xml
要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类(即DataSource)。
万物皆可bean
2.2、DataSource
配置数据源替换mybaits的数据源
<!--DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid
我们这里使用Spring提供的JDBC:org.springframework.jdbc.datasource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
pom.xml --> spring-dao.xml
↓
2.3、SqlSessionFactory
配置SqlSessionFactory,关联MyBatis
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--关联mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qia/mapper/*.xml"/>
</bean>
一般会让mybatis-config.xml只做取别名和设置settings 操作
2.4、sqlSessionTemplate
注册sqlSessionTemplate的bean,关联sqlSessionFactory
即注册了SqlSession
<!--SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
2.5、UserMapperImpl
UserMapperImpl 实现类,私有化sqlSessionTemplate
public class UserMapperImpl implements UserMapper {
//我们的所有操作,都使用sqlSession来执行,在原来,现在都使用SqlsessionTemplate
private SqlSessionTemplate sqlSession;
//不需要get方法,因为取这个sqlSession,也只会通过bean来取,不会用到get方法
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
要记得注册bean
<bean id="userMapper" class="com.qia.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
3、测试
@Test
public void test() throws IOException{
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
整合别人项目的话,也可以写个总的beans,即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: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/beans/spring-aop.xsd">
<import resource="spring-dao.xml"/>
</beans>
思路:
1、
Spring把所有的东西都变成了一个个的bean,用beans配置文件作用容器装起来,要的时候可以用CPX的上下文来获取。
要用哪个bean就取哪个bean就可以了,bean里的东西、方法都可以用
2、
先把原来的mybatis.xml变成bean,里面的 数据源,sql会话工厂,sql会话(sql会话盘子),都变成了bean。
让mybatis-config.xml只剩下做取别名和设置settings 操作
3、
写个mapper的接口实现类,且注册bean
主要是为了间接把mapper接口及绑定的mapper.xml变成一个bean,现在只需要通过这个实现类就可以执行数据库操作(解耦:mapper.xml实现了数据库sql和java代码的解耦)
spring无法注册接口,只能注册类,所以写实现类是必要的
4、
测试时
获取上下文这个容器
在上下文这个容器里拿bean
拿出bean,就可以调用里面的东西了
结论
Spring要接管所有对象,让他们可以直接创建
这样做业务时就可以简化操作
不用为了在业务层调用数据库层的操作时,要先创建会话工厂,在得到会话,在得到mapper执行对象
现在可以一步到位,所有的东西都是bean,用什么拿什么
JavaMVC的解耦,实现了前端页面,处理前后端请求响应,业务层,数据库层的解耦
mybatis的解耦,实现了数据库层,java代码和sql语句的解耦
Spring-mybatis的解耦,实现了所有的东西都是bean,可以用什么拿什么
2、整合Mybatis方式二:SqlSessionDaoSupport
mybatis-spring1.2.3版以上的才有这个
dao实现类继承Support类 , 然后直接注入SqlSessionFactory ,利用 getSqlSession() 获得SqlSession , 比起整合方式一 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看。
代码:
1、将我们上面写的UserMapperImpl修改一下
package com.qia.mapper;
import com.qia.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
public List<User> selectUser(){
// SqlSession sqlSession = getSqlSession();
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// List<User> userList = mapper.selectUser();
// return userList;
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}
2、写完一个类,就马上注册他的bean
<bean id="userMapper2" class="com.qia.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!-- 这个属性是为了传给它的父类,由它的父类创建 getSqlSession -->
</bean>
3、测试
@Test
public void test2() throws IOException{
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
3、小总结
两种整合方式差别在sqlSession
一种是私有化 sqlSessionTemplate ,在注册接口实现类为bean的时候将sqlSessionTemplate注入属性,即这个接口实现类在构造的时候这个属性就赋值好了,可以直接用
一种是通过继承 sqlSessionDaoSupport 这个类,SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法
一个是自己手动注入,一个是由父类创建获得
不需要关闭sqlSession,这两种方法都是线程安全的
背后的难是为了表面的简单
十三、声明式事务
1. 事务回顾
- 把一组业务当成一个业务来做;要么都成功,要么都失败!
- 事务在项目开发中,十分的重要,涉及到数据的一致性问题,不能马虎!
- 确保完整性和一致性。
事务ACID原则:
- 原子性(atomicity)
事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用。 - 一致性(consistency)
一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中。 - 隔离性(isolation)
可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。- 多个业务可能操作同一个资源, 防止数据损坏
- 持久性(durability)
事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中。- 事物一旦提交 , 无论系统发生什么问题 , 结果都不会再被影响 , 被持久化的写到存储器中!
测试:
1、将上面的代码拷贝到一个新项目中
导入maven依赖
导入配置文件
spring-dao.xml放三个bean,dataSource,sqlSessionFactory,sqlSession
mybatis-config.xml放别名和日志设置
applicationContext.xml用来导入spring-dao.xml包和注册bean
mapper接口
mapper.xml
mapper实现类
注册mapper实现类的bean
测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
2、在之前的案例中,我们给userMapper接口新增两个方法,删除和增加用户;
//添加一个用户
int addUser(User user);
//根据id删除用户
int deleteUser(int id);
UserMapper文件,我们故意把 deletes 写错,测试是否是事务要么都成功要么都失败!
<insert id="addUser" parameterType="com.qia.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int">
deletes from user where id = #{id}
</delete>
编写接口的UserMapperImpl实现类,增加查询业务的内部业务,增加add、delete业务
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
//增加一些操作
public List<User> selectUser() {
User user = new User(5, "小王", "185161");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.addUser(user);
mapper.deleteUser(5);
return mapper.selectUser();
}
//新增
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
//删除
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
测试
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
2. Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
- 编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
- 声明式事务管理
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
声明式事务 : AOP
编程式事务 : 需要在代码中 , 进行事务的管理
声明式事务(交由容器管理事务)
- 使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
- JDBC事务(配置声明式事务(接入数据源))
<!--配置声明式事务(接入数据源)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给那些方法配置事务-->
<!--配置事务的传播特性: new propagation-->
<tx:attributes>
<!--name是方法名 默认 propagation="REQUIRED"-->
<tx:method name="addUser" propagation="REQUIRED"/>
<tx:method name="deleteUser" propagation="REQUIRED"/>
<!--<tx:method name="updateUser" propagation="REQUIRED"/>-->
<tx:method name="selectUser" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
spring事务传播特性:
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
- 配置AOP,导入aop的头文件
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qia.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
- 删掉刚才插入的数据,再次测试!
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
userMapper.addUser(new User(7, "小王", "987654"));
}
思考:
为什么需要事务?
- 如果不配置事务,可能存在数据提交不一致的情况;
- 如果我们不在Spring中去配置声明式事务,我们就需要在代码中手动配置事务!
- 事务在项目的开发中十分重要,涉及到数据的一致性和完整性问题,不容马虎!
这一步实现了事务操作和java代码的解耦
总结和回顾
配置文件要注意多空格的问题
ioc 控制反转 创建实例获取对象的方式变了(set)
aop 面向切面编程 横向扩展功能
真正做项目时会用到下面三个
什么是JavaConfig?
Spring JavaConfig是Spring社区的产品,他提供了配置Spring IOC容器的纯Java方法
javaConfig的作用
利用纯java开发在不用添加xml配置文件的情况下使用注解以此达到高效的开发
JavaConfig的优点
1.面向对象的配置。由于配置被定义为JavaConfig中的类,因此用户可以充分使用Java中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean方法等。
2.减少或者消除XML配置。基于依赖注入原则的外化配置的好处已经被证明。但是,许多开发人员不希望在XML和Java之间来回切换。javaconfig为开发人员提供了一种纯Java的方法来配置与XML配置概念相似的Spring容器。从技术角度来说,只使用javaconfig配置类来配置容器是可行的,但是实际开发中,很多场景都是javaconfig和xml配置共用是最方便,理想的。
3.类型安全和重构友好。javaconfig提供了一种类型安全的方法了来配置spring容器,由于Java5.0对泛型的支持,现在可以按类型而不是名称检索bean,不需要任何的强制转换或者基于字符串的查找。
1、mybatis-config.xml
typeAliases
setting
2、spring-dao.xml
dataSource
sqlSessionFactory
sqlSession(SqlSessionTemplate)
配置声明式事务(接入数据源)
(结合AOP实现事务的织入:通知+切入点)
配置事务通知
配置事务切入
<?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
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--DataSource:使用Spring的数据源替换Mybatis的配置 c3p0 dbcp druid
我们这里使用Spring提供的JDBC:org.springframework.jdbc.datasource-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--关联mybatis配置文件 有了configLocation,就可以把配置信息写在这里,不用写在mybatis-config.xml了-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qia/mapper/*.xml"/>
</bean>
<!--SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!--配置声明式事务(接入数据源)-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--结合AOP实现事务的织入-->
<!--通知+切入点-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给那些方法配置事务-->
<!--配置事务的传播特性: new propagation-->
<tx:attributes>
<!--name是方法名 默认 propagation="REQUIRED"-->
<tx:method name="addUser" propagation="REQUIRED"/>
<tx:method name="deleteUser" propagation="REQUIRED"/>
<!--<tx:method name="updateUser" propagation="REQUIRED"/>-->
<tx:method name="selectUser" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qia.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
3、application.xml
import
其他bean
学习xml配置文件以来,逐步实现解耦
在mybatis开始学习xml配置文件,实现数据库sql语言和dao层java代码的解耦
在spring学习xml配置文件,把mvc各层实现解耦,变成一个个的bean
学习java-config,实现注解开发,逐步减少xml配置文件代码,实现完全的纯 java 开发
本项目完整pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Spring-Study</artifactId>
<groupId>com.qia</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-11-transaction</artifactId>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
SSM Project 项目完整pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Spring-Study</artifactId>
<groupId>com.qia</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-11-transaction</artifactId>
<dependencies>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 日志输出 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.19.0</version>
</dependency>
<!-- Mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.23</version>
</dependency>
<!--Spring操作数据库的话,还需要一个spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.23</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!-- commons -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- Other -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<!-- <dependency>-->
<!-- <groupId>org.hibernate</groupId>-->
<!-- <artifactId>hibernate-validator</artifactId>-->
<!-- <version>8.0.0.Final</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
<build>
<finalName>ssmProject</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>