Spring详细介绍以及具体操作步骤

文章目录

一、Spring框架:

1、原生web开发中存在哪些问题?

  • 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。
  • 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。
  • 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。

2、概念

二、Spring架构组成


Spring架构由诸多模块组成,可分类为

  • 核心技术:依赖注入,事件,资源,i18n,验证,数据绑定,类型转换,SpEL,AOP
  • 测试:模拟对象,TestContext框架,Spring MVC测试,WebTestClient。
  • 数据访问:事务,DAO支持,JDBC,ORM,封送XML。
  • Spring MVC和 Spring WebFlux Web框架。
  • 集成:远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
  • 语言:Kotlin,Groovy,动态语言。
GroupIdArtifactId说明
org.springframeworkspring-beansBeans 支持,包含 Groovy
org.springframeworkspring-aop基于代理的AOP支持
org.springframeworkspring-aspects基于AspectJ 的切面
org.springframeworkspring-context应用上下文运行时,包括调度和远程抽象
org.springframeworkspring-context-support支持将常见的第三方类库集成到 Spring 应用上下文
org.springframeworkspring-core其他模块所依赖的核心模块
org.springframeworkspring-expressionSpring 表达式语言,SpEL
org.springframeworkspring-instrumentJVM 引导的仪表(监测器)代理
org.springframeworkspring-instrument-tomcatTomcat 的仪表(监测器)代理
org.springframeworkspring-jdbc支持包括数据源设置和 JDBC 访问支持
org.springframeworkspring-jms支持包括发送/接收JMS消息的助手类
org.springframeworkspring-messaging对消息架构和协议的支持
org.springframeworkspring-orm对象/关系映射,包括对 JPA 和 Hibernate 的支持
org.springframeworkspring-oxm对象/XML 映射(Object/XML Mapping,OXM)
org.springframeworkspring-test单元测试和集成测试支持组件
org.springframeworkspring-tx事务基础组件,包括对 DAO 的支持及 JCA 的集成
org.springframeworkspring-webweb支持包,包括客户端及web远程调用
org.springframeworkspring-webmvcREST web 服务及 web 应用的 MVC 实现
org.springframeworkspring-webmvc-portlet用于 Portlet 环境的MVC实现
org.springframeworkspring-websocketWebSocket 和 SockJS 实现,包括对 STOMP 的支持
org.springframeworkspring-jclJakarta Commons Logging 日志系统

三、自定义工厂

不使用Spring提供IOC容器 (Container),通过自定义的工厂来实现对象的创建

1、创建bean.properties

userService=com.mxd.service.impl.UserServiceImpl

2、工厂类

public class MyFactory {

 //公共的访问方法
 public static Object getBean(String beanName) throws Exception {
     //读取properties文件中的信息
     InputStream inputStream = 
                 MyFactory.class.getClassLoader().getResourceAsStream("bean.properties");
     
     //创建属性集合列表
     Properties properties = new Properties();
     properties.load(inputStream);
     //获取值(全类名)
     String userServiceClass = properties.getProperty(beanName);
     //反射获取类对象
     Class clazz = Class.forName(userServiceClass);
     //创建实例
     Object obj = clazz.newInstance();
     return obj;
 }
}

3、测试

public void test1() throws Exception {
     UserService userService = (UserService) MyFactory.getBean("userService");
     String name = userService.getName();
     System.out.println(name);
 }

四、Spring环境搭建

1、在pom.xml中添加spring常用的依赖

<!--spring常用的依赖:spring-context-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>5.3.9</version>
</dependency>

2、创建Spring-context.xml配置文件

命名无限制,常用的名称有:spring-context.xml、applicationContext.xml、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">

</beans>

3、利用spring中的bean工厂创建对象

3.1 定义目标bean类型

接口:

public interface UserService {
 String getName();
}

实现类:

public class UserServiceImpl implements UserService {
 @Override
 public String getName() {
     return "你好Spring!";
 }
}
3.2 配置spring-context.xml中的bean标签
<!--	spring管理bean对象        
		bean标签 	id属性: 标识符,必须唯一
				 	举例:		接口名字:UserService-   id标识符 "userService"
          	 class属性: 业务层或者控制器层/dao层接口实现类的全限定名称
-->
<bean id="userService" class="com.mxd.service.impl.UserServiceImpl"></bean>
3.3 测试(调用Spring工厂API(ApplicationContext接口))
/**
** 程序中的对象都交由Spring的ApplicationContext工厂进行创建。
**/
@Test
public void test1(){
 //1. 读取配置文件中所需创建的bean对象,并获得工厂对象
 ClassPathXmlApplicationContext applicationContext = 
     					new ClassPathXmlApplicationContext("spring-config.xml");
 //2. 通过id获取bean对象
 UserService userService = (UserService) applicationContext.getBean("userService");
 //通过 当前类型.class 获取bean对象
 //UserService userService = (UserService) applicationContext.getBean(UserService.class);
 //3. 使用对象
 String name = userService.getName();
 System.out.println(name);
}

五、IOC 控制反转 【重点】

​ 反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转),解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健。

IOC反转控制:程序之间解耦

​ 在类和类之间存在控制权,控制权指的是对象的创建和使用

  比如有类A和类B,我们之前的做法是在A中调用B,那么控制权就在A中,这样做的耦合度较高,如果修改了B,A也要做相应修改。引入Spring框架后,控制权由spring容器来负责。当A想使用B时,需要由Spirng容器通过配置文件进行注入。这种思想就是IoC。

(为了更好的理解,我们可以这样认为,对象创建和使用的控制权转移到了Spring容器,由Spring容器来控制)。

六、DI(依赖注入)【重点】

在Spring创建对象的同时,为其属性赋值,称之为依赖注入。

常用的依赖注入有三种:setter注入,构造方法注入,基于注解的注入

1、setter方法注入(最常用):

创建对象时,Spring工厂会通过Set方法为对象的属性赋值。

1.1 定义目标Bean类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
 private Integer id;
 private String name;
 private Integer age;
 private Date birthday;
 private String[] hobby;
 private Set<String> phones;
 private List<String> names;
 private Map<String,String> countries;
 private Properties files;
}

