什么是Spring?
1、简介
Spring框架是由于软件开发
的复杂性而创建的。Spring使用的是基本的JavaBean
来完成以前只可能由EJB
完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
2、创始人
Rod在悉尼大学不仅获得了计算机学位,同时还获得了音乐学位。更令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位。有着相当丰富的C/C++技术背景的Rod早在1996年就开始了对Java服务器端技术的研究。他是一个在保险、电子商务和金融行业有着丰富经验的技术顾问,同时也是JSR-154(Servlet 2.4)和JDO 2.0的规范专家、JCP的积极成员。
真正引起了人们的注意的,是在2002年Rod Johnson根据多年经验撰写的《Expert o-ne-on-One J2EE Design and Development》。其中对正统J2EE架构的臃肿、低效的质疑,引发了人们对正统J2EE的反思。这本书也体现了Rod Johnson对技术的态度,技术的选择应该基于实证或是自身的经验,而不是任何形式的偶像崇拜或者门户之见。正是这本书真正地改变了Java世界。基于这本书的代码,Rod Johnson创建了轻量级的容器Spring。Spring的出现,使得正统J2EE架构一统天下的局面被打破。基于Struts+Hibernate+Spring的J2EE架构也逐渐得到人们的认可,甚至在大型的项目架构中也逐渐开始应用。
3、官网
首页:https://spring.io
下载地址:https://repo1.maven.org/maven2/org/springframework/spring
github:https://github.com/spring-projects/spring-framework
maven:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.13</version>
</dependency>
<!--MyBatis整合-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.13</version>
</dependency>
|
4.IOC理论
1
2
3
|
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
|
使用set来把决定权交给用户,降低系统耦合性。
Spring的使用
1.HelloSpring
例子:
Dao接口
1
2
3
|
public interface UserDao {
void getUser();
}
|
实现接口1:
1
2
3
4
5
6
|
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("默认获取用户数据");
}
}
|
实现接口2:
1
2
3
4
5
6
|
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("默认获取MYSQL数据");
}
}
|
业务层:
1
2
3
4
5
6
7
8
9
10
|
public class UserServiceImpl implements UserService{
private UserDao userDao;
@Override
public void getUser() {
userDao.getUser();
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
|
通过spring实例化:
1
2
3
4
5
6
7
8
|
public class MyTest {
public static void main(String[] args) {
//拿到spring容器
ApplicationContext beans = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userServiceImpl = (UserServiceImpl) beans.getBean("UserServiceImpl");
userServiceImpl.getUser();
}
}
|
配置文件:
1
2
3
4
5
6
7
8
9
10
11
|
<?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="userImpl" class="com.seawave.dao.UserDaoImpl"/>
<bean id="mysqlImpl" class="com.seawave.dao.UserDaoMysqlImpl"/>
<bean id="UserServiceImpl" class="com.seawave.service.UserServiceImpl">
<property name="userDao" ref="userImpl"/>
</bean>
</beans>
|
2.ioc创建对象的方式
-
默认使用无参构造
-
假设我们需要用有参构造创建对象:
方式一:通过下标赋值
1
2
3
4
|
<bean id="user" class="com.kuang.pojo.User">
<constructor-arg index="0" value="12"/>
<constructor-arg index="1" value="fuck"/>
</bean>
|
方式二:通过属性名赋值
1
2
3
4
|
<bean id="user" class="com.kuang.pojo.User">
<constructor-arg name="id" value="12"/>
<constructor-arg name="name" value="fuck"/>
</bean>
|
在配置文件加载的时候,容器中管理的对象就已经初始化了!
3.Spring配置
3.1、别名
通过别名获取对象
1
2
|
<!-- 别名,可以用过别名获取对象-->
<alias name="user" alias="UserTese"/>
|
3.2、bean
1
2
|
<bean id="user" class="com.kuang.pojo.User" name="userTest">
</bean>
|
- id:bean对象的唯一标识符,相当于对象名
- class:bean对应的全限定名,包名+类型
- name:也是别名,可以取多个
3.3、import
用于团队开发,导入其他的bean配置文件,将多个配置文件合并为一个。
1
2
3
|
<import resource="beans.xml"/>
<import resource="bean1.xml"/>
<import resource="bean2.xml"/>
|
4.依赖注入
4.1、构造器注入
前面用到的方式即为构造器注入
4.2 Set方式注入【重点】
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,由容器注入
【环境搭建】
1.复杂类型
1
2
3
4
5
6
7
8
9
|
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
|
2.真实测试对象
1
2
3
4
5
6
7
8
9
10
11
|
@Data
public class Student {
private String name;
private Address address;
private String[]books;
private List<String> hobbies;
private Map<String,String>card;
private Set<String>games;
private Properties info;
private String wife;
}
|
复杂类型注入:
1
2
3
4
5
6
7
|
<!-- 第二种,Bean注入,ref-->
<bean id="address" class="com.seawave.pojo.Address">
<property name="address" value="北京东城"/>
</bean>
<bean id="student" class="com.seawave.pojo.Student">
<property name="name" value="fuck"/>
<property name="address" ref="address"/>
|
数组注入:
1
2
3
4
5
6
7
8
|
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
|
List集合注入:
1
2
3
4
5
6
7
8
|
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
|
map注入:
1
2
3
4
5
6
|
<property name="card">
<map>
<entry key="身份证" value="330629197010232241"/>
<entry key="银行卡" value="340629197010232231"/>
</map>
</property>
|
set注入:
1
2
3
4
5
6
7
|
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
|
null值注入:
1
2
3
|
<property name="wife">
<null/>
</property>
|
Properties注入:
1
2
3
4
5
6
7
|
<property name="info">
<props>
<prop key="url">https://baidu.com</prop>
<prop key="username">seawave</prop>
<prop key="password">zgq123</prop>
</props>
</property>
|
5.bean的作用域
作用域 |
描述 |
singleton |
(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。 |
prototype |
将单个 bean 定义的作用域限定为任意数量的对象实例。 |
request |
将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。 |
session |
将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 |
application |
将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 |
websocket |
将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。 |
单例模式(默认模式)
1
|
<bean id="user" class="com.seawave.pojo.User" scope="singleton">
|
原型模式(每次从容器中获取都会产生一个新对象)
1
|
<bean id="user" class="com.seawave.pojo.User" scope="prototype">
|
其余的request、session、application、websocket 这些只能在web开发中失效
6.Bean自动装配
- 自动装配是Spring满足bean依赖一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性!
在spring中有三种装配方式:
- 在xml中显示的配置
- 在java中显示配置
- 隐式 的自动装配bean【重要】
测试源代码:
此处的cat和dog是一个复杂类型
1
2
3
4
5
6
|
<bean id="cat" class="com.seawave.pojo.Cat>
<property name="name" value="xiaomiao"/>
</bean>
<bean id="dog" class="com.seawave.pojo.Cat>
<property name="name" value="xiaowang"/>
</bean>
|
普通写法:
1
2
3
4
5
|
<bean id="people" class="com.seawave.pojo.People" autowire="byName">
<property name="name" value="fuck"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
|
ByName自动装配
会自动在容器上下文中查找和自己对象set方法后面的值对应的beanID,无需手动装配cat和dog类
1
2
3
|
<bean id="people" class="com.seawave.pojo.People" autowire="byName">
<property name="name" value="fuck"/>
</bean>
|
ByType自动装配
会自动在容器上下文中查找和自己对象属性类型相同的beanID,同理
1
2
3
|
<bean id="people" class="com.seawave.pojo.People" autowire="byType">
<property name="name" value="fuck"/>
</bean>
|
使用注解进行自动装配
要使用注解须知:
-
导入约束:context约束
-
配置注解支持
1
2
3
4
5
6
7
8
9
10
|
<?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:直接在属性名上使用
@nonull:如果标记了这个注解,说明这个属性可以为null。
@required:如果显式定义了此属性,说明这个对象可以为null,否则不允许为空。
7.使用Java的方式配置Spring
1
2
3
4
5
6
7
8
9
10
11
|
//这个注解表示这个类被Spring接管。
@Configuration
public class MyConfig {
//注册一个bean,就相当于我们之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
@Bean
public User getUser() {
return new User();
}
}
|
1
2
3
4
5
6
|
@Configuration
@Data
public class User {
@Value("fuck")
private String name;
}
|
代理模式
为什么要学习代理模式?因为这就是SprngAOP的底层!
代理模式的分类:
1.静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决。
- 真是角色:被代理的角色。
- 代理介绍:代理真实角色,代理之后一般会进行附属操作。
- 客户:访问代理对象的人。
代码步骤:
- 接口
1
2
3
4
|
//租房
public interface Rent {
public void rent();
}
|
- 真实角色
1
2
3
4
5
6
7
|
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
|
- 代理角色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.seawave.demo01;
public class Proxy {
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host=host;
}
public void rent(){
seeHouse();
sign();
host.rent();
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void sign(){
System.out.println("签合同");
}
}
|
- 客户访问代理角色
1
2
3
4
5
6
7
8
|
package com.seawave.demo01;
public class Client {
public static void main(String[] args) {
Host host=new Host();
host.rent();
}
}
|
代理模式的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共业务。
- 公共业务交给代理角色!实现了业务分工。
- 公共业务发生扩展的时候,方便管理!
缺点:
2.动态代理
- 动态代理和静态代理角色一样。
- 动态代理的代理类是动态生成的,不是我们写好的!
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package com.seawave.demo03;
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);
}
@Override
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 msg){
System.out.println("执行了"+msg+"方法");
}
}
|
1
2
3
4
5
6
7
8
9
|
public class Client {
public static void main(String[] args) {
ProxyInvocationHandler pih = new ProxyInvocationHandler();
Host host = new Host();
pih.setTarget(host);
Rent rent= (Rent) pih.getProxy();
rent.rent();
}
}
|
AOP
1.什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程
,通过预编译
方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP
的延续,是软件开发中的一个热点,也是Spring
框架中的一个重要内容,是函数式编程
的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度
降低,提高程序的可重用性,同时提高了开发的效率。
2.使用Spring实现AOP
使用aop织入需要导入依赖包:
1
2
3
4
5
|
<dependency>
<groupId>org.apache.geronimo.bundles</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8_2</version>
</dependency>
|
方式一:使用Spring的API接口
例子:
第一步、配置如下spring配置文件:
1
2
3
4
5
6
7
8
9
10
|
<bean id="userService" class="com.seawave.service.UserServiceImpl"/>
<bean id="log" class="com.seawave.log.Log"/>
<bean id="afterLog" class="com.seawave.log.AfterLog"/>
<!--配置aop-->
<aop:config>
<aop:pointcut id="poincut" expression="execution(* com.seawave.service.UserServiceImpl.* (..))"/>
<!-- 执行环绕-->
<aop:advisor advice-ref="log" pointcut-ref="poincut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="poincut"/>
</aop:config>
|
第二步、创建如下两个log日志类
1
2
3
4
5
6
7
8
9
|
public class Log implements MethodBeforeAdvice {
@Override
//method:要执行的目标对象的方法
//objects:参数
//target//目标对象
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
|
1
2
3
4
5
6
7
|
public class AfterLog implements AfterReturningAdvice {
@Override
//returnValue:返回值
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"返回结果为:"+returnValue);
}
}
|
第三步、在测试类中执行方法
1
2
3
4
5
6
7
|
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
|
运行结果如下
1
2
3
|
com.seawave.service.UserServiceImpl的add被执行了
增加了一个用户
执行了add返回结果为:null
|
由此可见,我们通过context.getBean方法从spring容器中得到了一个UserService的代理类,我们可以在执行UserService类的方法之前插入自己的逻辑方法。
方式二:使用自定义类
例子
第一步、新建diy类
1
2
3
4
5
6
7
8
|
public class DiyPointCut {
public void before() {
System.out.println("===========方法执行前==========");
}
public void after() {
System.out.println("===========方法执行后===========");
}
|
第二步、配置spring配置文件
1
2
3
4
5
6
7
8
9
10
|
//注册diy类
<bean id="diy" class="com.seawave.diy.DiyPointCut"/>
<!--方式二-->
<aop:config>
<aop:aspect ref="diy">
<aop:pointcut id="point" expression="execution(* com.seawave.service.UserServiceImpl.* (..))"/>
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
|
第三步、在测试类中测试,代码和第一种一样
我们可以得到如下结果:
1
2
3
|
===========方法执行前==========
增加了一个用户
===========方法执行后===========
|
整合MyBatis
实际开发中,我们通常会使用多哥框架技术,这就需要将mybatis和spring进行整合。
例子
-
配置数据源和工厂注入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- 一些基本配置 -->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!--创建sqlsession工厂-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource" />
<!--此配置项用于连接原有的mabatis配置文件,因此mybatis配置文件仍可使用。-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/seawave/mapper/UserMapper.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入sqlSessionFactory-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
|
我们可以看到:
-
创建接口实现类
1
2
3
4
5
6
7
8
9
10
11
|
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> getUsers() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUsers();
}
|
注意
setSqlSession方法必不可少,缺少会导致spring无法注入。
-
注入实现类
1
2
3
|
<bean id="userMapper" class="com.seawave.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
|
-
测试
1
2
3
4
5
6
7
8
9
10
|
public class MyTest {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.getUsers()) {
System.out.println(user);
}
}
}
|
Spring事务
事务是作为单个逻辑工作单元执行的一系列操作。一个逻辑工作单元必须有四个属性,称为原子性、一致性、隔离性和持久性 (ACID) 属性,只有这样才能成为一个事务。事务一般都是与数据库打交道的操作。
简单来说,事务就是将一系列要做的事情放在一起,要么一起成功,要么一起失败。
spring提供了以下两种事务:
本文将举例声明式事务。
例子:
在UserMapper中配置如下sql语句:
1
2
3
4
5
6
7
8
9
10
11
|
<mapper namespace="com.seawave.mapper.UserMapper">
<select id="getUsers" resultType="user">
select * from user
</select>
<insert id="insertUser" parameterType="user">
insert into user (id,name) values (#{id} , #{name});
</insert>
<delete id="delete" parameterType="user">
deletes from user where id=#{id};
</delete>
</mapper>
|
可以看到,我们故意将delete写错成deletes,以此来模仿业务逻辑中的错误。
配置spring配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<!-- 配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!-- 结合APO实现事务的织入-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务-->
<tx:attributes>
<tx:method name="getUsers"/>
<tx:method name="insertUser"/>
<tx:method name="deleteUser"/>
<!-- 配置事务的传播特性-->
<tx:method name="select" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.seawave.mapper.*.* (..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
|
测试:
1
2
3
4
5
6
7
8
9
|
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.getUsers()) {
System.out.println(user);
}
}
}
|
此时运行程序会提示sql语句错误,但是并没有执行insertUser中的插入语句,如果我们注释掉第二步中的语句,那么将会产生插入成功,删除失败的现象。而此时对于插入和删除这两个操作来说,他们是一致的,其中一个失败另外一个也不会执行。