1、简介
- 2002年,首次推出了Spring框架的雏形:interface21框架
- Spring是以interface21框架为基础进行完善的
- Spring是一个大杂烩,整合了现有的所有框架
- SSH:Struct2+Spring+Hibernate
- SSM:SpringMvc+Spring+MyBatis
优点:
- 免费的开源的框架(容器)
- 是一个轻量级的、非入侵式的框架
- 控制反转(IOC)、面向切面编程(AOP)
- 支持事务,整合框架
官方文档:Spring Framework Documentation
中文文档:Spring Framework 5.1.3.RELEASE 中文文档 | Docs4dev
常用jar包:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
2、IOC理论推导
2.1 最初的面向对象思想
UserDao接口:
public interface UserDao {
void getUser();
}
UserDaoImpl实现类:
public class UserDaoOracleImpl implements UserDao{
@Override
public void getUser() {
System.out.println("Oracle获取数据!");
}
}
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("Mysql获取数据!");
}
}
UserService接口:
public interface UserService {
void getUser();
}
UserServiceImpl实现类:
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoMysqlImpl();
// UserDao userDao = new UserDaoOracleImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
Test测试类:
public class MyTest {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
userService.getUser();
}
}
出现问题:每当Dao新增一个业务,Service层想要调用时,都必须要去修改UserServiceImpl实现类的代码,比如要调用Mysql类,则需new UserDaoMysqlImpl(),而如果要调用Oracle,则需new UserDaoOracleImpl()。这样会导致程序是主动创建对象,主动权在程序员手上,每次调用都需要更改程序内部代码,耦合性高,每一次修改成本大,程序可读性差。
2.2 简单的控制反转优化
UserServiceImpl实现类:
public class UserServiceImpl implements UserService {
// UserDao userDao = new UserDaoMysqlImpl();
// UserDao userDao = new UserDaoOracleImpl();
private UserDao userDao;
//使用set进行动态值注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
添加一个set方法,实现动态值注入,程序不再具有主动性
Test测试类:
public class MyTest {
public static void main(String[] args) {
UserService userService=new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
每一次更改业务时就不需要去Service层修改,用户要调用什么业务,就直接创建调用,如上述代码第4行。主动权在用户手上,耦合性低
3、第一个Spring程序
创建Hello类:
public class Hello {
String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
在resources中创建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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
原来创建对象的方法是 类型 变量名=new 类型()
现在id中的hello就是变量名,class中是变量类型,property中给字段设置值
-->
<bean id="hello" class="com.qing.pojo.Hello">
<!--
ref:引用Spring容器中创建好的对象
value:引用具体的值
-->
<property name="str" value="Spring"/>
</bean>
</beans>
MyTest测试:
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创建的,对象的属性是Spring容器设置的
4、IOC创建对象的方法
<bean id="user" class="com.qing.pojo.User">
<!-- 第一种,使用property标签,name中对应变量名-->
<property name="name" value="qingfan"/>
<!-- 第二种,下标赋值,index=0代表对象中的第一个属性,依次类推-->
<constructor-arg index="0" value="qingfan"/>
<!-- 第三种,通过类型创建,不建议使用-->
<constructor-arg type="java.lang.String" value="qingfan"/>
<!-- 第四种 直接通过参数名设置-->
<constructor-arg name="name" value="qingfan"/>
</bean>
第一种方式必须有无参构造器,后三种方式必须有对应的有参构造器
在配置文件加载时,容器中的对象就初始化了,且一个对象只有一个实例地址
5、Spring配置
5.1 别名 alias
<bean id="user" class="com.qing.pojo.User">
...
</bean>
<alias name="user" alias="newUser"/>
第四行在alias标签中为第一行注册的User对象取了一个别名newUser
5.2 Bean的配置
<bean id="userT" class="com.qing.pojo.User" name="user2 u3;u4,u5">
...
</bean>
- id:bean的唯一标识符,就是创建的对象名
- class:实体类的路径
- name:别名,比alias更加强大,可以同时取多个别名,并且分隔符可以使用空格 , ;等
5.3 import
将多个xml配置文件导入到一个文件,使用时只需调取总的配置文件,便利与团队开发
在bean.xml中导入bean1.xml和bean2.xml
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>
6、依赖注入
6.1 构造器注入
6.2 Set注入(重点)
环境搭建:
复杂对象
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
真实对象
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobby;
private Map<String,String>card;
private Set<String> games;
private Properties info;
private String NULL;
}
beans.xml文件
<bean id="address" class="com.qing.pojo.Address"/>
<bean id="student" class="com.qing.pojo.Student">
<!-- 普通值注入 value-->
<property name="name" value="qingfan"/>
<!-- Bean注入,ref-->
<property name="address" ref="address"/>
<!-- 数组注入 array-->
<property name="books">
<array>
<value>Java</value>
<value>C语言</value>
<value>C++</value>
</array>
</property>
<!-- List注入-->
<property name="hobby">
<list>
<value>打游戏</value>
<value>敲代码</value>
<value>听歌</value>
</list>
</property>
<!-- map注入-->
<property name="card">
<map>
<entry key="学生卡" value="001"/>
<entry key="饭卡" value="002"/>
<entry key="身份证" value="003"/>
</map>
</property>
<!-- set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>王者荣耀</value>
<value>CF</value>
</set>
</property>
<!-- 空值注入-->
<property name="NULL">
<null/>
</property>
<!-- Properties注入-->
<property name="info">
<props>
<prop key="学号">20191051</prop>
<prop key="性别">男</prop>
<prop key="姓名">qf</prop>
</props>
</property>
</bean>
6.3 p命名和c命名注入
p命名空间注入:
在xml配置文件的头部添加一行代码:
xmlns:p="http://www.springframework.org/schema/p"
使用bean注册对象时就可以直接赋值
<bean id="user" class="com.qing.pojo.User" p:name="qingfan" p:age="20"/>
注:使用p命名空间时必须有无参构造
c命名空间注入:
在xml配置文件的头部添加一行代码:
xmlns:c="http://www.springframework.org/schema/c"
<bean id="user2" class="com.qing.pojo.User" c:name="qingfan" c:age="20"/>
注:使用c命名空间时必须具有参构造
7、Bean的自动装配
7.1 测试环境
people有猫和狗,不使用自动装配,就需要手动装配,在people的bean中引用dog和cat
<bean id="dog" class="com.qing.pojo.Dog"/>
<bean id="cat" class="com.qing.pojo.Cat"/>
<bean id="people" class="com.qing.pojo.People">
<!--手动装配-->
<property name="name" value="qingfan"/>
<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>
</bean>
7.2 ByName自动装配
<!--
byName:会在容器上下文查找和自己对象属性名值对应的beanID
必须确保对象的属性名值全局唯一
-->
<bean id="dog" class="com.qing.pojo.Dog"/>
<bean id="cat" class="com.qing.pojo.Cat"/>
<bean id="people" class="com.qing.pojo.People" autowire="byName"/>
7.3 ByType自动装配
<!--
byType:会在容器上下文查找和自己对象类型相同的bean
必须确保set方法的类型全局唯一,且注册对象时,id可以省略
-->
<bean class="com.qing.pojo.Dog"/>
<bean class="com.qing.pojo.Cat"/>
<bean id="people" class="com.qing.pojo.People" autowire="byType/">
7.4 使用注解实现
使用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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
配置bean
<bean id="dog" class="com.qing.pojo.Dog"/>
<bean id="cat" class="com.qing.pojo.Cat"/>
<bean id="people" class="com.qing.pojo.People"/>
- @Autowired注解 通过byType实现,且set方法可以省略
public class People {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String name;
}
设置@Autowired(required = false),则对象值可以为空
- @Qualifier注解可以使bean id的对象名自动匹配指定的对象 byName实现·
<bean id="dog1" class="com.qing.pojo.Dog"/>
@Qualifier(value = "dog1")
private Dog dog;
- @Resource注解是Autowired和Qualifier的集合体,默认buName实现,如果找不到名字就通过byType实现
@Resource
// @Resource(name = "dog1")
private Dog dog;
8、注解开发
导入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"
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">
<context:annotation-config/>
<!-- 指定要扫描的包,这个包中的注解才能生效-->
<context:component-scan base-package="com.qing.pojo"/>
<context:annotation-config/>
</beans>
@Component、 @Value:
在类上面添加@Component,相当于 在bean配置文件中添加了
在set方法上添加了 @Value,相当于在bean标签中配置了
@Component
public class User {
@Value("qingfan")
public void setName(String name) {
this.name = name;
}
}
生成的User对象的对象名默认为User的小写user
@Component的衍生注解:
- dao
@Repository
public class UserDao {
}
- service:
@Service
public class UserService {
}
- controller:
@Controller
public class UserController {
}
四个注解功能一样,都是代表将某个类注册到Spring容器中
9、使用纯Java配置Spring
完全脱离xml配置文件,不用在文件中使用bean标签配置对象
User类:
//@Component代表将这个类注册到Spring容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("qingfan")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
Myconfig类:
//@Configuration 配置类
@Configuration
public class MyConfig {
//@Bean下面的方法的名字相当于bean标签中的id
//方法的返回值相当于bean标签中的class属性
@Bean
public User getUser(){
return new User();
}
}
Test测试类:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User User = context.getBean("getUser", User.class);
System.out.println(User.getName());
}
}
10、代理模式
- 静态代理
- 动态代理
10.1 静态代理
角色:
- 抽象角色:一般使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,实现操作,并且可以实现一些真实角色接触不到的业务
- 客户:访问代理对象的人
接口(抽象角色):
public interface Rent {
public void rent();
}
真实角色:
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东想要出租房子");
}
}
代理角色:
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void seeHouse() {
System.out.println("中介带你看房!");
}
public void contract() {
System.out.println("签合同!");
}
public void gertFree() {
System.out.println("中介收取中介费!");
}
//中介代理租房 并做一些其他附属操作
@Override
public void rent() {
host.rent();
seeHouse();
System.out.println("中介代理房东出租房子");
contract();
gertFree();
}
}
客户端访问:
public class Client {
public static void main(String[] args) {
Host host=new Host();
//中介代理
Proxy proxy = new Proxy(host);
//租房
proxy.rent();
}
}
10.2 动态代理
- 角色:和静态代理一样
- 代理类是动态生成的,不是我们直接写好的
分类:两大类,基于接口和基于类
- 基于接口:JDK动态代理 下面举例
- 基于类:Cglib
- java字节码实现:javasist
- 动态代理类代理的是一个接口,静态代理类代理的是一个类
接口:
public interface Rent {
public void rent();
}
真实角色:
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东想要出租房子");
}
}
动态代理角色:
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);
}
//处理代理实例,返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质,就是反射机制实现
log(method.getName()); //日志
Object result = method.invoke(target, args);
return result;
}
private void log(String name) {
System.out.println("执行了" + name + "方法!");
}
}
客户端:
public class Client {
public static void main(String[] args) {
//真实角色
Host host=new Host();
//代理角色
ProxyInvocationHandler pih=new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们调用的接口
pih.setTarget(host);
Rent proxy = (Rent) pih.getProxy(); //这里的Proxy是动态生成的
proxy.rent();
}
}
11、使用Spring实现AOP
导包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
11.1 方式一:使用原生Spring API接口实现
前日志类:
public class beforeLog implements MethodBeforeAdvice {
/*
前日志
method:要执行的目标对象的方法
args:参数
target:目标对象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
}
}
后日志类::
public class afterLog implements AfterReturningAdvice {
/*
后日志
returnValue:返回的值
method:要执行的目标对象的方法
args:参数
target:目标对象
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,结果为:" + returnValue);
}
}
配置beans.xml文件
<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
http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.qing.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.qing.log.beforeLog"/>
<bean id="afterLog" class="com.qing.log.AfterLog"/>
<!--配置AOP-->
<aop:config>
<!--切入点-->
<!--execution:表达式,要切入的位置:public void com.qing.service.UserServiceImpl.test()-->
<aop:pointcut id="pointcut" expression="execution(* com.qing.service.UserServiceImpl.*(..))"/>
<!--执行环绕-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
11.2 方式二:使用自定义类实现
自定义类:
public class DiyPointCut {
public void before() {
System.out.println("执行前");
}
public void after() {
System.out.println("执行后");
}
}
配置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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.qing.service.UserServiceImpl"/>
<bean id="diy" class="com.qing.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面 ref:要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.qing.service.UserServiceImpl.*(..))"/>
<!--切面-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试:
11.3 方式三:使用注解实现
自定义类:
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.qing.service.UserServiceImpl.*(..))")
public void before() {
System.out.println("方法执行前");
}
@After("execution(* com.qing.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("方法执行后");
}
//环绕增强中,可以给定一个参数,代表我们要处理切入的点
@Around("execution(* com.qing.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
//执行方法
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
配置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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.qing.service.UserServiceImpl"/>
<bean id="diy" class="com.qing.diy.AnnotationPointCut"/>
<!-- 开启注解支持-->
<aop:aspectj-autoproxy/>
</beans>
测试:
12、整合MyBatis
12.1 导包
<dependencies>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<!--Junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--Mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.6</version>
</dependency>
<!--Spring连接JDBC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
<!--aop织入-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<!--Spring整合MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
</dependencies>
12.2 原生MyBatis项目
MyBatis-config.xml:
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF8"/>
<property name="username" value="root"/>
<property name="password" value="****"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/qing/mapper/UserMapper.xml"></mapper>
</mappers>
</configuration>
接口:
public interface UserMapper {
public List<User> queryUser();
}
映射配置文件:
<mapper namespace="com.qing.mapper.UserMapper">
<select id="queryUser" resultType="com.qing.pojo.User">
select *from mybatis.user;
</select>
</mapper>
测试:
public class MyTest {
@Test
public void test() throws IOException {
String resources="Mybatis-config.xml";
//获取资源文件mybatis-config.xml
InputStream rs = Resources.getResourceAsStream(resources);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(rs);
//创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.queryUser();
System.out.println(list.toString());
}
}
12.3 Spring整合后的项目
接口:
public interface UserMapper {
public List<User> queryUser();
}
spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Datasource:使用Spring的数据源替换MyBatis的配置 连接数据库-->
<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=true&useUnicode=true&characterEncoding=UTF8"/>
<property name="username" value="root"/>
<property name="password" value="qing"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定MyBatis配置文件 注册Mapper,相当于MyBatis配置文件中的mapper标签-->
<property name="configLocation" value="classpath:Mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qing/mapper/*.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--创建SQLSession 只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--上面的所有代码固定,不用修改-->
<bean id="userMapperImpl" class="com.qing.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
- 8-14行相当于原生项目MyBatis-config.xml的8-18行 ,作用是连接数据库
- 17-22行相当于原生项目MyBatis-config.xml的20-22行和编写映射配置文件,作用是绑定MyBatis配置文件 注册Mapper
- 31-33行注册了spring的实现类,相当于原生项目中返回一个sqlSession,id为userMapperImpl
- 可以将7-27行固定代码写到一个固定的spring-dao.xml文件中,需要时在使用import导入,配合applicationContext.xml
- spring-dao.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--Datasource:使用Spring的数据源替换MyBatis的配置 连接数据库-->
<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=true&useUnicode=true&characterEncoding=UTF8"/>
<property name="username" value="root"/>
<property name="password" value="qing"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定MyBatis配置文件 注册Mapper,相当于MyBatis配置文件中的mapper标签-->
<property name="configLocation" value="classpath:Mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/qing/mapper/*.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--创建SQLSession 只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--导入spring-dao.xml主配置文件-->
<import resource="spring-dao.xml"/>
<bean id="userMapperImpl" class="com.qing.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
spring的实现类:
public class UserMapperImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public SqlSessionTemplate getSqlSession() {
return sqlSession;
}
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> queryUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.queryUser();
}
}
实现类简化:
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> queryUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
return mapper.queryUser();
}
}
继承SqlSessionDaoSupport
测试类:
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
System.out.println(userMapper.queryUser().toString());
}
}
测试类可以直接执行userMapperImpl中的queryUser方法
总结:整合后的项目,多了一个userMapperImpl实现类,但却不用在java代码中通过sqlSessionFactory获取sqlSession,也不用专注编写原生MyBatis核心配置文件了,可以将核心配置文件和映射配置文件整合到一个xml中
13 申明式事务
事务ACID原则:
- 原子性
- 一致性
- 隔离性:多个事务同时操作一个资源,防止数据损坏
- 持久性:事务一旦提交,无论系统发生什么问题,结果都不会被影响,数据被持久化的写到存储器中了
spring-dao.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!--Datasource:使用Spring的数据源替换MyBatis的配置 连接数据库-->
...
<!--sqlSessionFactory-->
...
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--创建SQLSession 只能使用构造器注入sqlSessionFactory,因为它没有set方法-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!-- 配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="datasource"/>
</bean>
<!-- 集合AOP实现事务的织入-->
<!-- 配置事务的类-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务 * 代表所有方法-->
<tx:attributes>
<!--<tx:method name="add" propagation="REQUIRED"/>-->
<!--<tx:method name="delete"/>-->
<!--<tx:method name="update"/>-->
<!--<tx:method name="query"/>-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入点-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.qing.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
14、Spring的扩展(面试)
14.1 Bean
14.1.1 Bean的生命周期
/*
UserService->推断构造方法->生成对象(普通对象)->依赖注入(ioc)->初始化前(@PostConstruct)->初始化->初始化后(执行AOP)->代理对象->Bean->destroy
依赖注入:给所有的Autowired属性赋值;
@PostConstruct添加到方法上,给指定的实体类字段属性进行初始化
@Service:底层也是@Component,将一个类标记成一个组件,交给spring容器托管
*/
@Service
public class UserService {
@Autowired
OrderService orderService;
@Autowired
private User admin;
public UserService() {
System.out.println("1");
}
public UserService(OrderService orderService) {
this.orderService = orderService;
System.out.println("2");
}
public UserService(OrderService orderService1,OrderService orderService2) {
this.orderService = orderService;
System.out.println("3");
}
//spring初始化时就执行
@PostConstruct
public void creat(){
admin.setName("qing");
//给admin赋值,一般是从数据库中查询UserMapper:mysql->User对象->admin
}
}
总结:
//1.spring是如何扫描userService中所有的Autowired注解:反射
for (Field field : userService.getClass().getFields()) {
if(field.isAnnotationPresent(Autowired.class)){
field.set(userService,"");
}
}
//2.spring是如何初始化时就执行指定方法,给对象赋值:方法上添加@PostConstruct注解
for (Method method : userService.getClass().getMethods()) {
if(method.isAnnotationPresent(PostConstruct.class)){
method.invoke(userService,"")
}
}
//3.bean初始化时,默认执行无参构造方法,若有多个构造方法(有无参构造),也执行无参构造;若有多个方法(没有无参构造),则会报错;若只有一个构造方法(无参或有参),都默认执行那一个方法
/*
4.spring中的单例Bean:
spring注册bean时,用的是单例bean,同样的名字才能拿到同样的对象,首先会根据类型去获取对象,若同一类型有多个对象名,则再根据名字去获取对象
先ByName在ByType
*/
@Bean
public OrderService orderService1() {
return new OrderService();
}
@Bean
public OrderService orderService2() {
return new OrderService();
}
//当OrderService对象有多个名字时,构造器参数中必须指定具体的参数名orderService1或orderService2才正确
public UserService(OrderService orderService1) {
this.orderService = orderService1;
}
14.1.2 BeanFactory和ApplicationContext区别
- 两者都是spring的容器
- BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能
- BeanFactory使用延迟加载,在启动的时候不会去实例化Bean,只有从容器中拿Bean的时候才会去实例化
ApplicationContext继承BeanFactory接口,是Spring的一各更高级的容器,扩展了许多功能:
- 国际化(MessageSource)
- 访问资源,如URL和文件(ResourceLoader)
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的Dao层
- 同时加载多个配置文件
- AOP(拦截器)
- ApplicationContext在启动的时候就把所有的Bean全部实例化了;也还可以为Bean配置lazy-init=true来让Bean延迟实例化
加载配置文件或注解类:
//注解类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Test.class);
//配置文件
//第一种加载方法,加载的是classpath下的配置文件。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//第二种加载方法,加载的是磁盘路径下的文件。
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("applicationContext.xml");
//第三种加载方法,XmlWebApplicationContext,从web系统中加载。
配置多个上下文:
不同项目使用不同分模块策略,spring配置文件分为:
applicationContext.xml(主文件,包括JDBC配置,hibernate.cfg.xml,与所有的Service与DAO基类)
applicationContext-cache.xml(cache策略,包括hibernate的配置)
applicationContext-jmx.xml(JMX,调试hibernate的cache性能)
applicationContext-security.xml(acegi安全)
applicationContext-transaction.xml(事务)
moduleName-Service.xml
moduleName-dao.xml
14.1.3 Bean的六大作用域
单例模式(Spring默认机制),一个对象只有一份实例,每个容器中只有一个bean实例,由BeanFactory维护
<bean ... scope="singleton"/>
原型模式,每次从容器中get对象时,都会产生一个新的对象,为每一个bean请求提供一个实例
<bean ... scope="prototype"/>
其余的几种模式在web开发时使用
14.1.4 单例Bean是否线程安全
- Spring框架并没有对单例bean进行任何多线程的封装处理
如果Bean是由状态的,则需要开发人员自己进行线程安全的保证,最简单的的办法就是将多态bean的作用域由“singleton”变更为“prototype”
- 有状态:有数据存储功能,pojo,如一个User类
- 无状态:不会保存数据,controller、dao、service
- Spring使用ThreadLocal解决线程安全问题
- spring如何保证事务获取同一个Connection:Dao操作数据库的Connection是有状态的,比如数据库事务,而spring的事务管理器使用ThreadLocal为不同线程维护了一套独立的connection副本,保证了线程安全
有状态数据不安全举例:
public class TestManagerImpl implements TestManager{
private User user;
public void deleteUser(User e) throws Exception {
user = e ; //1
prepareData(e);
}
public void prepareData(User e) throws Exception {
user = getUserByID(e.getId()); //2
.....
//使用user.getId(); //3
.....
.....
}
}
/*
如果有2个用户访问,都调用到了该Bean.
假定为user1,user2
当user1 调用到程序中的1步骤的时候,该Bean的私有变量user被付值为user1
当user1的程序走到2步骤的时候,该Bean的私有变量user被重新付值为user1_create
理想的状况,当user1走到3步骤的时候,私有变量user应该为user1_create;
但如果在user1调用到3步骤之前,user2开始运行到了1步骤了,由于单态的资源共享,则私有变量user被修改为user2
这种情况下,user1的步骤3用到的user.getId()实际用到是user2的对象。
*/
14.1.5 Bean的创建流程
四个主要阶段:实例化->属性赋值->初始化->销毁
- 实例化:new xxx(),并使用BeanDefinition保存;两个实例化时机:1. 当客户端向容器申请一个Bean时,2. 当容器初始化一个Bean时发现还需要依赖另一个Bean
- 依赖注入:设置对象属性,Spring通过BeanDefinition找到对象依赖的其他对象,并将这些对象值赋值给当前对象
- 处理Aware接口:Spring检测对象是否实现了xxxAware接口:BeanNameAware、BeanFactoryAware、ApplicationContextAware等,如果实现了就会调用对应的方法;
- BeanPostProcessor前置处理:调用postProcessBeforeInitialization方法
- InitializingBean:Spring检测对象如果实现了这个接口,就会执行它的afterPropertiesSet()方法,定制初始化逻辑
- init-method:如果Spring发现Bean配置了这个属性,就会调用它的配置方法,执行初始化逻辑 @PostConstruct
- BeanPostProcessor后置处理:调用postProcessAfterInitialization方法
- Bean初始化完成
14.2 事务
14.2.1 事务的实现方式和原理
- Spring框架有两种使用事务的方式,一种编程式的,一种是申明式的(@Transactional)
- 编程式事务使用TransactionTemplate模板,需自定义,比较麻烦
- 下面主要介绍申明式事务:
- 在某个方法上增加@Transactional注解,就可以开启事务,这个方法所有的sql都会在一个事务中执行,统一成功或失败
- 原理:Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法时,如果这个方法上存在@Transaction注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚
- 针对哪些异常回滚,是可以配置的,利用@Transaction注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚
14.2.2 事务的隔离级别
Spring的事务隔离级别有四个:READ_UNCOMMITTED(未提交读)、READ_COMMITTED(提交读、不可重复读)、REPEATABLE_READ(可重复读)和SERIALIZABLE(可串行化),还有一个,是数据库默认的隔离级别DEFAULT,MySQL默认是REPEATABLE_READ
1、READ_UNCOMMITTED:
- 一个事务可以读取到另一个事务未提交的事务记录。
出现的问题:脏读(dirty read)、幻读(phantom read)、不可重复读(on-repeatable read)
- 脏读:读到了其他事务还没有提交的数据。事务A开启,写入一条记录,这时候,事务B读入数据,读到了这条记录,但是,之后事务A回滚。因此,事务B读到的数据不是有效的
- 不可重复读:发现两次读取某数据的结果不同。在事务A两次读取的过程之间,事务B修改了那条记录并进行提交。因此,事务A前后两次读取的记录不一致
- 幻读:两次读取到的数据数量不一样。事务A两次从数据库读取一系列记录,期间,事务B插入了某条记录并提交。事务A第二次读取时,会读取到事务B刚刚插入的那条记录。在事务期间,事务A两次读取的一系列记录不一致
2、READ_COMMITTED:
- 一个事务只能读取到已经提交的记录,不能读取到未提交的记录
- 出现问题:不可重复读、幻读
3、REPEATABLE_READ:
- 一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的,相同的
- 出现问题:幻读
4、SERIALIZABLE:
- 最强的隔离级别。事务执行时,会在所有级别上加锁,比如read和write时都会加锁,仿佛事务是以串行的方式进行的,而不是一起发生的
- 可以防止脏读、幻读、不可重复读,但性能会下降
14.2.3 事务的传播机制
配置:
@Transactional(propagation=Propagation.***)
14.2.4 事务什么时候失效
- 数据库本身不支持事务,例如使用MySQL且引擎是MyISAM
- 没有被Spring管理
- 内部调用: 不带事务的方法调用该类中带事务的方法。因为Spring的回滚是用过代理模式生成的,如果是一个不带事务的方法调用该类的带事务的方法,直接通过
this.xxx()
调用,而不生成代理事务,所以事务不起作用。解决办法:拆类或者添加@Autowired注解 - 方法不是public,@Transactional注解只能应用于public方法
- 继承异常错误:Spring的事务默认是对RuntimeException进行回滚,而不继承RuntimeException的不回滚
14.3 Spring的循环依赖问题
循环依赖:多个对象之间存在循环的引用关系,如A对象创建时需要引用B对象,而B对象创建时又需要引用A对象,则就会出现“先有鸡还是先有蛋的问题”。
解决办法:
- 添加@lazy注解:解决构造方法的循环依赖问题
- 二级缓存:解决普通对象的循环依赖,二级缓存可以保存new出来的不完整的对象,当在单例池中找不到依赖的属性时,就会从二级缓存中取
- 三级缓存:如果引用的依赖对象配置了AOP,那么二级缓存拿到的属性就不满足需求;所以增加三级缓存保存所有对象的动态代理信息
14.4 Spring中的设计模式
1.简单工厂
原理:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类
实现:Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定
bean的额外处理:通过Spring接口的暴露,在实例化bean的阶段可以进行一些额外的处理,如:各种的Aware接口、BeanPostProcessor接口、InitializingBean接口,这些额外的处理只需要让bean实现对应的接口即可,那么spring就会在bean的生命周期调用我们实现的接口来处理该bean。
2.工厂方法
原理:实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值,如:
说明:该bean,因为实现了FactoryBean接口,所以返回的不是 SqlSessionFactoryBean 的实例,而是它的SqlSessionFactoryBean.getObject() 的返回值。
3.单例模式
原理:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
Spring依赖注入Bean实例默认是单例的。
实现:spring中的单例模式完成了后半句话,即提供了全局的访问点BeanFactory。但没有从构造器级别去控制单例,这是因为spring管理的是任意的java对象。
4.适配器模式
原理:HandlerAdatper根据Handler规则执行不同的Handler
实现:SpringMVC中的适配器HandlerAdatper,HandlerAdatper使得Handler的扩展变得容易,只需要增加一个新的Handler和一个对应的HandlerAdapter即可。因此Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了
5.装饰器模式
原理:动态地给一个对象添加一些额外的职责
实现:Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator
6.代理模式
原理:切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的
实现:AOP底层,就是动态代理模式的实现。动态代理、静态代理
7.观察者模式
spring的事件驱动模型使用的是观察者模式 ,Spring中Observer模式常用的地方是listener的实现
8.策略模式
Spring框架的资源访问Resource接口 。该接口提供了更强的资源访问能力,Spring框架本身大量使用了 Resource 接口来访问底层资源。
9.模版方法模式
原理:父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现
采用模板方法模式是为了以一种统一而集中的方式来处理资源的获取和释放,如JdbcTempalte