1.2 基本类型 + 字符串类型 + 日期类型
<bean id="user" class="com.mxd.pojo.User">
 <property name="id" value="1"/>
 <property name="name" value="张三"/>
 <property name="age" value="66"/>
 <property name="birthday" value="2020/11/11"/><!--注意格式"/"-->
</bean>

1.3 容器类型
<bean id="user" class="com.mxd.pojo.User">
 <property name="id" value="1"/>
 <property name="name" value="张三"/>
 <property name="age" value="66"/>
 <property name="birthday" value="2020/11/11"/>

 <!--数组-->
 <property name="hobby">
     <array>
         <value>篮球</value>
         <value>足球</value>
         <value>羽毛球</value>
     </array>
 </property>

 <!--set集合-->
 <property name="phones">
     <set>
         <value>17722223333</value>
         <value>15366668888</value>
         <value>1399997777</value>
     </set>
 </property>

 <!--list集合-->
 <property name="names">
     <list>
         <value>mxd</value>
         <value>tlx</value>
         <value>zy</value>
     </list>
 </property>

 <!--Map集合-->
 <property name="countries">
     <map>
         <entry key="china" value="中国"></entry>
         <entry key="uk" value="英国"></entry>
         <entry key="us" value="美国"></entry>
     </map>
 </property>

 <!--propertie文件-->
 <property name="files">
     <props>
         <prop key="one"></prop>
         <prop key="two"></prop>
         <prop key="three"></prop>
     </props>
 </property>
</bean>

1.4 自建类型

定义目标bean类型

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TbUser {
 private Integer id;
 private String name;
 private Integer age;
 private TbUserInfo tbUserInfo;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TbUserInfo {
 private String sex ; //性别
 private String phone ;//电话
 private String address ;//地址
 private Integer addrId ; //门牌号
}

配置spring-context.xml

<bean id="tbUser" class="com.mxd.pojo.TbUser">
 <property name="id" value="1"/>
 <property name="name" value="李四"/>
 <property name="age" value="99"/>
 <property name="tbUserInfo" ref="tbUserInfo"/><!--用ref属性引用-->
</bean>

<bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo">
 <property name="sex" value=""/>
 <property name="phone" value="119"/>
 <property name="address" value="南窑头"/>
 <property name="addrId" value="999"/>
</bean>

2、构造函数注入(了解)

创建对象时,Spring工厂会通过构造方法为对象的属性赋值,必须要有构造函数

<!--构造函数注入,基本和set方法一致-->
<bean id="user" class="com.mxd.pojo.User">
 <constructor-arg name="id" value="22"/>
 <constructor-arg name="name" value="王五"/>
 <constructor-arg name="age" value="66"/>
 <constructor-arg name="birthday" value="2021/12/7"/><!--注意时间格式-->
 <!--数组-->
 <constructor-arg name="hobby" >
     <array>
         <value>打篮球</value>
         <value>踢足球</value>
         <value>打台球</value>
     </array>
 </constructor-arg>

 <!--set集合-->
 <constructor-arg name="phones">
     <set>
         <value>17722223333</value>
         <value>15366668888</value>
         <value>1399997777</value>
     </set>
 </constructor-arg>

 <!--list集合-->
 <constructor-arg name="names">
     <list>
         <value>mxd</value>
         <value>tlx</value>
         <value>zy</value>
     </list>
 </constructor-arg>

 <!--Map集合-->
 <constructor-arg name="countries">
     <map>
         <entry key="china" value="中国"></entry>
         <entry key="uk" value="英国"></entry>
         <entry key="us" value="美国"></entry>
     </map>
 </constructor-arg>

 <!--propertie文件-->
 <constructor-arg name="files">
     <props>
         <prop key="one"></prop>
         <prop key="two"></prop>
         <prop key="three"></prop>
     </props>
 </constructor-arg>
</bean>

3、基于注解注入

需要的配置:
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.mxd"></context:component-scan>
	
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>

3.1 使用注解注册bean

有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

(1)@Component:可以用于注册所有bean

(2)@Repository:主要用于注册dao层的bean

(3)@Service:主要用于注册业务层的bean

(4)@Controller:主要用于注册控制层的bean

这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。

@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。

注:注册bean就是装配到Spring容器中,表示这个类的创建、销户交由spring进行管理,但这时bean可能并没有被创建。

3.2 装配bean时常用的注解
3.2.1 @Resource

jdk中定义的注解

@Resource是默认以byName(名字)的方式去匹配与属性名相同的bean的id,同时可以指定name属性。

既不指定name属性,也不指定type属性时,则自动按byName方式先进行查找,如果没有找到符合的bean,则回退为一个原始类型(type)进行查找,如果找到就注入,找不到就会抛出异常

3.2.2@Autowired

spring中提供的注解

使用@Autowired注解来自动装配指定的bean。

默认按照类型(type)注入,如果需要按照名称搜索,则需要借助于@Qualifier

@Qualifier(value="filmTypeService")
@Autowired

在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

  • 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
  • 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
  • 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值。

注:装配bean就是创建对象,依赖注入(DI)

3.2.3 @Resource和@Autowired的区别

相同点:

@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。

不同点:

(1)提供方

@Autowired是Spring的注解;@Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。

(2)注入方式

@Autowired默认按照Type 注入;@Resource默认按Name自动注入,也提供按照Type 注入;

(3)属性

@Autowired注解可用于为类的属性、构造器、方法进行注值。

默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。

​ 还有一个比较重要的点就是,@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称。

​ @Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。

注:

@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。

@Resource注解的使用性更为灵活,可指定名称,也可以指定类型;

@Autowired注解进行装配容易抛出异常,特别是装配的bean类型有多个的时候,而解决的办法是需要在增加@Qualitied进行限定。一般@Autowired和@Qualifier一起用,@Resource单独用。

4、p标签(p名称空间)注入(了解)

<bean id="tbUser" class="com.mxd.pojo.TbUser" 
	p:id="22" p:name="哈哈" p:age="33" p:tbUserInfo-ref="tbUserInfo">
</bean>

<bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo">
 <property name="sex" value=""/>
 <property name="phone" value="110"/>
 <property name="address" value="山西"/>
 <property name="addrId" value="222"/>
</bean>

5、自动注入(了解)

<bean id="user" class="com.mxd.pojo.User" autowire="byName">
</bean>

autowire属性有5种模式:

(1)no

(默认)不采用autowire机制.。这种情况,当我们需要使用依赖注入,只能用ref属性。

(2)byName

通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire=“byName”,那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。

(3)byType

通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。

(4)constructor

类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。

(5)default

采用父级标签(即beans的default-autowire属性)的配置。

七、控制简单对象的单例模式/多例模式

在spring-context.xml中:

<!--
	singleton(默认):每次调用工厂,得到的都是同一个对象。单例模式
	prototype:每次调用工厂,都会创建新的对象。多例模式
-->
<bean id="user" class="com.mxd.pojo.User" scope="prototype">
     
</bean>

  • 注意:需要根据场景决定对象的单例、多例模式。
  • 可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)。
  • 不可共用:Connection、SqlSession、ShoppingCart。

