Spring 基础基础
1、Spring 基础
1.1 简介
由一个叫 Rod Johnson 的程序员在 2002 年最早提出(interface 21 框架)并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。
Spring 理念:使现有技术更加容易使用,本身是一个大杂烩,整合现有框架。
下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/
1.2 核心部分
IOC:控制反转,把创建对象过程交给 Spring 进行管理 。
Aop:面向切面,不修改源代码进行功能增强。
1.3 优点
Spring 是一个开源的免费框架(容器)。
Spring 是一个轻量级的,非入侵的框架。
控制反转(IOC),面向切面编程(AOP)。
支持事物的处理,对框架整合的支持
总结:Spring 是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
1.4 拓展
Spring Boot
一个快速开发的脚手架
基于 SpringBoot 可以快速地开发单个微服务
约定大于配置
Spring Cloud
SPringCloud 是基于 SpringBoot 实现地
很多公司都在使用 SpringBoot 进行快速开发,学习 SpringBoot 的前提是完全掌握 Spring 以及 SpringMVC!
2、IOC 容器
2.1 什么是 IOC
控制反转,把对象创建和对象之间的调用过程交给 Spring 进行管理。
使用 IOC 的目的:为了降低耦合。
IOC 的底层原理:xml 解析、工厂模式、反射。
IOC 思想基于 IOC 容器完成,IOC 容器底层就是对象工厂。
Spring 提供 IOC 容器实现两种方式:(两个接口)
BeanFactory
:IOC 容器基本实现,是 Spring 内部使用 接口,不提供开发人员进行使用 。加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象。ApplicationContext
:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人 员进行使用。
加载配置文件时候就会把在配置文件对象进行创建。
ApplicationContext 接口有实现类有
FileSystemXmlApplicationContext
和ClassPathXmlApplicationContext
。
2.2 IOC 操作(Bean 管理)
什么是 Bean 管理
Spring 创建对象
Spirng 注入属性
Bean 管理操作有两种方式
基于 xml 配置文件方式实现
基于注解方式实现
2.3 Bean 管理——基于 xml 方式
alias
:用于设置别名
在 spring 配置文件中,使用 bean
标签,并对应属性,实现对象创建。
在 bean 标签有很多属性,介绍常用的属性 :
id
: bean 的唯一标识 。class
:类全路径(包类路径) 。name
: 早期版本使用,可以用于别名设置,可以取多个别名,用逗号或者分号分开即可。scope
:prototype、request、session、singleton
创建对象时候,默认使用无参构造方法完成对象创建。所以存在有参构造时最好把无参构造显示的写出来。
DI:依赖注入,就是注入属性。
方式:
使用 setter 方法。
使用使用有参数构造进行注入。
Spring 可以根据这两种方式分别使用property
和constructor-arg
标签进行配置,从而注入属性。
property
注入<bean id="helloSpring" class="com.th.pojo.Hello"> <property name="str" value="HelloSpring"/> </bean>name
: 类里面属性名称value
:向属性注入的值ref
:标识引用 Spring 容器中已经创建好的的对象<bean id="student" class="com.th.pojo.Student"> <!--第一种:普通值注入,用 value--> <property name="name" value="ThreePure"/> <!--第二种:Bean 注入,用 ref--> <property name="address" ref="address"/> <!--第三种:数组--> <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> <!--第五种:Map--> <property name="card"> <map> <entry key="身份证" value="13512626346"/> <entry key="银行卡" value="13512624327236346"/> </map> </property> <!--第六种:Set--> <property name="games"> <set> <value>LOL</value> <value>COC</value> <value>BOB</value> </set> </property> <!--第七种:空指针--> <property name="wife"> <null/> </property> <!--第八种:Properties--> <property name="info"> <props> <prop key="学号">13891</prop> <prop key="性别">男</prop> <prop key="年龄">21</prop> </props> </property> </bean>
创建外部文件,properties 格式文件,写数据库信息
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/mybatis prop.userName=root prop.password=mysqlpw
把外部 properties 属性文件引入到 spring 配置文件中
<!--引入外部属性文件--> <context:property-placeholder location="druid.properties"/>
注意:使用这个标签时必须在配置文件中导入
context
约束。获取外部文件的值 (使用
${}
获取 properties 的值)<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"/> <property name="url" value="${prop.url}"/> <property name="username" value="${prop.userName}"/> <property name="password" value="${prop.password}"/> </bean>
获取使用
DruidDataSource
对象public void testOne(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class); //jdbc:mysql://localhost:3306/mybatis System.out.println(dataSource.getUrl()); }
p 名称空间注入(了解):如果对于一个属性很多的实体类,那么
property
使用过于繁琐,p 名称空间是一种简化的方法。第一步:添加 p 名称空间到配置文件
xmlns:p="http://www.springframework.org/schema/p"
<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" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
第二步:进行属性注入,在
bean
标签里面进行操作<bean id="user1" class="com.th.pojo.User" p:age="20" p:name="TH"/>
注意:p 命名空间依赖于 getter 和 setter 方法注入,所以需要设置 getter 和 setter 方法。
constructor-arg
注入<!--方式一:使用下标的方式--> <bean id="user" class="com.th.pojo.User"> <constructor-arg index="0" value="狂神"/> </bean> <!--方式二:通过参数类型--> <bean id="user" class="com.th.pojo.User"> <constructor-arg type="java.lang.String" value="kunagshen"/> </bean> <!--方式三:通过属性名--> <!--name:可以设置别名,可以同时设置多个别名,空格、逗号,分号分隔--> <bean id="user" class="com.th.pojo.User" name="u1 u2,u3;u4"> <constructor-arg name="name" value="KS"/> </bean>name
: 类里面属性名称。value
:向属性注入的值。type
: 类属性的类型。index
:类属性的下标(0 表示第一个属性)。ref
:标识引用 Spring 容器中已经创建好的的对象。通过参数类型注入属性只能对于一些简单的不含相同属性实体类,局限性较大。
最推荐通过属性名进行属性注入。
c 名称空间注入(了解):
第一步:添加 c 名称空间到配置文件
xmlns:c="http://www.springframework.org/schema/c""
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
第二步:进行属性注入,在
bean
标签里面进行操作<bean id="user2" class="com.th.pojo.User" c:age="18" c:name="三淳" scope="prototype"/>
注意:c 命名空间依赖于构造器注入,所以需要设置有参构造方法。
Bean 的作用域
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-scopes
singleton
:单例模式,Spring 默认使用单例模式。加载 spring 配置文件时候就会创建单实例对象<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
prototype
:原型模式,每次从容器中 get 的时候,都会产生新的对象。<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
request
、session
、application
、websocket
在 Web 开发种使用。 在调用getBean
方法时候创建多实例对象。Bean 的自动装配
Spring 种有 3 种装配方式:
在 xml 中显示配置
在 java 中显示配置
隐式的自动装配 bean : Spring 在上下文种自动寻找,并自动给 bean 装配属性。
<!--这个 id 必须小写,否则报异常--> <bean id="cat" class="com.th.pojo.Cat"/> <bean id="dog" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People" autowire="byName"> <property name="name" value="ThreePure1"/> <!--其他对象属性在配置后可以自动装配--> </bean>
注意: 使用 byName 需要保证所有的 bean 的 id 唯一,并且这个 bean 需要跟自动注入的类属性名称一样!
<!--因为是按照类型装配,所以 id 可以省略--> <bean class="com.th.pojo.Cat"/> <bean id="dog" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People" autowire="byType"> <property name="name" value="ThreePure2"/> </bean>
注意: 使用 byType 需要保证所有的 bean 的 class 唯一,并且这个 bean 需要跟自动注入的属性的类型一致!
2.4 Bean 管理——基于注解方式
使用注解须知
在 Spring4 之后,使用注解开发必须要导入 AOP 包。导入
spring-webmvc
后包含了 AOP 的包。导入约束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
配置注解的支持
<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 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/> <!--扫描指定的包,这个包下所有使用的注解就会生效--> <context:component-scan base-package="com.th"/> </beans>
开启组件扫描,配置扫描指定的包,使注解完全生效 ,多个包使用逗号隔开。
<context:component-scan base-package="com.th"/>
.当然也可以对扫描进行更加细致的配置:
use-default-filters="false"
:不使用默认的过滤器,可以自定义配置。context:include-filter
标签: 设置扫描哪些内容,可以定义类型以及表达式。<!--扫描 com.th 包下的 Controller 注解--> <context:component-scan base-package="com.th" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
context:exclude-filter
标签: 设置哪些内容不进行扫描,可以定义类型以及表达式。<!--扫描 com.th 包下的除 Controller 以外的注解--> <context:component-scan base-package="com.th"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan>
使用注解
@Component
: 组件、放于类上,表示创建这个类的对象,相当于<bean id="user" class="com.th.pojo.User" />
。import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component public class User { //相当于:<property name="name" value="TH"/> 也可以放在 setter 方法前 //@Value("TH") public String name; @Value("sanchun") public void setName(String name) { this.name = name; } }
在注解里面
@component
的value
属性值可以省略不写,默认值是类名称,首字母小写。比如User
类的user
。public class User { //相当于:<property name="name" value="TH"/> 也可以放在 setter 方法前 @Value("TH") public String name; }public String name; @Value("sanchun") public void setName(String name) { this.name = name; }
对于复杂的数据注入到属性中,建议使用 xml 方式进行注入。
dao 层
@Repository
service 层
@Service
controller
@Controller
这四个注解的功能一致,都是代表将某个类注册到 Spring 中,装配 bean,只是后三者在 Web 开发中按照 MVC 架构分层特定使用。
在导入注解约束以及对注解支持的情况下使用
@Autowired
作用范围:属性前、setter 方法前。
Autowired 我们可以不用编写 Setter 方法了,前提是你这个自动装配的属性在 IOC(Spring)容器中存在,且符合 byType 类型。
public @interface Autowired { boolean required() default true; }
根绝以上源码,可以知道
@Autowired
注解可以传递参数,required
用于设定装配属性是否允许为空。如果定义了@Autowired
的required
属性为false
,说明这个属性可以为空,否则不能为空。@Nullable
:有相似作用,字段标记了这个注解,说明这个字段可以为 null。@Qualifier
:当一个 Spring 容器中含有多个相同类的对象时,自动装配的环境较为复杂,可以结合使用@Qualifier
注解来实现选取具体的某一个对象。其value
值为确定的其中bean
的id
值。典型案例就是一个接口含有多个实现类。Resource
:java 原生对自动装配的支持。默认使用byName
实现,其次才是对byType
的支持,若二则都不存在则会报错。Resource
的name
属性可以实现与@Qualifier
类似的功能。<bean id="cat11" class="com.th.pojo.Cat"/> <!-- <bean id="cat22" class="com.th.pojo.Cat"/>--> <bean id="dog11" class="com.th.pojo.Dog"/> <bean id="dog22" class="com.th.pojo.Dog"/> <bean id="man" class="com.th.pojo.People"> <property name="name" value="ThreePure2"/> </bean>import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.lang.Nullable; import javax.annotation.Resource; public class People { @Nullable private String name; @Resource(name = "cat11") private Cat cat; @Autowired @Qualifier(value = "dog22") private Dog dog; public String getName() { return name; } public void setName(String name) { this.name = name; } public Cat getCat() { return cat; } public Dog getDog() { return dog; } }
使用
@Scope
注解实现对作用域范围的控制,括号内直接输入具体的作用域并用""包起来。@Component @Scope("prototype") public class User { …… }
2.5 使用 JavaConfig 实现配置
以上即便是使用注解方式也并没有完全脱离 xml 文件,而 JavaConfig 就是完全使用注解来实现。
实体类中同样可以通过
@Component
注解来进行创建对象,通过@Value
实现属性注入。需要一个配置类来替代
applicationContext.xml
的功能@Configuration
注解标识的类表示一个配置类被
@Configuration
注解标识的类也会被 Spring 容器托管,注册到容器中,因为他本来就是一个@Component
。需要配置类中的方法代替
applicationContext.xml
中bean
标签进行 bean 的注册。这个方法的名字,就相当于 bean 标签的 id 属性
这个方法的返间值,就相当 bean 标签中的 class 属性,也就是要注入到容器的对象!
通过 Annotationconfig 上下文来获收容器,通过配置类的 class 对象加载!
AnnotationConfigApplicationContext
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
其他注解
@ComponentScan
: 用于自动扫描包@Import
: 用于导入其他配置类
2.6 Bean 的生命周期
通过构造器创建 bean 实例(无参数构造)
为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
调用 bean 的初始化的方法(需要进行配置初始化的方法)
bean 可以使用了(对象获取到了)
当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)
结果:
注意:需要使用 ApplicationContext 的实现类来实现手动销毁操作。
另外,Bean 的完整生命周期还有包括两项在初始化之前的状态。实现这两个状态需要为 Spring 配置后置处理器。实现后置处理器需要创建一个类并实现BeanPostProcessor
接口并重写postProcessBeforeInitialization
和postProcessAfterInitialization
两个方法,这两个方法对应这额外的两种状态。
有了后置处理器后需要将其配置到 Spring 中。在 Spring 中,只需要将这个后置处理器像配置 bean 一样在applicationContext.xml
中配置那么这个 Spring 容器中的全部 bean 都会加上后置处理器。
输出结果为:
3、AOP
3.1 什么是 AOP
面向切面编程(方面),利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗地讲就是不修改原有代码实现新功能的添加。
案例:原有的数据增删改查前后添加日志功能。
Spring 框架一般都是基于 AspectJ
实现 AOP 操作。AspectJ 不是 Spring 组成部分,独立 AOP 框架,一般把 AspectJ 和 Spirng 框架一起使用,AOP 操作可以基于 xml 配置文件实现 或者基于注解方式实现(使用)。
3.2 AOP 原理
AOP 底层使用动态代理,动态代理有两种,分别为JDK动态代理
和CGLIB动态代理
。
原理:创建接口实现类代理对象,增强类的方法。
使用:
java.lang.Object
java.lang.reflect.Proxy
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
第一参数,类加载器 。
第二参数,增强方法所在的类,这个类实现的接口,支持多个接口 。
第三参数,实现这个接口
InvocationHandler
,创建代理对象,写增强的部分。使用动态代理前:
public interface UserDao { public int add(int a, int b); public String update(String id); }public class UserDaoImpl implements UserDao{ @Override public int add(int a, int b) { System.out.println("add() executed"); return a+b; } @Override public String update(String id) { System.out.println("update()executed"); return id; } }
使用动态代理增加功能:
第一步:创建代理对象类、 实现
InvocationHandler
接口。class UserDaoProxy implements InvocationHandler { private Object obj; /**把创建的是谁的代理对象,就把谁传递过来,通过有参构造传递。比如这里产生的是 UserdaoImpl 的代理对象。*/ /**为例让这个代理类使用更加广泛,直接传递 Object 对象,*/ public UserDaoProxy(Object obj) { this.obj = obj; } /**增强的逻辑代码*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 被增强方法之前 System.out.println("It was executed before the "+method.getName()+",and the args :"+ Arrays.toString(args)); // 被增强方法执行 Object res = method.invoke(obj, args); // 被增强方法之后 System.out.println("It was executed before ... :"+obj.toString()); return res; } }
在创建代理对象代码时,需要代理哪个对象就传递哪个对象。通过代理类的有参构造实现代理对象的传递。
通过重写
invoke
方法,在其内部添加扩展功能。其中参数:proxy : 调用该方法的代理实例,
method:执行的方法(需要增强功能的方法)
orgs: 为这个方法传递的参数。
通过
method.invoke(obj, args)
方法执行原来 UserDao 中的方法,method
就是传递过来的方法。可以在其之前或者之后对功能进行扩展,第二步:使用
Proxy
类创建接口代理对象(获取代理实例执行方法)public class JDKProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); //创建接口实现类的接口对象 UserDao proxyDao = (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); //使用代理对象执行方法 int addResult = proxyDao.add(1, 2); System.out.println("add method result:"+addResult); } }
newProxyInstance()
方法的三个参数:loader:类加载器定义代理类
interfaces:被代理类实现的接口列表
h:调度方法调用的调用处理函数(代理对象)
原理:创建子类的代理对象,增强类的方法。
3.3 术语
连接点:类中可以增强功能的方法。
切入点:最终实际进行了功能增强的方法。
通知(增强):增加的那部分功能。含有多种类型:
前置通知
后置通知
环绕通知
异常通知
最终通知
切面:是一个动作,把一个通知应用到切入点的过程。
3.4 切入点表达式
作用:知道对哪个类里面的哪个方法进行增强
语法结构:
execution([权限修饰符] [返回类型] [类全路径].[方法名称]([参数列表]))
权限修饰符可以用*代表全部权限,返回类型一般省略。
举例:
对 com.atguigu.dao.BookDao 类里面的 add 方法进行增强 。
execution(* com.atguigu.dao.BookDao.add(..))
对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
execution(* com.atguigu.dao.BookDao.* (..))
对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
execution(* com.atguigu.dao.*.* (..))
3.5 AOP 操作
创建类(被增强功能的类),在类里面定义方法。
public class User { public void add(){ System.out.println("add method..."); } }
创建增强类(编写增强逻辑)。
public class UserProxy { public void before(){ System.out.println("before..."); } }
进行通知的配置。
在 spring 配置文件中,开启注解扫描。
<!--开启注解扫描--> <context:component-scan base-package="com.th" />
使用注解创建 User 和 UserProxy 对象。
@Component
@Component public class User { } @Component public class UserProxy { }
在增强类上面添加注解
@Aspect
。 @Aspect 用于生成代理对象@Component @Aspect public class UserProxy { }
在 spring 配置文件中开启生成代理对象。
proxy-target-class
默认状态为false
,proxy-target-class="false"
表示使用 JDK 动态代理,proxy-target-class="true"
表示使用 cglib 动态代理,<!--- 开启 Aspect 生成代理对象--> <aop:aspectj-autoproxy/>
配置不同类型的通知。
在增强类里的通知方法上面添加通知类型注解,使用切入点表达式配置。
@Component @Aspect public class UserProxy { /**前置通知 @Before 注解表示作为前置通知*/ @Before("execution(* com.th.atguigu.aopanno.User.add(..))") public void before(){ System.out.println("before..."); } /**后置通知(返回通知)*/ @AfterReturning(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void afterReturning() { System.out.println("afterReturning........."); } /**最终通知*/ @After(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void after() { System.out.println("after........."); } /**异常通知*/ @AfterThrowing(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void afterThrowing() { System.out.println("afterThrowing........."); } /**环绕通知*/ @Around(value = "execution(* com.th.atguigu.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { System.out.println("环绕之前........."); //被增强的方法执行 proceedingJoinPoint.proceed(); System.out.println("环绕之后........."); } }
结果:
环绕之前......... before... add method... afterReturning......... after......... 环绕之后......... //如果发生异常: 环绕之前......... before... afterThrowing......... after.........
异常通知:只有在被增强方法内部发生异常时调用。
最终通知:无论是否发生异常都会被执行。而后置通知如果发生异常就不会被执行。
相同的切入点抽取
如上所示,有多种不同类型的通知,在每个通知上都需要相同的注解表达式会比较麻烦,那么可以抽取公共部分简化操作。在通知的注解上使用配置了的方法名即可。
@Pointcut("execution(* com.th.atguigu.aopanno.User.add(..))") public void pointDemo(){ } /**前置通知 @Before 注解表示作为前置通知*/ @Before("pointDemo()") public void before(){ System.out.println("before..."); }
设置增强类优先级
当有多个增强类对同一个方法进行增强时,可以设置增强类的优先级。
在增强类上面添加注解
@Order(数字类型值)
,数字类型值越小优先级越高。@Component @Aspect @Order(0) public class PersonProxy { @Before("execution(* com.th.atguigu.aopanno.User.add(..))") public void beforePerson(){ System.out.println("before of PersonProxy"); } }before of PersonProxy 环绕之前......... before... add method... afterReturning......... after......... 环绕之后.........
完全使用注解开发 (创建配置类,不需要创建 xml 配置文件)
@Configuration @ComponentScan(basePackages = {"com.atguigu"}) @EnableAspectJAutoProxy(proxyTargetClass = true) public class ConfigAop { }
@EnableAspectJAutoProxy(proxyTargetClass = true)
相当于<aop:aspectj-autoproxy/>
。
创建两个类,增强类和被增强类,创建方法 。
在 spring 配置文件中(使用
bean
)创建两个类对象。<!--创建增强类和被增强类对象--> <bean id="book" class="com.th.atguigu.aopxml.Book"/> <bean id="bookProxy" class="com.th.atguigu.aopxml.BookProxy"/>
在 spring 配置文件中配置切入点。
<!--配置 aop 增强--> <aop:config> <!--切入点--> <aop:pointcut id="p" expression="execution(* com.th.atguigu.aopxml.Book.buy(..))"/> <!--配置切面--> <aop:aspect ref="bookProxy"> <!--增强作用在具体的方法上--> <aop:before method="before" pointcut-ref="p"/> </aop:aspect> </aop:config>
测试
public void testTwo(){ ApplicationContext context = new ClassPathXmlApplicationContext("beanXml.xml"); Book book = context.getBean("book", Book.class); book.buy(); }
4、整合数据库
4.1 整合 mybatis
Mybatis 使用步骤:
导入依赖(
mybatis
、mysql-connector-java
)配置 mybatis(
mybatis-config.xml
)构建工具类,获取 SqlSessionFactory 和 SqlSession
编写实体类(可使用
lombok
)Dao 接口
接口实现类 Impl【XXXMapper.xml】
在 Mybatis 核心配置文件中注册每一个 Mapper.xml
测试
导包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.3</version> </dependency>
mybatis-spring
:将 MyBatis 代码无缝地整合到 Spring 中,允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和SqlSession
并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的DataAccessException
。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。http://mybatis.org/spring/zh/index.html
配置 mybatis 数据源
数据源:用 Spring 的数据源代替 Mybatis 的配置 ——————
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="mysqlpw"/> </bean>
数据源的形式是多样的,也可以使用其他形式对数据源进行配置。
获取
SqlSessionFactory
<!--sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="DataSource" /> <!--绑定 mybatis 的配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/th/mapper/*.xml"/> </bean>
可以在这个
SqlSessionFactoryBean
配置 Mybatis 的一些其他配置(别名等)。最主要的是可以直接导入 Mybatis 的原有配置文件mybatis-config.xml
,两种配置可以配合使用。mapperLocations
获取 Mapper 的路径,相当于在 Mybatis 对 Mappper 的注册。获取和
SqlSession
,SqlSessionTemplate
:就是 mybatis 中的 sqlSession。<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能通过构造器注入 sqlSessionFactory,因为它没有 set 方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
编写实体类
Dao
接口接口实现类
XXXMapper.xml
和Impl
实现类,Impl实现类
是必须的,而在 Mybatis 中实现类不需要,在 Spring 中Impl实现类
是必须的,用于获取sqlSessionTemplate
。public class UserMapperImpl implements UserMapper{ //我们的所有操作,都用 sqlSession 来执行,在原来,现在使用 SqlSessionTemplate; private SqlSessionTemplate sqlSessionTemplate; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSessionTemplate = sqlSession; } @Override public List<User> selectUserList() { UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class); return mapper.selectUserList(); } }
当然
Impl实现类
也可以通过继承SqlSessionDaoSupport
类,通过调用getSqlSession()
方法得到一个SqlSessionTemplate
来执行 SQL 方法。public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> selectUserList() { return getSqlSession().getMapper(UserMapper.class).selectUserList(); } }
Spring 配置文件中创建实现类对象。
<bean id="userMapperImpl" class="com.th.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSessionTemplate"/> </bean>
通过继承
SqlSessionDaoSupport
类的Impl实现类
那么在创建其对象时需要传递sqlSessionFactory
。<bean id="userMapperImpl2" class="com.th.mapper.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
通过实现类对象进行业务操作
public void testSelectUserList() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml"); UserMapper userMapper = context.getBean("userMapperImpl2", UserMapper.class); for (User user : userMapper.selectUserList()) { System.out.println(user); } }
5、声明式事务管理
5.1 什么事务
事务是数据库操作最基本单元,逻辑上一组操作,要么都成功,如果有一个失败所有操作都失败。
事务分类:
声明式事物:通过 AOP 实现。
编程式事物:在代码中进行事务管理。
Spring 均支持这两种事物,但是推荐使用声明式事物。
5.2、事务四个特性(ACID)
原子性
一致性
隔离性
持久性
5.3 Spring 中的事务管理
Spring 提供一个接口PlatformTransactionManager
,代表事务管理器,这个接口针对不同的框架提供不同的实现类,DataSourceTransactionManager
为mybatis
和JdbcTemplate
提供使用。
在 spring 配置文件配置事务管理器
要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个
DataSourceTransactionManager
对象:<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--注入数据源--> <property name="dataSource" ref="dataSource"/> </bean>
在 spring 配置文件,开启事务注解
在 spring 配置文件引入名称空间 tx
开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
在 service 类上面(或者 service 类里面方法上面)添加事务注解
@Transactional
@Transactional
:这个注解作用在类上,也可以作用在方法上。@Service @Transactional() public class AccountService { }
声明式事务管理参数配置,
@Transactional()
属性参数的设置。propagation
:事务传播行为多事务方法(对数据库表数据进行变换的操作)直接进行调用,这个过程中事务是如何进行管理的。如一个事物方法调用另一个事务方法,这个被调用的事务方法是否进行事务管理。Spring 有 7 种事务传播行为。
@Transactional(propagation = Propagation.REQUIRED)
ioslation
:事务隔离级别事务具有隔离性,不考虑隔离性可能会产生脏读、不可重复读、虚(幻)读问题。
脏读
:一个未提交事务读取到另一个未提交事务的数据。不可重复读
:一个未提交事务读取到另一提交事务修改数据。虚读
:一个未提交事务读取到另一提交事务添加数据。Spring 通过设置事务隔离级别,解决读问题:
@Transactional(isolation = Isolation.SERIALIZABLE)
timeout
:超时时间事务需要在一定时间内进行提交,如果不提交则进行回滚 。
默认值是 -1(不超时) ,设置时间以秒单位进行计算。
@Transactional(timeout = -1)
readOnly
:是否只读读:查询操作,写:添加修改删除操作
readOnly
默认值false
,表示可以查询,可以添加修改删除操作设置
readOnly
值是true
,设置成 true 之后,只能查询.@Transactional(readOnly = true)
rollbackFor
:回滚设置出现哪些异常进行事务回滚
noRollbackFor
:不回滚设置出现哪些异常不进行事务回滚
在 spring 配置文件配置事务管理器
<!--开启 Spring 的事务处理功能--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean>
配置事物通知,结合 AOP 实现事物的织入
<tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--给哪些方法配置事物--> <!--new 配置事物的传播特性:propagation--> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="query" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
配置事物切入
<aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.th.kuang.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" /> </aop:config>
关于No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available: expected single matching bean but found 2: transactionManager,getDataSourceTransactionManager
报错问题。
由于在原来注解方式进行事务管理时存在一个数据源和事务管理器,@Transactional()
注解时不直接指定,Spring 就不知道具体使用哪一个事务管理器来进行事务管理了。使用只要@Transactional(transactionManager = “aaaTransactionManager”)
来进行指定具体的事务管理即可,当然最直接的方法是直接删除原来的事务管理。
6、Spring5 框架新功能
6.1 基础
整个 Spring5 框架的代码基于 Java8,运行时兼容 JDK9,许多不建议使用的类和方 法在代码库中删除
Spring 5.0 框架自带了通用的日志封装。
Spring5 已经移除
Log4jConfigListener
,官方建议使用Log4j2
。Spring5 框架核心容器支持
@Nullable
注解@Nullable
注解可以使用在方法、属性,参数上,分别表示方法返回可以为空,属性值可以为空,参数值可以为空 。Spring5 核心容器支持函数式风格
GenericApplicationContext
//函数式风格创建对象,交给 spring 进行管理 @Test public void testGenericApplicationContext() { //1 创建 GenericApplicationContext 对象 GenericApplicationContext context = new GenericApplicationContext(); //2 调用 context 的方法对象注册 context.refresh(); context.registerBean("user1",User.class,() -> new User()); //3 获取在 spring 注册的对象 // User user = (User)context.getBean("com.atguigu.spring5.test.User"); User user = (User)context.getBean("user1"); System.out.println(user); }
Spring5 支持整合 JUnit4 和 JUnit5。
@ExtendWith(SpringExtension.class) @ContextConfiguration("classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } } //使用一个复合注解替代上面两个注解完成整合 @SpringJUnitConfig(locations = "classpath:bean1.xml") public class JTest5 { @Autowired private UserService userService; @Test public void test1() { userService.accountMoney(); } }
6.1 Webflux
是 Spring5 添加新的模块,用于 web 开发的,功能和
SpringMVC
类似的,Webflux
使用 当前一种比较流行的响应式编程出现的框架。使用传统 web 框架,比如 SpringMVC 这些基于
Servlet
容器,Webflux 是一种异步非阻塞的框架,异步非阻塞的框架在Servlet3.1
以后才支持,核心是基于Reactor
的相关 API 实现 的。异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步。
阻塞和非阻塞针对被调用者,被调用者受到请求之后,做完请求任务之后才给出反馈就是阻塞,受到请求之后马上给出反馈然后再去做事情就是非阻塞。
Webflux 特点:
非阻塞式:在有限资源下,提高系统吞吐量和伸缩性,以
Reactor
为基础实现响应式编程。函数式编程:Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求
比较 SpringMVC
两个框架都可以使用注解方式,都运行在
Tomet
等容器中。SpringMVC
采用命令式编程(代码一行一行执行),Webflux
采用异步响应式编程。
响应式编程编程 RP,即Reactive Programming
,响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。
电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公 式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。
响应式编程的思想是观察者模式。Java8 及其之前版本提供的观察者模式两个类Observer
和Observable
。
评论