spring学习
Spring
一.简介
1.Spring Framework百度百科
2.Spring 优点
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的(很小,mybatis也是)、非入侵式的开发框架(引入spring不会改变你的代码情况)
- 控制反转(IOC),面向切面编程(AOP)
- Beans 组件和 Context 组件是实现IOC和依赖注入的基础,AOP组件用来实现面向切面编程。
- 支持事务的处理,支持框架的整合
2.1 Spring 的重要模块
- Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。这些模块是:核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。
- Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依赖注入功能。
- Spring Aspects : 该模块为与AspectJ的集成提供支持。
- Spring AOP :提供了面向切面的编程实现。
- Spring JDBC : Java数据库连接。
- Spring JMS :Java消息服务。
- Spring ORM : 用于支持Hibernate等ORM工具。
- Spring Web : 为创建Web应用程序提供支持。
- Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
3.学习Spring之后用在哪?
- Spring Boot 是构建所有基于Spring的应用程序的起点,Spring Boot旨在通过最少的Spring配置来实现启动并进行
- spring的弊端:发展太久后,配置会十分繁琐,人称“配置地域”
4.Spring Framework的作者(Rod Johnson)
5.学习Spring Framework需要经常访问的官网
- Spring Framework官网:https://spring.io/projects/spring-framework#overview
- Spring Framework官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html(最新版本)
- Spring Framework 中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference(版本5.1.3)
- GitHub官网: Spring Framework的源代码以及版本更新地址:
二.ioC
在IDEA项目中导入jar包,在pom.xml上面配置spring web mvc的依赖和spring jdbc的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<!--spring操作数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.4</version>
</dependency>
1.ioC理论推导
一个最原始的ioC
-
先写一个UserDao接口
public interface UserDao { public void getUser(); }
-
再写几个UserDao的实现类来实现这个接口的getUser()方法
public class UserDaoImpl implements UserDao{ public void getUser() { System.out.println("默认获取用户数据!"); } } public class UserDaoMysqlImpl implements UserDao{ public void getUser() { System.out.println("获取mysql数据库的数据!"); } }
-
再写一个UserService接口,方法和UserDao一样
public interface UserService { void getUser(); }
-
再写一个UserService的实现类来,实现UserService接口的方法,创建UserDao实现类的对象,在这个UserService方法中调用dao层对象的方法,从而起到应用dao层的作用
public class UserServiceImpl implements UserService{ //之前:UserDao userDao=new UserDaoImpl();..... private UserDao userDao; //利用set进行动态实现dao层实现类的注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void getUser() { userDao.getUser(); } }
-
写一个测试类,来调用业务层(service)
public class Test { public static void main(String[] args) { //用户实际调用的是业务层,dao层他们不接触 UserService userService=new UserServiceImpl(); ((UserServiceImpl) userService).setUserDao(new UserDaoImpl()); userService.getUser(); ((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl()); userService.getUser(); } } /*默认获取用户数据! 获取mysql数据库的数据!
-
之前,程序员需要在业务层(service)主动创建(用户需要的接口)对象,用户在应用层调用业务层的接口,控制权在程序员手里
-
控制反转,在业务层使用set注入接口的方式,让程序员不再具有主动性,而是变成被动地接受对象,主动权在用户上,用户决定调用(自己需要的接口)对象
-
这种思想,从本质上解决了问题,程序员不再用去管业务层对对象的创建,系统的耦合性大大降低,可以更加专注于业务的实现上(写dao层的接口实现,扩展业务)
2.ioC的本质
-
IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。
-
对象由Spring来创建,管理,装配
-
IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,在测试类中获取spring的上下文对象---applicationContext,然后通过bean的id获取你想要的对象,完全不用考虑对象是如何被创建出来的。
3.Spring中利用Bean装配的方式实现ioC
前面都是运用xml 显示配置的方式对Bean进行装配
-
3.1 HelloSpring程序的创建
-
新建一个Hello类存放在pojo包中
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 + '\'' + '}'; } }
-
写一个xml配置,实现ioC(beans.xml)------(模板从Spring Framework官方文档中获取)
<?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 来创建对象,在spring中我们称对象为bean 类型(class) 变量名(id) =new 类型(class) Hello hello=new Hello(); property==给该类中的属性或对象 赋值(set) --> <bean id="hello" class="com.lyj.pojo.Hello"> <property name="str" value="hellospring"/> </bean> </beans>
-
在应用层用户进行测试(不用创建对象)
public class Test { public static void main(String[] args) { //获取spring上下文对象!!! ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象都在spring中管理,要去使用,直接去根据bean的id来取 Hello hello = (Hello) context.getBean("hello"); //得到你想要这个对象的东西 System.out.println(hello.getStr()); } } /* 输出:hellospring
注意:此处hello对象由spring创建,hello对象的属性由spring来设置(装配),如果hello对象所对应的类Hello没有set方法,则没法进行注入,无法通过xml文件进行赋值
-
-
3.1将上面1中的原始ioC,利用spring中xml配置的方式实现ioC
-
写一个xml配置,实现ioC(beans.xml)
<!--使用Spring 来创建对象,在spring中我们称对象为bean--> <bean id="userImpl" class="com.lyj.dao.UserDaoImpl"/> <bean id="mysqlImpl" class="com.lyj.dao.UserDaoMysqlImpl"/> <bean id="oracleImpl" class="com.lyj.dao.UserDaoOracleImpl"/> <!--UserServiceImpl类里面有一个UserDao对象,我们通过Spring对其赋值--> <bean id="userServiceImpl" class="com.lyj.service.UserServiceImpl"> <property name="userDao" ref="userImpl"/> </bean> <!--ref:引用spring中创建好的对象 value:引用具体的值,基本数据类型的数据+String类等常用类的数据 --> </beans>
-
用户在应用层进行测试(不用创建对象)
public class Test { public static void main(String[] args) { //获取spring上下文对象!!! ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //我们的对象都在spring中管理,要去使用,直接去根据bean的id来取 UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl"); //得到你想要这个对象的东西 userServiceImpl.getUser(); } } /* 输出:默认获取用户数据!
- 按照上面的步骤,我们就彻底不用改程序了(理论推导中的原始版本的实现ioC,虽然不用修改业务层service里面接口实现类,但是还是需要修改 用户的测试类,即修改了程序),而现在利用spring,要实现不同的操作,只需要改变xml配置文件中 赋值是什么即可
- 对象由Spring来创建,管理,装配
-
4.ioC创建对象的方式
-
使用无参构造创造对象(这是默认的!!!!)
-
若想使用有参构造对象提供如下方法
-
public class User { public User(String name){ this.name=name; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+name); } }
-
<!--第一种方法:直接通过参数名来设置有参构造--> <bean id="user" class="com.lyj.pojo.User"> <constructor-arg name="name" value="lyj"/> </bean> <!--第二种方法:参数下标赋值法,来进行有参构造--> <bean id="user" class="com.lyj.pojo.User"> <constructor-arg index="0" value="lyj"/> </bean> <!--第三种方法:通过参数类型设置有参构造--> <bean id="user" class="com.lyj.pojo.User"> <constructor-arg type="java.lang.String" value="lyj"/> </bean>
三种选其一
-
-
若无参有参都想一起用,必须在类中写好无参构造+有参构造,否则无参将不能使用
-
需要注意的是:当spring容器被创建的时候( ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");)就已经把容器里面的对象都创建了,所以通过context.getBean()方法,可以获得容器里面的对象
三.Spring的配置
1.别名
<!--别名:如果添加了别名,我们也可以使用别名来获取这个对象-->
<alias name="user" alias="u1"/>
2.bean的配置
<!--id:对象名
class:对象对应的全限定名:包名+类型
name:也是别名,而且可以取多个别名,通过“空格” “,” “;"都可以取别名-->
<bean id="user" class="com.lyj.pojo.User" name="u2 u3,u4;u5">
<constructor-arg name="name" value="lyj"/>
</bean>
@org.junit.Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml");
User user = (User) context.getBean("u5");//u1--u5都可以打印
user.show();
}
/*name=lyj
3.import(导入)
-
import主要应用于团队开发,他可以将多个配置文件,导入合并到一个文件
-
应用:一个项目有多人开发,每一个人负责不同的类的开发,而这些类需要注册在不同的xml配置文件中,我们可以利用import将所有人的xml配置文件合并在一个总的xml配置文件(applicationContext.xml)中。使用时,直接使用总的xml即可
applicationContext.xml总配置文件中加入以下
<import resource="beans.xml"/> <import resource="pojo.xml"/>
测试:
@org.junit.Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) context.getBean("u5"); user.show(); } /*name=lyj
注意:如果不同xml导入到applicationContext.xml总配置文件中,同id的bean,后面导入的xml会覆盖前面xml的bean
四.依赖注入(DI)
1.构造器注入
-
前面已经有体现(见二.ioC.4中的有参构造)
-
构造器注入:保证了一些必要的属性在Bean实例化时就设置,并且确保了bean实例在实例化后就可以使用.
-
在类中,不用为属性设置setter方法,只需提供构造方法即可(该类中只需有一个有参构造)
-
-
public class User {
private String name;
public User(String name){
this.name=name;
}
}
```
- 在构造文件中配置该类bean,并配置构造器,在配置构造器中用
```xml
2.set方式注入[重点]
对象bean来自的类必须要有set方法,否则没法注入
-
依赖注入:主要是set注入
- 依赖:对象(bean)的创建依赖于容器
- 注入:对象(bean)中的所有属性,由容器注入
-
下面对各种类型数据的set方式注入进行举例
-
写好需要所需类Student 中测试对象的属性(包括其类型)
//已经写好其get/set方法,此处省略 public class Student { private String name; private Address address;//复杂类型 private String books[]; private List<String> hobbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; }
-
复杂类型Address
//复杂类型 public class Address { public String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; }
-
配置文件pojo.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="address1" class="com.lyj.pojo.Address"> <property name="address" value="广州"/> </bean> <bean id="student" class="com.lyj.pojo.Student"> <!--第一种注入,普通值注入:Value--> <property name="name" value="lyj"/> <!--第二种注入,复杂类型bean对象注入:ref--> <property name="address" ref="address1"/> <!--第三种注入,数组注入--> <property name="books"> <array> <value>水浒传</value> <value>三国演义</value> <value>西游记</value> <value>红楼梦</value> </array> </property> <!--第四种注入,List集合注入--> <property name="hobbys"> <list> <value>打篮球</value> <value>看电影</value> <value>看视频</value> </list> </property> <!--第五种注入,Set集合注入--> <property name="games"> <set> <value>王者荣耀</value> <value>和平精英</value> <value>LOL</value> </set> </property> <!--第六种注入,Map集合(键值对)注入--> <property name="card"> <map> <entry key="身份证" value="1111111111"/> <entry key="学生卡" value="2222222222"/> <entry key="银行卡" value="3333333333"/> </map> </property> <!--第七种注入,Properties注入--> <property name="info"> <props> <prop key="性别">男</prop> <prop key="身高">181cm</prop> <prop key="体重">63kg</prop> </props> </property> <!--第八种注入,null注入--> <property name="wife"> <null/> </property> </bean> </beans>
-
测试类
public class Test { @org.junit.Test public void test01(){ ApplicationContext context = new ClassPathXmlApplicationContext("pojo.xml"); Student student = (Student) context.getBean("student"); System.out.println(student); } } /* Student{ name='lyj', address=Address{address='广州'}, books=[水浒传, 三国演义, 西游记, 红楼梦], hobbys=[打篮球, 看电影, 看视频], card={身份证=1111111111, 学生卡=2222222222, 银行卡=3333333333}, games=[王者荣耀, 和平精英, LOL], wife='null', info={性别=男, 身高=181cm, 体重=63kg} }
-
3.拓展方式注入(C命名和P命名空间的注入)
-
Spring framework官方文档这一块的位置
-
写好需要所需类User 中测试对象的属性(包括其类型)
//写好get/set方法,此处省略 public class User { private String name; private int age; public User(){ } public User(String name,int age){ this.name=name; this.age=age; } }
-
配置文件user_pojo.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:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--c命名空间注入,是通过构造器注入:与construct-args相对应--> <bean id="user1" class="com.lyj.pojo.User" c:name="lyj" c:age="21"/> <!--p命名空间,是用set方法注入,可以直接注入属性值:与property相对应--> <bean id="user2" class="com.lyj.pojo.User" p:name="zyx" p:age="18"/> </beans>
-
测试类
@org.junit.Test public void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("user_pojo.xml"); User user1 = context.getBean("user1", User.class); System.out.println(user1); User user2 = context.getBean("user2", User.class); System.out.println(user2); } /* User{name='lyj', age=21} User{name='zyx', age=18} */
-
需要注意的问题:
-
1.c命名空间注入,是通过构造器注入:与construct-args相对应,p命名空间,是用set方法注入,可以直接注入属性值:与property相对应
-
2.p命名和c命名空间不能直接使用,需要在装配bean的xml文件中导入xml约束
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
-
解决约束没法导入的问题
-
五.Bean的作用域(Scopes)
Scope | Description |
---|---|
singleton | (默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。在spring IoC容器仅存在一个Bean实例 |
prototype | 将单个 bean 定义的作用域限定为任意数量的对象实例。每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。每次请求都会创建一个新的 bean 实例。 |
request | 将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。每次HTTP请求都会创建一个新的Bean。仅在可感知网络的 Spring ApplicationContext 中有效。 |
session | 将单个 bean 定义的范围限定为 HTTP Session 的生命周期。同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
application | 将单个 bean 定义的范围限定为ServletContext 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
websocket | 将单个 bean 定义的范围限定为WebSocket 的生命周期。仅在可感知网络的 Spring ApplicationContext 上下文中有效。 |
1.单例模式singleton(Spring默认机制)
<bean id="user1" class="com.lyj.pojo.User" c:name="lyj" c:age="21" scope="singleton"/>
2.原型模式prototype
<bean id="user2" class="com.lyj.pojo.User" p:name="zyx" p:age="18" scope="prototype"/>
3.其余模式
- 其余作用域scope的设置,只有在web开发才会用到
4. Spring 中的单例 bean 的线程安全问题了解吗?
- 的确是存在安全问题的。因为,当多个线程操作同一个对象的时候,对这个对象的成员变量的写操作会存在线程安全问题。
但是,一般情况下,我们常用的 Controller
、Service
、Dao
这些 Bean 是无状态的。无状态的 Bean 不能保存数据,因此是线程安全的。
常见的有 2 种解决办法:
- 在类中定义一个
ThreadLocal
成员变量,将需要的可变成员变量保存在ThreadLocal
中(推荐的一种方式)。 - 改变 Bean 的作用域为 “prototype”:每次请求都会创建一个新的 bean 实例,自然不会存在线程安全问题。
六.Bean的装配
- 在Spring中有三种装配方式
- 在xml中显示的配置(前面运用的就是这种)(重要)
- 在xml中自动装配(隐式)(重要)
- 在java中显示的配置
七.Bean的自动装配
-
环境搭建
public class People { private String name; private Dog dog; private Cat cat; public String getName() { return name; } public void setName(String name) { this.name = name; } public Dog getDog() { return dog; } public void setDog(Dog dog) { this.dog = dog; } public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } }
1.byName自动装配
<bean id="cat" class="com.lyj.pojo.Cat"/>
<bean id="dog" class="com.lyj.pojo.Dog"/>
<bean id="people1" class="com.lyj.pojo.People" autowire="byName">
<property name="name" value="lyj"/>
</bean>
- byName自动装配,需要保证id唯一,它会在spring容器的上下文中自动查找,和自己对象类中的set方法后面一样值(小写)的bean_id
- 如这里需要自动装配对象是people,它所在类People中的set方法是setCat()和setDog(),那么前面的bean_id只能为cat和dog(cat1,dog1,Cat,Dog均不行)
2.byType自动装配
<bean id="cat1" class="com.lyj.pojo.Cat"/>
<bean id="dog1" class="com.lyj.pojo.Dog"/>
<!--
<bean class="com.lyj.pojo.Cat"/>
<bean class="com.lyj.pojo.Dog"/>
-->
<bean id="people2" class="com.lyj.pojo.People" autowire="byType">
<property name="name" value="lyj"/>
</bean>
- byType自动装配,需要保证class类型唯一,它会在spring容器的上下文中自动查找,和自己对象类型相同的bean,即使你不写id一样可以找到
3.使用注解进行自动装配
使用注解自动装配和使用xml显示装配,原理都是反射
注解自动装配是:反射直接给属性赋值
xml显示装配是:反射获得set方法赋值。
-
使用注解时需要在配置文件上准备
-
导入context约束
-
配置注解的支持(< context:annotation-config/>)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans 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>
-
-
3.1@Autowired
-
直接在属性上使用,也可以在其Set方法上使用
-
使用Autowired我们可以不用编写Set方法就可以对属性赋值,前提是:
-
这个属性的类型class在IOC(Spring)容器中存在(byType)
-
如果有多个相同的类型class,这个自动装配的属性名字在IOC(Spring)容器中存在,该属性的名字与bean_id名相同!!!(byName)
-
<?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"> <bean id="cat" class="com.lyj.pojo.Cat"/> <bean id="dog" class="com.lyj.pojo.Dog"/> <!-- <bean id="cat1" class="com.lyj.pojo.Cat"/> <bean id="dog1" class="com.lyj.pojo.Dog"/>--> <bean id="people" class="com.lyj.pojo.People"> <property name="name" value="lyj"/> </bean> <context:annotation-config/> </beans>
public class People { private String name; @Autowired private Dog dog; @Autowired private Cat cat; public String getName() { return name; } public void setName(String name) { this.name = name; } public Dog getDog() { return dog; } public Cat getCat() { return cat; } @Override public String toString() { return "People{" + "name='" + name + '\'' + ", dog=" + dog + ", cat=" + cat + '}'; } }
-
-
3.2@Qualifier
- 当@Autowired自动装配环境比较复杂时(容器里面的bean的类型不满足byType唯一和bean的id不满足byName),自动装配无法通过一个注解【@Autowired】完成的时候,我们可以利用@Qualifier(value="xxx")来配合@Autowired的使用,作用是指定一个唯一id的bean对象注入
<bean id="cat1" class="com.lyj.pojo.Cat"/> <bean id="dog1" class="com.lyj.pojo.Dog"/> <bean id="cat2" class="com.lyj.pojo.Cat"/> <bean id="dog2" class="com.lyj.pojo.Dog"/>
public class People { private String name; @Autowired @Qualifier(value = "dog1") private Dog dog; @Autowired @Qualifier(value = "cat1") private Cat cat; }
-
3.3@Resource
- 这个注解也是用来自动装配的,但是它不在spring包内,而是在java包内
- @Resource与@Autowired区别
- @Resource(容器里面的bean的id不满足byName和和bean的类型不满足byType唯一)时,可以用@Resource(name="xxxx")的 形式来确定,不用和其他注解进行搭配使用
- 执行顺序不同:@Resource先通过byName方式实现,而@Autowired先通过byType方式实现
八.使用注解开发
-
要使用注解开发,必须要保证aop的包导入
-
使用注解时需要在配置xml文件上准备
-
导入context约束
-
配置注解的支持(< context:annotation-config/>)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans 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:component-scan base-package="com.lyj"/> <context:annotation-config/> </beans>
-
1.使用注解将 类 被spring管理----装配bean
-
@Component:将这个注解放在类上,意味着这个类被Spring管理,会自己装配bean,该bean的id是该类的小写形式
//@Component(组件)等价于 <bean id="user" class="com.lyj.pojo.User"/> @Component public class User { public String name; }
@org.junit.Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
System.out.println("name="+user.name);
}
/*name=null(因为name没有注入值)
2.使用注解实现属性的注入
@Component
public class User {
//@Value("lyj")等价于<property name="name" value="lyj"/>
@Value("lyj")
public String name;
}
/*在测试类中:name=lyj
3.衍生的注解(用法相同)
- @Component有几个衍生注解,在WEB开发中,会按照MVC三层架构用在不同的层之中
- dao【@Repository】
- service【@Service】
- controller【@Controller】
- 这四个注解的功能是相同的,都是代表某个类注册到Spring容器之中,装配Bean
4.自动装配bean(见第七)
5.使用注解实现作用域
@Component
//@Scope("singleton")等价于scope="singleton"作用域为单例模式
@Scope("singleton")
public class User {
//@Value("lyj")等价于<property name="name" value="lyj"/>
@Value("lyj")
public String name;
}
6.xml与注解的用途
- xml主要用于管理bean对象,适用于任何场所,维护简单方便
- 注解则是主要负责完成属性的注入更加便捷,但是它只试用与自己的类,其他类使用不了,维护相对复杂
九.使用Java的方式装配Bean
这是完全不使用Spring的xml配置,全权使用java来配置Spring容器,装配bean
-
实体类
//@Component(组件)等价于 <bean id="user" class="com.lyj.pojo.User"/> @Component //@Scope("singleton")等价于scope="singleton"作用域为单例模式 @Scope("singleton") public class User { //@Value("lyj")等价于<property name="name" value="lyj"/> @Value("lyj") public String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
利用到@Configuration将一个类变成一个配置类(代替配置文件xml)
@Configuration //此处@Configuration本来就是一个@Component,意味着这个类被Spring管理,会自己装配bean @ComponentScan("com.lyj.pojo") //代表扫描包,这个包内的注解可以被识别 @Import(xxx.class) //导入其他的配置类,将其他的配置xml导入到一起 public class LyjConfig(){ @Bean //相当于注册一个bean 标签 //这个方法的名字,相当于bean标签中的id属性 //这个方法的返回类型,相当于bean标签中的class属性 public User user(){ } }
-
测试类
new AnnotationConfigApplicationContext("LyjConfig.class"); //只能通过AnnotationConfig得到容器的上下文,通过配置类的class对象来加载
-
纯java配置中,在SpringBoot随处可见
十.代理模式
代理模式是SpringAOP的底层,面试必考
- 代理的简单模型
1.静态代理
-
1)抽象角色(被代理的接口):一般会使用接口或者抽象类,里面会写一个方法(这是一个功能如上面的:租房)
public interface Rent { public void rent(); }
-
2)真实角色:被代理的角色
public class Host implements Rent { @Override public void rent() { System.out.println("房东要租房呀呀呀!!!"); } }
-
3)代理角色:代理真实的角色,除了代理真实的角色所需,还有一些附属操作(公共业务)
public class Proxy implements Rent{ private Host host; public Proxy(){} public Proxy(Host host){ this.host=host; } @Override public void rent() { host.rent(); seeHouse(); sign(); fare(); } //看房 public void seeHouse(){ System.out.println("中介带你看房!"); } //签租赁合同 public void sign(){ System.out.println("满意的话,请和中介签租赁合同!"); } //收中介费 public void fare(){ System.out.println("成交后,收中介费!"); } }
-
4)客户:访问代理角色的人
public class Client { public static void main(String[] args){ //房东租房子 Host host=new Host(); //联系中介 Proxy proxy = new Proxy(host); //不用联系房东,直接找中介租房 proxy.rent(); } } /* 房东要租房呀呀呀!!! 中介带你看房! 满意的话,请和中介签租赁合同! 成交后,收中介费!
-
静态代理的优缺点
2.加深理解
-
面向切面编程(AOP)是代理模式的具体体现!!!
-
在不改变原有代码的前提下,横切进代码中,通过代理角色,扩展业务的功能
-
具体举例:
dao层的主程序不进行改变,在service层通过代理,扩展业务功能,如可以加一个日志Log功能
3.动态代理
-
3.1动态代理的基本概念
-
动态代理和静态代理的角色是一样的
-
动态代理的代理类是动态生成的,不是我们自己写好的
-
动态代理是对接口的代理
-
动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口-------JDK动态代理(此处以这个为例)
- 基于类:cglib
- java字节码实现:javasist
这些方法能够简单且快速--动态改变类的结构或者动态!!!
-
-
3.2动态代理需要用到的一个类和接口
-
InvocationHandler(接口):调用处理程序 (并返回一个结果)
-
public interface InvocationHandler
-
InvocationHandler
是由代理实例(new的一个代理) 的调用处理程序 实现的接口 。 -
每个代理实例(new的一个代理)都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的
invoke
方法。
-
-
invoke
Object invoke(Object proxy, 方法 method, Object[] args) throws Throwable
- 处理代理实例上(new的一个代理)的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
-
-
Proxy(类):代理
-
public class Proxy extends Object implements Serializable
Proxy
提供了创建动态代理类和实例的静态方法
-
newProxyInstance
public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
- 返回指定接口的代理类的实例(new的一个代理),该接口将方法调用分派给指定的调用处理程序。(同时动态生成了代理类)
-
-
-
3.3案例分析
-
被代理的接口(抽样角色)
public interface Rent { public void rent(); }
-
真实角色(被代理的对象)
public class Host implements Rent { @Override public void rent() { System.out.println("房东要租房呀呀呀!!!"); } }
-
需要一个类,自动生成代理类和代理的实例(new的一个代理),执行invoke的方法
public class ProxyInvocationHandler implements InvocationHandler { //被代理的指定接口 private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //利用Proxy类中的 newProxyInstance方法得到指定接口的代理实例(new的一个代理), // 同时该接口将方法调用分派给指定的调用处理程序 //动态生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }//此处的getClass是获得反射对象(代理类),getInterfaces()是获得反射对象这个类的接口 //继承了InvocationHandler接口,就需要重新写该接口的方法 //这个方法用来处理代理实例(new的一个代理),并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //利用invoke来执行接口下面的方法 Object result = method.invoke(rent, args); seeHouse(); sign(); fare(); return result; } //看房 public void seeHouse(){ System.out.println("中介带你看房!"); } //签租赁合同 public void sign(){ System.out.println("满意的话,请和中介签租赁合同!"); } //收中介费 public void fare(){ System.out.println("成交后,收中介费!"); } }
-
客户:访问代理角色的人
public class Client { public static void main(String[] args) { //真实角色 Host host =new Host(); //代理角色:不存在 //于是通过调用 处理程序InvocationHandler ProxyInvocationHandler pih=new ProxyInvocationHandler(); //来处理我们要调用的接口的对象 pih.setRent(host);//Host继承了Rent----多态 //动态生成代理类 Rent proxy = (Rent) pih.getProxy();//动态代理是对接口的代理--所以这里的代理类的类型不是Host proxy.rent(); } } /* 房东要租房呀呀呀!!! 中介带你看房! 满意的话,请和中介签租赁合同! 成交后,收中介费!
-
-
3.4一个接口中的多个方法的动态代理
-
被代理的接口(抽样角色)
public interface User { public void add(); public void delete(); public void update(); public void select(); }
-
真实角色(被代理的对象)
public class UserImpl implements User{ @Override public void add() { System.out.println("增加一个用户"); } @Override public void delete() { System.out.println("删除一个用户"); } @Override public void update() { System.out.println("更新一个用户"); } @Override public void select() { System.out.println("查询一个用户"); } }
-
需要一个类,自动生成代理类和代理的实例(new的一个代理),执行invoke的方法
public class ProxyInvocationHandler implements InvocationHandler { //被代理的指定接口 private Object target; public void setTarget(Object target) { this.target = target; } //利用Proxy类中的 newProxyInstance方法得到指定接口的代理实例, // 同时该接口将方法调用分派给指定的调用处理程序 //动态生成代理类,返回一个代理实例 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); }//此处的getClass是获得反射对象(代理类),getInterfaces()是获得反射对象这个类的接口 //继承了InvocationHandler接口,就需要重新该接口的方法 //这个方法用来处理代理实例,并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //利用invoke来执行接口下面的方法 log(method.getName());//log(要使用的方法的名字)----反射 Object result = method.invoke(target,args); return result; } //增加一个日志log public void log(String msg){ System.out.println("使用了"+msg+"方法"); } }
-
客户:访问代理的人
public class Client { public static void main(String[] args) { //代理实例 UserImpl userImpl = new UserImpl(); //没有代理类 //于是通过调用 处理程序InvocationHandler ProxyInvocationHandler pih=new ProxyInvocationHandler(); //来处理我们要调用的接口的对象 pih.setTarget(userImpl);//UsertImpl继承了User接口----多态 //动态生成代理类 User proxy = (User) pih.getProxy();//动态代理是对接口的代理--所以这里的代理类的类型不是UserImpl proxy.add(); proxy.delete(); proxy.update(); proxy.select(); } } /* 使用了add方法 增加一个用户 使用了delete方法 删除一个用户 使用了update方法 更新一个用户 使用了select方法 查询一个用户
-
-
3.5动态代理的优点
-
静态代理的全部优点
- 静态代理的优缺点
-
一个动态代理类代理的是一个接口,一般就是对应一类业务
-
一个动态代理类可以代理多个类,只要实现同一个接口即可(被代理的对象只要实现了同一个接口,就可以用一个动态代理代理全部的真实角色)
-
十一.AOP
-
面向切面编程(AOP)是代理模式的具体体现!!!
-
在不改变原有代码的前提下,横切进代码中,通过代理角色,扩展业务的功能
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果被代理的对象(真实角色),实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的被代理对象(真实角色),就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib ,这时候Spring AOP会使用 Cglib 生成一个被代理对象的子类来作为代理
1.什么是AOP
2.AOP在spring中的一些名词
- 2.1
-
2.2在bean装配的xml配置文件中,advice的切入类型
3.使用Spring实现AOP
-
使用Spring中的AOP需要导入一个AOP织入依赖包(在pom.xml)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency>
-
3.1方式一:使用Spring的接口!!
定义一个类,来继承Spring中的接口后,spring中有很多接口,每个接口实现的通知(切入业务方法的位置,用法)不一样,最后在spring容器(装配bean的xml文件)中来实现AOP
-
指定接口的业务
public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
-
指定对象的业务
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加一个用户"); } @Override public void delete() { System.out.println("删除一个用户"); } @Override public void update() { System.out.println("更新一个用户"); } @Override public void select() { System.out.println("查询一个用户"); } }
-
继承Spring的接口,得到横切关注点的类
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:返回值 @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(method.getName()+"方法执行完毕,返回:"+returnValue); } }
-
利用spring的AOP,将横切关注点的类横切入指定业务之中(利用bean装配的的Xml文件)
-
首先需要引入spring-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" 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"> </beans>
-
然后装配bean,配置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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.lyj.service.UserServiceImpl"/> <bean id="beforeLog" class="com.lyj.log.BeforeLog"/> <bean id="AfterLog" class="com.lyj.log.AfterLog"/> <!--配置aop--> <aop:config> <!--切入点pointcut,experssion表达式--> <aop:pointcut id="pointcut1" expression="execution(* com.lyj.service.UserServiceImpl.*(..))"/> <!--执行切入的类型:advisor(环绕通知)--> <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut1"/> <aop:advisor advice-ref="AfterLog" pointcut-ref="pointcut1"/> </aop:config> </beans>
- execution(* (修饰词,可以不写)* (返回值类型)* (类名)* (方法名)* (参数) )
- execution(* com.lyj.service.UserServiceImpl.*(..))--------此处将修饰词省略,第一个 *代表任意的返回值类型,第二个 *代表这个类下的所有方法,(..)代表这个方法的所有参数
-
-
测试有了横切关注点后的业务(代理过后的业务)
public class Test { public static void main(String[] args) { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext.xml"); //动态代理是对接口的代理 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } /* 开始执行com.lyj.service.UserServiceImpl的add 增加一个用户 add方法执行完毕,返回:null
-
-
3.2方式二:使用自己定义的类
自己定义一个切面,横切关注点被模块化成一个对象(一个类),里面写好需关注的横切关注点的方法,通过spring容器(配置bean的xml文件)的advice通知切面需要完成的工作(自定义类中的一个方法)
-
指定接口的业务和指定对象的业务和上面一样
-
自定义一个切面,将横切关注点模块化成一个对象(一个类),里面写好需关注的横切关注点的方法
public class DiyLog { public void before(){ System.out.println("============方法执行前============="); } public void after(){ System.out.println("============方法执行后============="); } }
-
利用spring的AOP,将自定义横切关注点的类(切面)横切入指定业务之中(利用bean装配的的Xml文件)
-
利用advice通知切面需要完成的工作(自定义类中的一个方法)
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userService" class="com.lyj.service.UserServiceImpl"/> <bean id="diyLog" class="com.lyj.diy.DiyLog"/> <aop:config> <!--自定义切面:aspect,ref是需要引用的类--> <aop:aspect ref="diyLog"> <!--切入点pointcut,experssion表达式--> <aop:pointcut id="pointcut2" expression="execution(* com.lyj.service.UserServiceImpl.*(..))"/> <!--advice通知的类型--> <aop:before method="before" pointcut-ref="pointcut2"/> <aop:after method="after" pointcut-ref="pointcut2"/> </aop:aspect> </aop:config> </beans>
-
-
测试有了切面后的业务(代理过后的业务)
@org.junit.Test public void test02() { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext2.xml"); //动态代理是对接口的代理 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } /* ============方法执行前============= 增加一个用户 ============方法执行后=============
-
-
3.3方式三:使用注解的方式实现AOP
-
指定接口的业务和指定对象的业务和上面一样
-
自己自定义一个利用注解实现的切面类
@Aspect//标注这个类是一个切面 public class AnnotationLog { //下面的@代表advice通知的类型 @Before("execution(* com.lyj.service.UserServiceImpl.*(..))") public void before(){ System.out.println("===========执行方法前==================="); } @After("execution(* com.lyj.service.UserServiceImpl.*(..))") public void after(){ System.out.println("===========执行方法后==================="); } }
-
利用spring的AOP,将自定义横切关注点的类(切面)装配到容器之中(利用bean装配的的Xml文件)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/><!--开启ioC注解的支持,注入到xml文件--> <!--注册bean--> <bean id="userService" class="com.lyj.service.UserServiceImpl"/> <bean id="annotationLog" class="com.lyj.diy.AnnotationLog"/> <aop:aspectj-autoproxy/><!--开启AOP注解的支持--> </beans>
注意此处要开启AOP注解的支持:aop:aspectj-autoproxy/,否则就无法切入
-
测试有了注解切面后的业务(代理过后的业务)
@org.junit.Test public void test02() { ApplicationContext context= new ClassPathXmlApplicationContext("applicationContext2.xml"); //动态代理是对接口的代理 UserService userService = (UserService) context.getBean("userService"); userService.add(); } } /* ============方法执行前============= 增加一个用户 ============方法执行后=============
-
十二.Spring整合Mybatis
mybatis-spring的中文文档:http://mybatis.org/spring/zh/index.html 。
1.需要导入的jar包
-
junit
-
mybatis
-
mysql-connector-java(连接数据库)
-
spring-webmvc
-
spring-jdbc(spring操作数据库)
-
aspectjweaver(spring的AOP织入包)
-
mybatis-spring(spring和mybatis整合包)
-
在bulid中配置resources,来防止我们资源导出失败的问题
<dependencies> <!--junit--> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <!--连接mybatis和数据库--> <dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <!--spring操作数据库--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.4</version> </dependency> <!--spring的AOP织入包--> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.6</version> </dependency> <!--(spring和mybatis整合包)--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6</version> </dependency> </dependencies> <!--在bulid中配置resources,来防止我们资源导出失败的问题--> <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>
2.回顾Mybatis
-
1)创建mybatis数据库
-
2)创建一个Maven项目,并在pom.xml中配置好依赖,导入包
-
3)新建一个mybatis-config.xml的核心配置文件
-
4)连接数据库,编写数据库的实体类User
-
5)编写一个接口,把接口用来操作数据库实体类的对象UserMapper
-
6)新建一个与这个接口相对应的配置文件(此处命名为UserMapper.xml)
-
7)将上面的Mapper.xml在Mybatis核心配置文件(mybatis-config.xml)中注册
-
8)测试程序,由于没有工具类,所以需要自己写
@org.junit.Test public void test() throws IOException { //使用Mybatis,要获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(true); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); List<User> userList=userMapper.getUserList(); for(User user:userList){ System.out.println(user); } //关闭SqlSession sqlSession.close(); }
3.spring整合mybatis方式一(SqlSessionTemplate)
-
1)创建一个bean装配的xml文件(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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
-
2)在spring容器中编写数据源配置DataSource
- DataSource:使用Spring的数据源替换Mybatis的核心配置的数据源,连接数据库
这里需要使用Spring提供的JDBC:org.springframework.jdbc.datasource,所以要导入包spring-jdbc(spring操作数据库)- Spring本身也提供了一个简单的数据源实现类DriverManagerDataSource ,它位于org.springframework.jdbc.datasource包中。这个类实现了javax.sql.DataSource接口,但 它并没有提供池化连接的机制,每次调用getConnection()获取新连接时,只是简单地创建一个新的连接。因此,这个数据源类比较适合在单元测试 或简单的独立应用中使用,因为它不需要额外的依赖类。
- Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0。可以在Spring配置文件中利用这两者中任何一个配置数据源。
- C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
- DBCP(DataBase connection pool)数据库连接池。是 apache 上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,通过连接池预先同数据库建立一些连接,放在内存中,应用程序需要建立数据库连接时直接到连接池中申请一个就行,用完后再放回去。
此处替换完成后,mybatis的核心配置文件中的环境配置
可以省去 <!--DataSource:使用Spring的数据源替换Mybatis的核心配置,连接数据库 这里需要使用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=true& useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean>
- DataSource:使用Spring的数据源替换Mybatis的核心配置的数据源,连接数据库
-
3)对于在mybatis使用的工具类中的sqlSessionFactory或是测试代码手动创建的sqlSessionFactory,在MyBatis-Spring 中,可使用
SqlSessionFactoryBean
来创建SqlSessionFactory
注意:spring中也可以绑定mybatis的核心配置文件,以及Mapper.xml文件在核心配置文件的注册等等
绑定完后,mybatis的核心配置文件中注册的Mapper.xml可以删去
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!--spring中也可以绑定mybatis的核心配置文件,以及Mapper.xml文件在核心配置文件的注册等等--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/lyj/dao/UserMapper.xml"/> </bean>
-
4)对于在mybatis使用的工具类中的sqlSession或是测试代码手动创建的sqlSession,在MyBatis-Spring 中,利用spring中装配的对象sqlSessionFactory来创建sqlSessionTemplate(Template:模板)
SqlSessionTemplate
是 MyBatis-Spring 的核心。作为SqlSession
的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的SqlSession
。- 当调用 SQL 方法时(包括由
getMapper()
方法返回的映射器中的方法),SqlSessionTemplate
将会保证使用的SqlSession
与当前 Spring 的事务相关。 此外,它管理 session 的生命周期,包含必要的关闭、提交或回滚操作。
<!--SqlSessionTemplate:就是我们用的SqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--SqlSessionTemplate没有set方法,只能使用构造器注入sqlSessionFactory--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
-
5)给所需要的接口加一个实现类(mybatis没有的)
这个实现类利用SqlSessionTemplate用于执行UserMapper接口的业务
public class UserMapperImpl implements UserMapper{ //我们所有的操作都是用SqlSession(SqlSessionTemplate)来执行 private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> getUserList() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.getUserList(); } }
-
6)将上面这个实现类,注入到Spring容器中
<bean id="userMapperImpl" class="com.lyj.dao.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
-
7)测试
@org.junit.Test public void test02(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class); List<User> userList = userMapperImpl.getUserList(); for (User user : userList) { System.out.println(user); } /*User{id=1, name='lyj', pwd='123457'} User{id=2, name='bbb', pwd='bbbbbb'} User{id=3, name='张三', pwd='345678'} User{id=4, name='李四', pwd='null'} User{id=5, name='王五', pwd='null'} User{id=6, name='刘六', pwd='678901'} User{id=7, name='秦七', pwd='789012'}
4.spring整合mybatis方式二(SqlSessionDaoSupport)
-
1)2)3)步骤和上面方式一相同,不同在于怎么样得到SqlSession,方式一是利用SqlSessionFactory创建SqlSessionTemplate
-
4)给所需要的接口加一个实现类,并继承SqlSessionDaoSupport接口
-
方式二不需要用SqlSessionFactory注入得到一个SqlSession,而是让实现类继承一个SqlSessionDaoSupport接口
-
SqlSessionDaoSupport
是一个抽象的支持类,用来为你提供SqlSession
。调用getSqlSession()
方法你会得到一个SqlSessionTemplate
,之后可以用于执行 SQL 方法
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> getUserList() { return getSqlSession().getMapper(UserMapper.class).getUserList(); } }
-
-
5)将上面这个实现类,注入到Spring容器中
<!--将实现类注入到容器中,方式二--> <bean id="userMapperImpl2" class="com.lyj.dao.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean>
-
6)测试
@org.junit.Test public void test03(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapperImpl2 userMapperImpl = context.getBean("userMapperImpl2", UserMapperImpl2.class); List<User> userList = userMapperImpl.getUserList(); for (User user : userList) { System.out.println(user); } } /*User{id=1, name='lyj', pwd='123457'} User{id=2, name='bbb', pwd='bbbbbb'} User{id=3, name='张三', pwd='345678'} User{id=4, name='李四', pwd='null'} User{id=5, name='王五', pwd='null'} User{id=6, name='刘六', pwd='678901'} User{id=7, name='秦七', pwd='789012'}
十三.声明式事务
1.事务的回顾
-
ACID
-
对于spring中未开启事务的案例:根据上面spring整合mybatis方式二
-
在接口中新增添加用户,和删除用户的方法
public interface UserMapper { public List<User> selectUserList(); public int insertUser(User user); public int deleteUser(int id); }
-
接口对应的Mapper配置文件,写好sql语句
<?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"> <!--namespace绑定一个对应的Dao/Mapper接口--> <mapper namespace="com.lyj.dao.UserMapper"> <!--查询语句--> <!--返回的方法名字不要写错(自己接口中的方法),返回的类型不能写错,(自己接口方法的返回类型)--> <select id="selectUserList" resultType="com.lyj.pojo.User"> select * from mybatis.user </select> <!--在mybatis-config核心配置文件中起了别名--> <insert id="insertUser" parameterType="user"> insert into mybatis.user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <delete id="deleteUser" parameterType="int"> delete from mybatis.user where id=#{id} </delete> </mapper>
-
重写接口的实现类的方法
@Override public List<User> selectUserList() { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.selectUserList(); } @Override public int insertUser(User user) { System.out.println("插入用户数据"); return getSqlSession().getMapper(UserMapper.class).insertUser(user); } @Override public int deleteUser(int id) { System.out.println("删除用户数据"); return getSqlSession().getMapper(UserMapper.class).deleteUser(id); } }
-
(将删除数据的sql删除语句故意写错),执行测试类
@org.junit.Test public void test001(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapperImpl2 userMapperImpl = context.getBean("userMapperImpl2", UserMapperImpl2.class); User user1 = new User(8,"杜八","891234"); userMapperImpl.insertUser(user1); userMapperImpl.deleteUser(8); List<User> userList = userMapperImpl.selectUserList(); for (User user : userList) { System.out.println(user); } } /*程序报错,但是在数据库中,添加数据User(8,"杜八","891234")成功,没有进行数据删除,显然不是一个事务,出错未进行数据回滚
2.Spring中的事务管理
-
编程式事务:需要在代码中,进行事务的管理
-
声明式事务:AOP的应用(事务的代码是横切进去的,不影响原先的代码)以下是声明式事务的例子
-
1)开启spring中配置声明式事务
-
要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个
DataSourceTransactionManager
对象:<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean>
-
-
2)结合AOP实现事务的织入
-
首先要在spring中导入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 https://www.springframework.org/schema/tx/spring-tx.xsd"> </beans>
-
-
3)配置事务
<!--开启spring中配置声明式事务--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--结合AOP实现事务的织入--> <!--1.配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="txManager"> <!--给接口的方法配置事务,配置事务的传播特性:new propagation--> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="select" read-only="true"/> </tx:attributes> </tx:advice>
propagation:事务的传播行为,isolation:事务的隔离行为
事务的隔离级别
-
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
-
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别
-
可重复读(Repeated Read):在同一个事务内的查询都是事务开始时刻一致的,Mysql的InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻读(多个事务同时修改同一条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据将会覆盖前事务,前一个事务就像发生幻觉一样)
-
可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
事务隔离级别 脏 读 不可重复读 幻 读 读未提及(READ_UNCOMMITTED) 允许 允许 允许 读已提交(READ_COMMITTED) 禁止 允许 允许 可重复读(REPEATABLE_READ) 禁止 禁止 允许 顺序读(SERIALIZABLE) 禁止 禁止 禁止 - 不可重复读和幻读的区别主要是:解决不可重复读需要锁定了当前满足条件的记录,而解决幻读需要锁定当前满足条件的记录及相近的记录。比如查询某个商品的信息,可重复读事务隔离级别可以保证当前商品信息被锁定,解决不可重复读;但是如果统计商品个数,中途有记录插入,可重复读事务隔离级别就不能保证两个事务统计的个数相同。
事务的传播级别
Spring事务定义了7种传播机制:- PROPAGATION_REQUIRED:默认的Spring事物传播级别,若当前存在事务,则加入该事务,若不存在事务,则新建一个事务。
- PROPAGATION_REQUIRE_NEW:若当前没有事务,则新建一个事务。若当前存在事务, 则挂起当前事务,再新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。
- PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
- PROPAGATION_SUPPORTS:支持当前事务,若当前不存在事务,以非事务的方式执行。
- PROPAGATION_NOT_SUPPORTED:以非事务的方式执行,若当前存在事务,则把当前事务挂起。
- PROPAGATION_MANDATORY:强制事务执行,若当前不存在事务,则抛出异常.
- PROPAGATION_NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
- Spring事务传播级别一般不需要定义,默认就是PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解。
-
-
4)切入事务
<!--配置事务的切入--> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.lyj.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
-
5)(将删除数据的sql删除语句故意写错),执行测试类
注意:AOP为动态代理,是对接口的代理,所以getBean的类型要转为接口UserMapper
@org.junit.Test public void test001(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapperImpl2",UserMapper.class); User user1 = new User(8,"杜八","891234"); userMapper.insertUser(user1); userMapper.deleteUser(8); List<User> userList = userMapper.selectUserList(); for (User user : userList) { System.out.println(user); } } /*结果数据不会得到更新!!!事务回滚
十四. Spring 框架中用到了哪些设计模式?
- 工厂设计模式 : Spring使用工厂模式通过
BeanFactory
、ApplicationContext
创建 bean 对象。 - 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中
jdbcTemplate
、hibernateTemplate
等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。 - 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配
Controller
。 - ......