八、spring中bean对象的生命周期

1、饿汉式创建的优势

工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。

提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)

注:单例:始终只有一个对象 。

​ 饿汉式:类一加载就创建对象

​ 懒汉式 :需要使用的时候才创建对象

2、生命周期方法

实体类中:
  • 自定义无参构造:
public User(){
     System.out.println("对象被创建");
 }

  • 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
public void init(){
     System.out.println("对象初始化");
 }

  • 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
public void destroy(){
     System.out.println("对象销毁");
 }

  • 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
  • 分类:singleton对象由Spring容器直接销毁、prototype对象由JVM销毁。
spring-context.xml中:
单例模式:
<bean id="user" class="com.mxd.pojo.User" init-method="init" destroy-method="destroy">

</bean>

测试:

public void test3(){
     ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
     User user = (User) applicationContext.getBean("user");
     applicationContext.close();//调用close方法之后才会被销毁
 }

输出结果:

对象被创建
对象初始化
对象销毁

多例模式:
<!--lazy-init(延时加载):当使用多例模式时,需要使用的时候才创建对象 初始化过程需要懒加载-->
<bean id="user" class="com.mxd.pojo.User" init-method="init" 
		destroy-method="destroy" scope="prototype" lazy-init="true">
</bean>

测试:

public void test3(){
     ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
     User user = (User) applicationContext.getBean("user");
     applicationContext.close();//多例模式中销毁时由JVM直接销毁
 }

输出结果:

对象被创建
对象初始化

3、生命周期注解

@PostConstruct  //初始化注解
public void init(){
	System.out.println("对象初始化");
}

@PreDestroy     //销毁注解
public void destroy(){
	System.out.println("对象销毁");
}


@Scope("singleton")//默认单例模式,依赖范围的注解,prototype:多例

//@Primary 如果有多个实现类,这个注解标记的哪个类上,就默认使用当前这个接口的实现类去使用的


4、生命周期阶段

**单例bean:**singleton

随工厂启动创建构造方法set方法(注入值)init(初始化)构建完成随工厂关闭销毁

**多例bean:**prototype

被使用时创建构造方法set方法(注入值)init(初始化)构建完成JVM垃圾回收销毁

九、Spring+Mybatis【重点】

1、需要导入的依赖:

<!--spring和mybatis整合必须要导入的两个依赖-->
<!--1.mybatis提供和spring无缝整合的jar包-->
<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis-spring</artifactId>
 <version>2.0.5</version>
</dependency>
<!--2.spring的jdbc的jar包-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jdbc</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency>
<!--*********************************************************-->
<!--spring常用依赖-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency> 
<!--mybatis的依赖-->
<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis</artifactId>
 <version>3.5.7</version>
</dependency>
<!--mysql驱动-->
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>8.0.25</version>
</dependency>
<!--分页插件(根据需求选择)-->
<dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper</artifactId>
 <version>5.1.10</version>
</dependency>

2、配置spring-mybatis.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 https://www.springframework.org/schema/context/spring-context.xsd">

<!--
  1、 加载properties文件中数据库参数信息
  2、创建数据源对象(有好几种:①spring-jdbc中的数据源 ②mybatis中的数据源 ③druid中的数据源)
  3、创建SqlSessionFactory对象 SqlSessionFactoryBean对象
  4、配置mapper的扫描器对象
-->

<!--1.加载properties文件中数据库参数信息(输入<property...就会有提示了)-->
<context:property-placeholder location="classpath:db.properties"/>

<!--2.创建数据源对象(有好几种)-->
<bean id="dataSource" 
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}" />
</bean>

<!--3.创建SqlSessionFactory对象 SqlSessionFactoryBean对象-->
<bean id="sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean">
  <!--        配置注入的数据源-->
  <property name="dataSource" ref="dataSource" />
  <!--        配置mybatis映射文件的路径,如果用注解了这个就要注释-->
  <property name="mapperLocations" value="classpath:com/mxd/mapper/*Mapper.xml" />
  <!--        配置实体的别名包扫描-->
  <property name="typeAliasesPackage" value="com.mxd.pojo" />
  
  <!--提供分页插件,如果配置了这个插件,就必须要进行分页查询,不然会抛异常-->
    <!--<property name="plugins">
         <array>
             <bean class="com.github.pagehelper.PageInterceptor"></bean>
         </array>
     </property>-->
  
</bean>


<!--4.spring与mybatis整合:
 配置mapper的扫描器对象      管理DAO实现类的创建,并创建DAO对象,存入工厂管理
 mapper/dao实现对象在工厂中的id是:“首字母小写的-接口的类名”
 mybatis提供的这个和spring整合的依赖中MapperScannerConfigurer,Mapper的配置扫描器必须配置这个类
 他的id必须mapperScannerConfigurer,否则报错
	-->
<bean id="mapperScannerConfigurer" 
    class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
  <!--        必须配置扫描器数据源对象-->
  <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  <!--        配置接口扫描器扫描的默认包路径,然后交给spring  -->
  <property name="basePackage" value="com.mxd.mapper"/>
</bean>

</beans>

3、剩余操作:

3.1 db.properties配置文件
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/testdata?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=000000

3.2 实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dept {
 private Integer deptno;
 private String dname;
 private String loc;
}

3.3 mapper接口
public interface DeptMapper {

 List<Dept> findAll();
}

3.4 services接口和实现类
public interface DeptService {
 List<Dept> findAll();
}

//使用@Service注册bean
@Service
public class DeptServieImpl implements DeptService {
 //使用@Autowired注入
 @Autowired
 private DeptMapper deptMapper;

 @Override
 public List<Dept> findAll() {
     return deptMapper.findAll();
 }
}

3.5 测试
@Test
public void test1(){
 ClassPathXmlApplicationContext applicationContext = 
     new ClassPathXmlApplicationContext("spring-context.xml");
 DeptService bean = applicationContext.getBean(DeptService.class);
 List<Dept> all = bean.findAll();
 System.out.println(all);
}

3.6 错误解析 | 注意事项 | 附加内容
3.6.1 使用@service等注解时

Spring容器在运行的时候,怎么知道从哪个包扫描呢?需要在spring的xml文件中添加

<!--
 在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,
如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean
 注意:如果配置了<context:component-scan>那么<context:annotation-config/>标签
就可以不用再xml中配置了,因为前者包含了后者。
-->
<context:component-scan base-package="com.mxd.service"/>

3.6.2 引入其他的xml文件

可以通过下列标签引入其他的xml文件

<import resource="classpath:spring-mybatis.xml"></import>

3.6.3 其他的数据源
<!--与PooledDataSource集成(mybatis)-->
<bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
 <property name="driver" value="${driverClass}"/>
 <property name="url" value="${url}"/>
 <property name="username" value="${username}"/>
 <property name="password" value="${password}"/>
</bean>

 <!--与DruidDataSource集成(druid)-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
 <!--基本配置-->
 <property name="driverClassName" value="${jdbc.driverClass}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>
</bean>

使用druid数据源时,要导入依赖

<dependency>
 <groupId>com.alibaba</groupId>
 <artifactId>druid</artifactId>
 <version>1.1.10</version>
</dependency>

3.6.4 Druid连接池可选参数
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
 <!--基本配置-->
 <property name="driverClassName" value="${jdbc.driverClass}"/>
 <property name="url" value="${jdbc.url}"/>
 <property name="username" value="${jdbc.username}"/>
 <property name="password" value="${jdbc.password}"/>

 <!-- 配置初始化大小、最小、最大 -->
 <property name="initialSize" value="${jdbc.init}"/>
 <property name="minIdle" value="${jdbc.minIdle}"/>
 <property name="maxActive" value="${jdbc.maxActive}"/>

 <!-- 配置获取连接等待超时的时间 -->
 <property name="maxWait" value="60000"/>

 <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
 <property name="timeBetweenEvictionRunsMillis" value="60000"/>

 <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
 <property name="minEvictableIdleTimeMillis" value="300000"/>
</bean>

3.6.5 分页插件的使用

在service层的实现类中:

@Override
public PageInfo<Shop> findShopByPage(Integer page, Integer size) {
 //设置页面,每页显示条数
 PageHelper.startPage(page,size) ;
 List<Shop> list = shopMapper.findAll();
 //创建PageInfo<Shop>对象
 PageInfo<Shop> pageInfo = PageInfo.of(list);
 return pageInfo;
}

测试类:

@Test
public void test1(){
 ClassPathXmlApplicationContext applicationContext = 
     new ClassPathXmlApplicationContext("spring-config.xml");
 //创建service对象,按照类型创建
 ShopService shopService = applicationContext.getBean(ShopService.class);
 PageInfo<Shop> pageInfo = shopService.findShopByPage(1, 2);
 List<Shop> list = pageInfo.getList();
 System.out.println(list);
 System.out.println(pageInfo);
}

十、Spring+Mybatis+Servlet【重点】

在上面spring+mybatis整合的基础上完成下列操作

1、需要导入的依赖:

<!--servlet-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope><!--不发布-->
</dependency>
<!--fastjson,json解析工具-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

2、剩余操作:

2.1 创建servlet
@WebServlet("/findAll")
public class DeptController extends HttpServlet {
 
 private DeptService deptService;

 //为了防止空指针
 public DeptController(){
     //创建Spring的ioc容器
     ClassPathXmlApplicationContext applicationContext = 
         new ClassPathXmlApplicationContext("spring-context.xml");
     deptService =  applicationContext.getBean(DeptService.class);
     System.out.println(deptService);
 }

 @Override
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

     //处理返回页面的乱码问题方法①:
     resp.setContentType("text/html;charset=utf-8");
     //处理返回页面的乱码问题方法②:
     /*resp.setContentType("application/json");
     resp.setCharacterEncoding("utf-8");*/
     List<Dept> all = deptService.findAll();
     System.out.println(all);//在控制台输出查看一下
     String res = JSONObject.toJSONString(all);
     PrintWriter pw = resp.getWriter();
     pw.write(res);
     pw.flush();
     pw.close();
 }
}

2.2 错误解析

如果发送请求时出现空指针异常,检查注入问题,是否未注入

十一、代理设计模式

1、概念

将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。

2、静态代理

2.1 概念

代理角色和真实角色都需要实现同一个接口

通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。

  • 代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
  • 静态代理的问题
    • 代理类数量过多,不利于项目的管理。
    • 多个代理类的辅助功能代码冗余,修改时,维护性差。
2.2 实现步骤
①UserService接口:
public interface UserSrervice {
 //添加用户
 void addUser();
}

②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
 @Override
 public void addUser() {
     System.out.println("UserServiceImpl:添加了一个用户");
 }
}

③静态代理模板:
public class StaticProxy implements UserService {

 private UserService userService;
 //构造为属性初始化
 public StaticProxy(UserService userService){
     this.userService = userService;
 }

 @Override
 public void addUser() {
     System.out.println("staticproxy:  权限校验!");
     //调用业务层的方法
     userService.addUser();
     System.out.println("staticproxy:  生成日志!");
 }
}

④测试:
@Test
public void staticProxyTest(){
  //最初的使用方式,真实角色
  UserService userService = new UserServiceImpl();
  userService.addUser();
  System.out.println("****************************");
  //创建静态代理对象,业务能力增强(代理角色)
  StaticProxy staticProxy = new StaticProxy(userSrervice);
  staticProxy.addUser();
}

⑤运行结果:
UserServiceImpl:添加了一个用户
****************************
staticproxy:  权限校验!
UserServiceImpl:添加了一个用户
staticproxy:  生成日志!

3、动态代理

动态创建代理类的对象,为原始类的对象添加辅助功能。

3.1 JDK动态代理实现(基于接口)

基于接口实现 ,通过反射的方式,执行代理处理程序的时候就获取了代理对象

①UserService接口:
public interface UserSrervice {
 //添加用户
 void addUser();
}

②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
 @Override
 public void addUser() {
     System.out.println("UserServiceImpl:添加了一个用户");
 }
}

③JDK动态代理模板:
public class JdkProxy implements InvocationHandler {//java.lang.reflect.InvocationHandler
 //声明被代理接口
 private Object target;

 //注入
 public JdkProxy(Object target) {
     this.target = target;
 }

 //处理代理实例,调用被代理对象的方法!
 /*
     Object proxy:具体的代理类对象,其中有被代理接口的方法!
     Method method:被调用的方法
     Object[] args:被调用方法的参数
 */
 //InvocationHandler接口是动态代理的桥梁
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     System.out.println("JdkProxy:  权限校验!");
     //调用被代理对象的方法
     Object obj = method.invoke(target, args);
     System.out.println("JdkProxy:  生成日志!");
     return obj;
 }
}

④测试:
@Test
public void jdkProxyTest(){
 //最初的使用方式,真实角色
 UserService userSrervice1 = new UserServiceImpl();
 userSrervice1.addUser();
 System.out.println("****************************");
 JdkProxy jdkProxy = new JdkProxy(userSrervice1);
 /* 用代理类,创建代理实例
    参数为:
    	1.类加载器ClassLoader loader
    	2.被代理的接口 Class<?>[] interfaces
    	3.调用处理器对象 InvocationHandler h
    不能强转为实现类,因为不能基于类实现,只能基于接口实现
 */
 //Proxy类是动态代理的入口
 UserService UserService2 = (UserService) Proxy.newProxyInstance(
     jdkProxy.getClass().getClassLoader(),
     userSrervice1.getClass().getInterfaces(),
     jdkProxy);

 //调用业务方法
 UserService2.addUser();
}

⑤运行结果:
UserServiceImpl:添加了一个用户
****************************
JdkProxy:  权限校验!
UserServiceImpl:添加了一个用户
JdkProxy:  生成日志!

3.2 CGlib动态代理实现(基于继承)
①在pom.xml中添加依赖
<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib</artifactId>
 <version>3.3.0</version>
</dependency>

②UserService接口:
public interface UserSrervice {
 //添加用户
 void addUser();
}

③UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
 @Override
 public void addUser() {
     System.out.println("UserServiceImpl:添加了一个用户");
 }
}

④CGlib动态代理模板:
public class CglibProxy implements InvocationHandler {//net.sf.cglib.proxy.InvocationHandler

 //代理的目标对象
 private Object target;

 public Object createProxyInstance(Object target){
     this.target=target;

     //创建目标对象的代理对象
     //1.生成代理对象
     Enhancer enhancer = new Enhancer();
     //2.设置父类
     enhancer.setSuperclass(target.getClass());
     //3.设置回调 对象:代表当前类对象地址值引用 this
     enhancer.setCallback(this);
     //4.创建当前代理角色对象
     Object obj = enhancer.create();
     return obj;
 }

 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     System.out.println("CglibProxy:  权限校验!");
     //调用被代理对象的方法
     Object obj = method.invoke(target, args);
     System.out.println("CglibProxy:  生成日志!");
     return obj;
 }
}

⑤测试:
@Test
public void cglibProxyTest(){
 //最初的使用方式,真实角色
 UserService userSrervice1 = new UserServiceImpl();
 userSrervice1.addUser();
 System.out.println("****************************");

 CglibProxy cglibProxy = new CglibProxy();
 //基于子类,实现类
 UserServiceImpl userService2 = 
     (UserServiceImpl) cglibProxy.createProxyInstance(userSrervice1);
 userService2.addUser();
}

十二、AOP(面向切面编程)【重点】

1、概念

AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。对逻辑业务方法和非业务方法进行分离。对逻辑业务方法和非业务方法进行分离。

注:目前上面叙述的代理方式都可以实现针对业务方法进行增强,而且JDK动态代理/CGlib动态代理将增强代码和业务方法直接分离了,但是写法很麻烦,Spring提供一个核心AOP :面向切面编程 基于OOP(面向对象)

2、AOP开发术语即解释

2.1 通知、增强(Advice):

就是你想要的功能,例如安全,事物,日志等。先定义好,然后在想用的地方用一下。分为:前置通知、后置通知、异常通知、最终通知、环绕通知等。

2.2 连接点(Joinpoint):

连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectj还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

2.3 切入点(Pointcut):

被Spring切入连接点。上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对吧,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

2.4 目标对象(Target):

代理的目标对象。引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,它可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

2.5 引介(Introduction):

一种特殊的增强,可在运行期为类动态添加Field和Method。

允许我们向现有的类添加新方法属性。就是把切面(也就是新方法属性:通知定义的)用到目标类中

2.6 织入(Weaving):

把通知应用到具体的类,进而创建新的代理类的过程。

把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时

2.7 代理(Proxy):

被AOP织入通知后,产生的结果类。

2.8 切面(Aspect):

由切入点和通知组成,将横切逻辑织入切面所指定的连接点中。

切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切入点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

3、作用

Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。

就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等

4、开发流程

4.1 在pom.xml中添加依赖
<!--spring常用依赖-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-context</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency>
<!--spring切面jar包-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aspects</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency>

4.2 定义原始类和接口
UserService接口:
public interface UserSrervice {
 //添加用户
 void addUser();
}

UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice {
 @Override
 public void addUser() {
     System.out.println("UserServiceImpl:添加了一个用户");
 }
}

4.3 定义通知类

我们这里只利用一个通知做介绍,其余都差不多

//前置通知
public class BeforeHandler implements MethodBeforeAdvice {

 @Override
 public void before(Method method, Object[] args, Object target) throws Throwable {
     System.out.println("前置通知:执行业务方法之前");
 }
}

4.4 在spring-context.xml中定义bean标签
<!--service层bean对象-->
<bean id="userServiceImpl" class="com.mxd.service.impl.UserServiceImpl"/>
<!--前置通知-->
<bean id="beforeHandler" class="com.mxd.aop.BeforeHandler"/>

4.5 定义切入点(PointCut),形成切面(Aspect)
4.6.1 方式一:使用原生的Spring API接口
 <!--pointcut:切点
        使用切点表达式
        execution()
         execution(* *.*.service.impl.*.*(..))
         第一个* 是默认的 和第二个*必须有空格,必须写
         第二个* :就是com
         第三个* :mxd
         第四个:* :一般不用写*,要写为明确给业务层的包名service
         第五个:* :一般service下的impl包
         第五个*:impl包下的实现类
         第六个*:类中方法名,也可以用*代替
         (..):不明确方法的参数时在括号里点两个点
-->

<aop:config>
 <!--配置切入点-->
 <aop:pointcut id="pointcut" expression="execution(* *.*.service.*.*.addUser(..))"/>
 <!--如果方法名唯一可以像下面这样写,但是不建议写-->
 <!--<aop:pointcut id="pointcut" expression="execution(* addUser(..))"/>-->
 <!--组装切面-->
 <aop:advisor advice-ref="beforeHandler" pointcut-ref="pointcut"/>
</aop:config>

通知类接口:

前置通知:MethodBeforeAdvice

后置通知:AfterAdvice

后置通知(最终通知):AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值

异常通知:ThrowsAdvice

环绕通知:MethodInterceptor

4.6.2 方式二:自定义切面

①自定义切面:

public class MyAspect {
 public void before() {
     System.out.println("自定义前置通知!");
 }
 public void after(){
     System.out.println("自定义后置通知!");
 }
}

②spring-context.xml中配置:

<bean id="myAspect" class="com.mxd.aop.MyAspect"/>

<aop:config>
 <!--自定义切面,注意要写ref属-->
 <aop:aspect ref="myAspect">
      <!--切点-->
     <aop:pointcut id="pointcut" expression="execution(* *.*.*.*.*.addUser(..))"/>
      <!--前置通知-->
     <aop:before method="before" pointcut-ref="pointcut"/>
     <aop:after method="after" pointcut-ref="pointcut"/>
 </aop:aspect>
</aop:config>

③测试:

@Test
public void aopTest(){
 //最初的使用方式,真实角色
 UserService userSrervice1 = new UserServiceImpl();
 userSrervice1.addUser();
 System.out.println("****************************");

 ClassPathXmlApplicationContext applicationContext = 
 			new ClassPathXmlApplicationContext("spring-context.xml");
 UserService userService = 
 			applicationContext.getBean("userServiceImpl", UserService.class);
 userService.addUser();
}

④运行结果:

UserServiceImpl:添加了一个用户
****************************
自定义前置通知!
UserServiceImpl:添加了一个用户
自定义后置通知!

5、注解开发:

①自定义切面

@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
 // 定义切入点
 @Pointcut("execution(* com.mxd.service.impl.UserServiceImpl.*(..))")
 public void pc(){}
 
 @Before("pc()") // 前置通知
 public void mybefore(JoinPoint a) {
     System.out.println("target:"+a.getTarget());
     System.out.println("args:"+a.getArgs());
     System.out.println("method's name:"+a.getSignature().getName());
     System.out.println("before~~~~");
 }

 @AfterReturning(value="pc()",returning="ret") // 后置通知
 public void myAfterReturning(JoinPoint a,Object ret){
     System.out.println("after~~~~:"+ret);
 }
 
 @AfterThrowing(value="pc()",throwing="ex") // 异常通知
 public void myThrows(JoinPoint jp,Exception ex){
     System.out.println("throws");
     System.out.println("===="+ex.getMessage());
 }
 
  @Around("pc()") // 环绕通知(使用around时,将其他的注解注释了)
 public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
     //是要将所有的通知关联进来
     Object target = null ; //代理角色
     try{
         System.out.println("执行业务方法前,前置通知") ;
         target= p.proceed();//执行底层业务方法
         System.out.println("执行业务方法之后的,后置通知");
         return target;
     } catch (Throwable throwable) {
         throwable.printStackTrace();
         System.out.println("执行异常通知..."+throwable.getMessage());
     }finally {
         System.out.println("after..通知");
     }
 }
}

②配置spring-context.xml文件:

<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

十三、事务【重点】

1、事务属性

1.1 隔离级别
1.1.1 概念

isolation 隔离级别

名称描述
default(默认值)(采用数据库的默认的设置) (建议)
read-uncommited读未提交
read-commited读提交 (Oracle数据库默认的隔离级别)
repeatable-read可重复读 (MySQL数据库默认的隔离级别)
serialized-read序列化读

隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read

1.1.2 特性
  • 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
  • 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
1.1.3 并发问题

事务并发时的安全问题

问题描述
脏读一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止
不可重复读一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止
幻影读一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止
1.2 传播行为

propagation传播行为

当涉及到事务嵌套(Service调用Service)时,可以设置:

  • SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
  • REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
1.3 读写性

readonly 读写性

  • true:只读,可提高查询效率。(适合查询)
  • false:可读可写。 (默认值)(适合增删改)
1.4 事务超时

timeout事务超时时间

当前事务所需操作的数据被其他事务占用,则等待。

  • 100:自定义等待时间100(秒)。
  • -1:由数据库指定等待时间,默认值。(建议)
1.5 事务回滚

rollback-for 回滚属性

  • 如果事务中抛出 RuntimeException,则自动回滚
  • 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
  • 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”

2、具体的实现流程:

2.1 在pom.xml中添加依赖
<!--提供spring的事务支持的jar包-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-tx</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency>

2.2 编写相关的mapper接口,xml
@Repository
public interface BankMapper {
 void subMoney(Bank bank);
 void addMoney(Bank bank);
}

<update id="subMoney" parameterType="com.mxd.pojo.Bank">
 update bank
 set money=money - #{money}
 where id = #{id}
</update>

<update id="addMoney" parameterType="com.mxd.pojo.Bank">
 update bank
 set money=money + #{money}
 where id = #{id}
</update>

2.3 编写service层接口和实现类
public interface BankService {
 void transfer_accounts(int id1,int id2,float price);
}

@Service
@Transactional
public class BankServiceImpl implements BankService {
 @Autowired
 private BankMapper bankMapper;

 @Override
 @Transactional(propagation = Propagation.MANDATORY)
 public void transfer_accounts(int id1,int id2,float price) {
     //转账人,减去金额
     Bank bank1 = new Bank();
     bank1.setId(id1);
     bank1.setMoney(price);
     bankMapper.subMoney(bank1);

     int i=1/0;      //异常报错

     //被转账人,加上金额
     Bank bank2 = new Bank();
     bank2.setId(2);
     bank2.setMoney(price);
     bankMapper.addMoney(bank2);
 }
}

2.4 编写spring-context.xml
2.4.1 配置事务管理器

事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。

<!--xml配置方式; 引入配置事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑-->
<bean id="dataSourceTransactionManager"
				class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<!--管理数据源-->
	<property name="dataSource" ref="dataSource"></property>
</bean>

注意: DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败

2.4.2 配置事务通知

基于事务管理器,进一步定制,生成一个额外功能:Advice。

此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。

<!--配置事务通知:
         id:事务控制的标识
         transaction-manager:引入指定的事务管理器
-->
<tx:advice id="transactionInterceptor" transaction-manager="dataSourceTransactionManager">
 <tx:attributes>
 <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT"    
           	propagation="REQUIRED" read-only="false"/>-->
 	<tx:method name="*" rollback-for="Exception"/>
 </tx:attributes>
</tx:advice>

2.4.3 编织

将事务管理的Advice 切入需要事务的业务方法中

<aop:config>
 <aop:pointcut id="pointcut" expression="execution(* transfer_accounts(..))"/>
	<!-- 组织切面 -->
 <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="pointcut"/>
</aop:config>

2.5 测试
@Test
public void test1(){
 ClassPathXmlApplicationContext applicationContext = 
     					new ClassPathXmlApplicationContext("spring-context.xml");
 BankService bean = applicationContext.getBean(BankService.class);
 bean.transfer_accounts(1, 2, 100);
}

3、注解实现事务控制

3.1 编写 spring-context.xml 文件
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

3.2 具体操作

用于控制事务切入

  • @Transactional
  • 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//标记在类上,表示类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
	...
 //标记在方法上表示该方法自己的事务控制,仅对此方法有效
	@Transactional(propagation=Propagation.REQUIRED)//默认情况
	public List<User> queryAll() {
		return userDao.queryAll();
	}
	public void save(User user){
		userDao.save(user);
	}
}

3.3 注解参数详解

事物传播行为介绍:

@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
  @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
  @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
  @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
  @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

事物超时设置:

@Transactional(timeout=30) //默认是30秒

事务隔离级别:

@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
  @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
  @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
  @Transactional(isolation = Isolation.SERIALIZABLE):串行化

MYSQL: 默认为REPEATABLE_READ级别
  SQLSERVER: 默认为READ_COMMITTED

脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据

十四、Spring-test和junit整合

1、在pom.xml文件中添加依赖

<!--spring-test-->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-test</artifactId>
 <version>5.2.5.RELEASE</version>
</dependency>
<!--junit-->
<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.13.1</version>
 <scope>test</scope>
</dependency>

2、具体操作:

可以免去工厂的创建过程;

可以直接将要测试的组件注入到测试类。

@RunWith(SpringRunner.class) //由SpringJUnit4ClassRunner启动测试
@ContextConfiguration("classpath:spring-context.xml") //spring的配置文件位置
public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入

 @Autowired // 注入要测试的组件
 @Qualifier("userDAO")
 private UserDAO userDAO;

 @Test
 public void test(){
     // 测试使用userDAO
     userDAO.queryUser();
     ....
 }
}

十五、注解开发

1、 声明bean

用于替换自建类型组件的 <bean…>标签;可以更快速的声明bean

  • @Service 业务类专用
    @Repository dao实现类专用
    @Controller web层专用
  • @Component 通用
  • @Scope 用户控制bean的创建模式
// @Service说明 此类是一个业务类,需要将此类纳入工厂  等价替换掉 <bean class="xxx.UserServiceImpl">
// @Service默认beanId == 首字母小写的类名"userServiceImpl"
// @Service("userService") 自定义beanId为"userService"
@Service //声明bean,且id="userServiceImpl"
@Scope("singleton") //声明创建模式,默认为单例模式 ;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
 	...   
}

2、 注入(DI)

用于完成bean中属性值的注入

  • @Autowired 基于类型自动注入
  • @Resource 基于名称自动注入
  • @Qualifier(“userDAO”) 限定要自动注入的bean的id,一般和@Autowired联用
  • @Value 注入简单类型数据 (jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
    
    @Autowired //注入类型为UserDAO的bean
    @Qualifier("userDAO2") //如果有多个类型为UserDAO的bean,可以用此注解从中挑选一个
    private UserDAO userDAO;
}

@Service
public class UserServiceImpl implements UserService {
    
	@Resource("userDAO3") //注入id=“userDAO3”的bean
    private UserDAO userDAO;
    /*
    @Resource //注入id=“userDAO”的bean
    private UserDAO userDAO;
    */
}

public class XX{
    @Value("100") //注入数字
    private Integer id;
    @Value("shine") //注入String
	private String name;
}

3、 事务控制

用于控制事务切入

  • @Transactional
  • 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
	...
    //该方法自己的事务控制,仅对此方法有效
	@Transactional(propagation=Propagation.SUPPORTS)
	public List<User> queryAll() {
		return userDao.queryAll();
	}
	public void save(User user){
		userDao.save(user);
	}
}

4、注解所需配置

<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
	
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>

5、 AOP开发

5.1 注解使用
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
    // 定义切入点
    @Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))")
    public void pc(){}
    
    @Before("pc()") // 前置通知
    public void mybefore(JoinPoint a) {
        System.out.println("target:"+a.getTarget());
        System.out.println("args:"+a.getArgs());
        System.out.println("method's name:"+a.getSignature().getName());
        System.out.println("before~~~~");
    }

    @AfterReturning(value="pc()",returning="ret") // 后置通知
    public void myAfterReturning(JoinPoint a,Object ret){
        System.out.println("after~~~~:"+ret);
    }
    
    @Around("pc()") // 环绕通知
    public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
        System.out.println("interceptor1~~~~");
        Object ret = p.proceed();
        System.out.println("interceptor2~~~~");
        return ret;
    }
    
    @AfterThrowing(value="pc()",throwing="ex") // 异常通知
    public void myThrows(JoinPoint jp,Exception ex){
        System.out.println("throws");
        System.out.println("===="+ex.getMessage());
    }
}

5.2 配置
<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

十六、lombok:

Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。

1、添加依赖

<dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.20</version>
</dependency>

2、例子

@Data
@NoArgsConstructor   //提供无参构造
@AllArgsConstructor  //提供所有参数的有参构造
public class Bank {
 private Integer id;
 private String username;
 private Float money;
}

十七、附加:

1、BeanFactory接口和ApplicationContext接口,有什么区别(生命周期角度考虑)

答:

​ BeanFactory是Spring容器中的顶层接口,ApplicationContext是它的子接口。
​ BeanFactory和ApplicationContext的区别:创建对象的时间点不一样。
​ ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。(立即加载的思想来创建bean对象)
​ BeanFactory:什么使用什么时候创建对象。(延迟加载的思想来创建bean对象)

BeanFactory顶层的父接口
ApplicationContext:间接的子接口

共同点:都是工厂接口
前者:老工厂接口,部分不支持SpringAOP结束 
	管理bean对象的操作 bean对象的初始化/销毁...,不支持国际化
后者:是常用的工厂接口
	ClassPathXmlApplicationContext:就是ApplicationContext的具体的子实现类 
	很多子实现类
		ClassPathXmlApplicationContext:获取项目下类路径下的配置文件(推荐:resources下面的配置文件)
		FileSystemXmpApplicationContext:可以获取系统文件的配置文件
		GenericXmlApplicationContext:通用的子类(统一资源定位的文件)
	ApplicationContext:支持国际化/SpringAOP..	

2. 简述以下SpringIOC

控制反转/控制倒置,通过引入SpringIOC容器,利用依赖注入的方式,实现对象间的解耦
实现对象的解耦,利用工厂模式.
创建容器对象,加载Spring的配置文件----读取xml文件 (dom4j解析:基于面向对象/sax解析:基于事件编程)

<?xml version="1.0" encoding="UTF-8"?>
<beans>
	<bean id="" class=""></bean>
</beans>
容器对象.getBean("唯一标识"/类型的Class)
通过反射的方式:获取class属性  "xx.xx.x"---->Class.forName("类的全限定名称");---Class  
										创建当前类实例
<bean id="标识" class="类的全限定名称"></bean>


3.什么是Spring的AOP

面向切面编程:基于OOP面向对象的!
利用通知类型:前置通知,后置通知,异常通知,最终通知(不去设置),环绕通知(前面所有通知都可以进行配置)
实现业务层方法的增强(控制)

需要实现类似于jdk动态代理/cglib动态代理:将业务层代码和增强代码进行分离

好处:
  1)方便后期业务的方法维护管理:管理整个业务方法service
  2)通过AOP实现程序间的解耦(Spring ioc) 
	 3)业务层代码和增强代码进行分离
  
  //简单的配置---- 实现了SpringAOP相关的接口 xxAdvice
  <aop:config>
  		<aop:pointcut id="" 表达式="execulotion(* *.*.service.impl.*.*(..))" >
  		<aop:advice>
  <aop:config>


4.jdk动态代理和Cglib动态代理的区别

jdk动态代理基于接口实现的:需要有一个接口:xxService
	代理实现过程: java.lang.reflect. InvocationHandler  :代理的处理程序
	
cglib:基于子类实现
		InvocationHandler(cglib.proxy包下的) :代理的处理程序
		
		实现的过程:需要增强类Enhancer
		
		 //定义方法:bindObject
 public Object bindObject(Object tareget){
     this.tareget = tareget ;

     //1)使用步骤
     // 创建增强类
     Enhancer enhancer = new Enhancer() ;
     //2)给Enhancer增强类对象 进行赋值
     enhancer.setSuperclass(tareget.getClass());
     //3)设置回调 对象:代表当前类对象地址值引用 this
     enhancer.setCallback(this);
     //4)创建当前角色对象
     Object obj = enhancer.create();
     return obj ;

 }

5.列出Spring常见的注解,分别阐述其意思

Spring注解
创建bena对象的注解
@Component: 通用的注解
@Service   :业务层
@Controller :控制器
@Reospitory :持久层dao层

注入注解
@Autowired:自动按照类型注入
@Qualifier:按照唯一标识进行注入,结合使用

@Resource(name="唯一标识")

生命相关的注解
@PostConstruct:初始化
@PreDestroy:销毁
@Scope("名称"):单例/多列
//@Primary 指定唯一标识

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熱愛。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值