1 Introduce
-
基于 B站狂神说Java,非常非常感谢秦老师! 内容\排版经过完全理解后重新组织输出, 原创度较高.
1.1 Spring
给 Java 软件开发行业带来了"春天"
作者 Rod Johnson, 是悉尼大学音乐博士…
理念
使现有技术更加容易使用, Servlet 更简单好用. Spring 本身是个大杂烩, 可以整合现有的其他技术框架
导入依赖
直接导入MVC的, 互相依赖比较’完整’, 不需要手动导入多种互相依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
1.2 优点
- 是开源免费的容器 (框架)
- 是轻量级、非侵入式的
- 拥有 IoC, AOP 的功能
- 支持事务处理
- 支持整合第三方框架
Spring 就是一个轻量级的控制反转和面向切面编程的 IoC 和 AOP 框架
1.3 组成
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sB7jPWyQ-1587282086571)(D:\微云同步助手\364291826\同步文件夹\知识库\10 - md文库 秦疆老师\框架\Spring.assets\image-20200414101635262.png)]
1.4 拓展
现代化的Java开发, 就是Spring开发
-
Spring Boot
- 一个快速开发的脚手架
- “约定大于配置”
- 可以快速开发单个微服务
-
Spring Cloud
- 基于Spring Boot实现
大多数公司都在使用Spring Boot进行快速开发, 学习的前提是完全掌握 Spring 及 SpringMVC
Spring 发展时间久出现弊端, (秦老师认为唯一弊端) 违背原来的理念, 配置十分繁琐, 人称 “配置地狱”
2 IoC
2.0 Hello Spring
-
Spring 的核心配置文件: beans.xml, 名字可以随便取. 官网叫ApplicationContext.xml
并配入一个 bean 元素
<?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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <!--使用Spring 创建对象, 在Spring中这些都称之为 bean bean 可以理解为 对象 --> <bean id="hello" class="com.zhangcl.pojo.Hello"> <property name="str" value="Hello, Spring!"></property> </bean> </beans>
-
测试执行
import com.zhangcl.pojo.Hello; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTests { public static void main(String[] args) { /*获取 SpringContext 对象 * 这之后,我们的对象都在Spring中管理了, 如果要使用, 直接取就行了 * */ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); /*取对象*/ Hello hello = (Hello) context.getBean("hello"); System.out.println(hello); } }
2.1 本质
控制反转是一种通过描述 (XML或注解) + 通过第三方去生产或获取特定兑现的方式.
在Spring中实现控制反转的是IoC容器, 实现方法是DI
采用XML方式配置Bean
2.2 理解 ★
要怎样真正体会\理解 IoC 思想的带来的革命性变化?
-
用户直接调用的是业务层, 而DAO是由业务来调用的, 所以如果要改动具体调哪个DAO的实现则需要去修改业务层的代码
-
以前写代码实现类对接口的注入需要把实现在业务层手动赋值给接口
- 控制权在程序员手里
-
后来, 第一次革命性的变化是可以由调用方通过一个setter把自己需要的实现类set给接口, 不再需要改动业务层
-
控制的主动权在调用方手里
-
程序不再具有主动性, 而是被动接受注入对象
-
系统耦合性大大降低
-
2.3 创建对象
IoC创建对象的方式
-
借助对象的无参构造创建 (默认)
-
借助对象的有参构造创建
报错
假如有参构造"被不存在" (故意手写个有参构造, 编译器就不自动提供无参构造了), 但又强行在beans中配置, 则Idea会报错, 并且运行后会报init错
-
编译器报错
No matching constructor found in class 'User'
-
运行报错
Caused by: java.lang.NoSuchMethodException: com.zhangcl.pojo.User.<init>()
2.4 Bean scopes
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEjvDm0y-1587282086574)(D:\微云同步助手\364291826\同步文件夹\知识库\10 - md文库 秦疆老师\框架\Spring.assets\image-20200414192140329.png)]
- singleton - 每个Spring IoC container有一个, 相当于全局单例
- prototype - 每次get就产生一个新的对象
- request | session | application - 在web场景下才用的
2.5 @Autowired
2.5.1 简介
Spring 中自动装配Bean的三种方式:
- 在xml文件中显式地配置:
<Bean id... class... autowire="具体方式">
, 具体方式又分为:byName
- 会在容器中寻找与Java中
setXX()
的XX名字相匹配的Bean - 如果匹配不到, 执行会报异常:
NullPointer
- 会在容器中寻找与Java中
byType
- 会在容器中寻找与Java中属性类型相匹配的Bean的class
- [前提]: 需要保证Bean之中这个类型唯一
- 即使被装配的Bean不存在 ‘id属性’ 也可以匹配到
- 在Java代码中显式地配置 (此版本官方Document已经跳过了此部分)
- 隐式地自动装配 (using
@Autowired
is ‘better’ than XML)- since: JDK 1.5, Spring 2.5
- 默认匹配方式:
byType
- 如果都不能匹配, 可以通过增加
@Qualifier(value=BEANID)
配合, 以通过byName
匹配
- 如果都不能匹配, 可以通过增加
- POJO甚至可以没有setter (需要在容器中存在且可以通过byName找到)
- Java提供
@Resource
功能与之非常接近!
2.5.2 使用
需要先配置对annotation的支持
- 引入约束: line 4 7 8
- 配置支持: line 10
- 使用: Bean配置时就不需要写property之类的了, 而是直接在POJO的属性头或set方法上加
@Autowired
<?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>
2.5.3 @Qualifier
如果@Autowired
装配的对象name和class都匹配不到或者同类多个, 则可以通过增加此注解显式地指定一个bean, 以配合@Autowired
// <bean id=dog222 ...>
@Autowired
@Qualifier(value=dog222)
private Dog dog;
2.5.4 @Resource
与@Autowired
的很像:
-
from
javax.annotation.Resource
-
可能是Java提供的, 想要代替
@Autowired
的. -
匹配方式:
byName
或者byType
// 一般情况
@Resource
private Dog dog;
// 如果匹配不到
@Resource(name="dog222")
2.6 使用注解开发
前提:
since Spring 4, 必须导入aop
依赖才可以使用注解开发
步骤:
- 确保
aop
依赖存在 - 引入context约束
- 配置注解驱动
<context:annotation-config/>
2.6.1 注入: 类和属性
-
配置 - 指定扫描包, 这样此次包下的注解才会被识别. 在applicationContext.xml中:
<context:component-scan base-package="com.zhangcl.pojo"/>
-
标识 - 类, 在需要被Spring管理的类上增加注解
@Component
-
标识 - 属性, 在属性上增加
@Value
package com.zhangcl.pojo; import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component; @Component public class User{ // <bean id="name" value="张三"> @Value("张三") public String name; }
2.6.2 @Component 衍生
@Component
有一些衍生注解: 例如在web开发中, 按照MVC三层架构, 他们本质功能相同
- DAO - @Repository
- service - @Service
- controller - @Controller
2.6.3 Bean作用域
@Component
@Scope("singleton")
public class User{
// ...
}
2.6.4 XML 和 注解
对比
- XML
- 功能完整全面, 适用性强.
- 维护方便 (集中在一起)
- 注解
- 方便快捷
- bean之间不能太方便互相引用
ref=otherBean
- 维护不太方便, 如果类太多
最佳实践
-
XML - 负责管理Bean
-
注解 - 负责属性注入
只需要注意: 要确保注解生效 (别忘了扫描包)
2.6.5 其它
@Nullable
- 修饰方法形参, 则可以传入null, 而不追究
NullPointer
- 修饰方法形参, 则可以传入null, 而不追究
2.6.6 纯Java注解配置Bean
JavaConfig, 原是Spring的一个子项目, 在 Spring 4 之后成为核心功能
在SpringBoot中, 这种纯Java配置随处可见!
package com.zhangcl.config;
import com.zhangcl.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration // 标识
@ComponentScan("com.zhangcl.pojo") // 扫描
@Import(ConfigurationBean2.class) // 相当于import
public class ConfigurationBean { // 相当于beans
@Bean // 相当于bean
public User getUser(){ // 方法名相当于bean的id, 返回值相当于class
return new User();
}
}
Context实现类
调用时获取bean的Context实现类需要换成AnnotationConfigApplicationContext
2.7 Configuration XML
XML文件配置 - 整理
2.7.1 alias
<alias name="user2" alias="user2Alias"></alias>
注意: 别名和原名共存
其实还有另一种取别名方式
2.7.2 bean
<bean id="bean的唯一标识" class="对象的全限定名" name="别名1 别名2 别名3 ..."></bean>
<bean .... .... name="别名1,别名2,别名3,..."></bean>
2.7.3 import
团队开发用, 将其他配置文件的信息import进来
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tL4Jhrnf-1587282086576)(D:\微云同步助手\364291826\同步文件夹\知识库\10 - md文库 秦疆老师\框架\Spring.assets\image-20200414145916208.png)]
团队成员开发各个beans.xml, 再在applicationContext.xml 中import合并为一个, 就不需要读取时候读太多xml文件了
<import resource="beans.xml"></import>
<import resource="beans2.xml"></import>
<import resource="beans3.xml"></import>
注意: 可能有重名, 可以通过别名规避问题
3 Dependency Injection (DI)
本质是Set注入, 有三种方式
-
Setter
-
构造器
-
拓展方式
3.1 Setter注入 ★
-
pojo
- 复杂类型对象
package com.zhangcl.pojo; public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } }
- 真实测试对象
package com.zhangcl.pojo; import java.util.*; public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String,String> cards; private Set<String> games; private Properties info; private String girlFriend; public String getName() { return name; } public void setName(String name) { this.name = name; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public String[] getBooks() { return books; } public void setBooks(String[] books) { this.books = books; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } public Map<String, String> getCards() { return cards; } public void setCards(Map<String, String> cards) { this.cards = cards; } public Set<String> getGames() { return games; } public void setGames(Set<String> games) { this.games = games; } public Properties getInfo() { return info; } public void setInfo(Properties info) { this.info = info; } public String getGirlFriend() { return girlFriend; } public void setGirlFriend(String girlFriend) { this.girlFriend = girlFriend; } @Override public String toString() { return "Address{" + "name='" + name + '\'' + ", address=" + address + ", books=" + Arrays.toString(books) + ", hobbies=" + hobbies + ", cards=" + cards + ", games=" + games + ", info=" + info + ", girlFriend='" + girlFriend + '\'' + '}'; } }
-
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 https://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="address" class="com.zhangcl.pojo.Address"> <property name="address" value="北京 互联网"></property> </bean> <bean id="student" class="com.zhangcl.pojo.Student"> <!--1. 普通值--> <property name="name" value="秦疆"></property> <!--2. bean引用--> <property name="address" ref="address"></property> <!--3. 数组--> <property name="books"> <array> <value>红楼梦</value> <value>水浒传</value> <value>西游记</value> <value>三国演义</value> </array> </property> <!--4. List--> <property name="hobbies"> <list> <value>爱好足球</value> <value>爱好篮球</value> <value>爱好代码</value> </list> </property> <!--5. Map--> <property name="cards"> <map> <entry key="身份证" value="111111222222223333"></entry> <entry key="银行卡" value="1234545345234234234"></entry> </map> </property> <!--6. Set--> <property name="games"> <set> <value>LOL</value> <value>COC</value> <value>BOB</value> </set> </property> <!--7. null--> <property name="girlFriend"> <null></null> </property> <!--8. Properties--> <property name="info"> <props> <prop key="url">12.44.222.1</prop> <prop key="username">root</prop> <prop key="password">*****</prop> </props> </property> </bean> </beans>
-
Tests.java
import com.zhangcl.pojo.Student; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Tests { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) context.getBean("student"); System.out.println(student); } }
3.2 构造器注入
- constructor-arg
- type - 通过属性的类型锁定属性并注入
- index - 属性的序号
- name - 属性的属性名
<bean id="user" class="com.zhangcl.pojo.User">
<!--它们同时出现不合理, 这里只是为了演示-->
<constructor-arg type="java.lang.String" value="创建: 有参 type"></constructor-arg>
<constructor-arg index="0" value="创建: 有参 index"></constructor-arg>
<constructor-arg name="name" value="创建: 有参 name"></constructor-arg>
</bean>
注意:
- type的如果多个属性同type, 则需要谨慎使用
- 更推荐使用name方式
3.3 拓展方式注入: p-namespace and c-namespace
个人认为有两种 :
- p-namespace and c-namespace
- 注解注入, 本质还是setter
其中我们侧重整理一下 p-namespace and c-namespace:
是一种注入值的 Shortcut (更快捷的方式)
-
p-namespace
- 通过property直接注入, 相当于set注入 -
c-namespace
- 通过constructor-arg注入
基本使用:
- 导入约束: 第 3 4 行
- 使用: 第 8 9 行
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user-p" class="com.zhangcl.pojo.User" p:name="pname" p:age="18"></bean>
<bean id="user-c" class="com.zhangcl.pojo.User" c:name="cname" c:age="19"></bean>
</beans>
4 代理模式
4.1 静态代理
理解 - 角色分析:
-
抽象角色: 一般是使用接口和抽象类
-
真实角色: 被代理的
-
代理角色: 去代理真实角色的, 代理后还可以增加一些附属操作
-
客户: 访问真实角色的人
优势 - 静态代理:
- 真实角色的功能更加纯粹, 公共功能可以交给代理去做
- 公共业务发生扩展时, 方便集中管理
**缺点: **
- 每个真实角色会追加代理角色, 开发量和难度增大 (动态代理可以弥补)
基本步骤:
- 接口
- 真实角色实现接口
- 代理角色也实现接口, 调用真实角色, 同时加入公共功能
- 访问代理角色
4.2 动态代理
和静态代理比较
- 拥有静态代理的全部优点
- 动态生成代理类或者别的代码, 不需要大量手写重复类型的代码!
实现方式, 分为两大类
-
基于接口
- 如, JDK动态代理
-
基于类的动态代理
- 如, cglib
还有一个方式是 Java字节码
- 如,
JAVAssist
, jBoos服务器, 简单\快速\直接使用Java编码的格式
会用到两个重要的类:
Proxy
- 提供创建动态代理类和实例的静态方法的类
- 也是这些方法创建的所有动态代理类的父类
Foo f = Proxy.newProxyInstance(Foo.class.getClassLoader(), new Class<?>[]{Foo.class}, handler);
InvocationHandler
- 调用
基本实现流程
-
准备一些环境
package com.zhangcl.demo02; public interface UserService { void add(); void update(); void delete(); void query(); } package com.zhangcl.demo02; public class UserServiceImpl implements UserService { public void add() { System.out.println("add了数据"); } public void update() { System.out.println("update了数据"); } public void delete() { System.out.println("delete了数据"); } public void query() { System.out.println("query了数据"); } }
-
创建MyInvocationHandler.java, 实现InvocationHandler接口, 用于生成动态代理类实例
package com.zhangcl.demo02; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Date; public class MyInvocationHandler implements InvocationHandler { private Object target; public void setTarget(Object target){ this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = method.invoke(target, args); // 增加个日志功能 System.out.println("[日志]" + proxy.getClass().getSimpleName() + "在" + new Date() + "时间执行了" + method.getName() + "方法"); System.out.println("结果" + result); return result; } /** * 其实也可以把获取proxy instance的方法也给封装进来, 就不需要再在调用处写太复杂了 * 这个封装只是为了调用得更加简洁 */ public Object getProxy(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
-
测试 MyTests.java
package com.zhangcl.demo02; public class MyTest { public static void main(String[] args) { // 目标角色 真实角色 UserService userService = new UserServiceImpl(); // 代理角色 暂时还没有xx MyInvocationHandler handler = new MyInvocationHandler(); // 传入要代理的目标角色 handler.setTarget(userService); // 动态生成代理橘色 有了xx UserService proxy = (UserService) handler.getProxy(); proxy.update(); } } /* update了数据 [日志]$Proxy0在Fri Apr 17 16:57:20 CST 2020时间执行了update方法 结果null */
5 AOP
- 面向切面编程
- 通过预编译方式和运行期动态代理, 实现程序功能统一维护
- 好处: 提高代码可重用性 | 提高效率 | 降低耦合度
- 能实现: 验证参数 | 前置日志 | 后置日志
5.1 简介
AOP在Spring中的作用:
提供声明式事务 | 允许用户自定义切面
- “横切关注点” - 跨越多个模块的方法或功能, 又与主业务逻辑无关, 但却是开发人员需要关注的功能. 如: 日志\安全验证\缓存\事务etc
- Aspect - 切面, 横切关注点被模块化的特殊对象, 本质是一个类
- Advice - 切面要增加的具体功能, 本质是一个方法
- Before | After | Around | AfterReturning | AfterThrowing
- 其实就是方法执行的条件, 比如if(), 只不过Spring帮我们封装了
- Target - 目标, 被通知的目标对象
- PointCut - 切入点, 通知执行的"位置"
- JointPoint - 连接点, 与PointCut匹配的执行点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-expX6QFC-1587282086578)(D:\微云同步助手\364291826\同步文件夹\知识库\10 - md文库 秦疆老师\框架\Spring.assets\image-20200417183154436.png)]
5.2 使用
前提: 需要导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
5.2.1 方式一: 使用Spring的API接口
-
Java接口和实现
package com.zhangcl.service; public interface UserService { int add(); void delete(); void update(); void select(); } / package com.zhangcl.service; public class UserServiceImpl implements UserService { public int add() { System.out.println("增加了一个User"); return 1; } public void delete() { System.out.println("删除了一个User"); } public void update() { System.out.println("修改了一个User"); } public void select() { System.out.println("查询了一个User"); } }
-
Log.java
package com.zhangcl.log; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class Log implements MethodBeforeAdvice { public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了"); } } class AfterLog implements AfterReturningAdvice{ public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName() + "执行了" + method.getName() + "方法, 返回结果: " + returnValue); } }
-
注册Bean
<?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"> <!--注册bean--> <bean id="userService" class="com.zhangcl.service.UserServiceImpl"></bean> <bean id="log" class="com.zhangcl.log.Log"></bean> <bean id="agterLog" class="com.zhangcl.log.AfterLog"></bean> <!--方式1: 使用原生的Spring 的 api接口--> <!--配置aop: 需要导入AOP的约束--> <aop:config> <!--切入点 表达式, execution 要执行的位置--> <aop:pointcut id="pointcut" expression="execution(* com.zhangcl.service.UserServiceImpl.*(..))"/> <!--执行环绕增强--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"></aop:advisor> <aop:advisor advice-ref="agterLog" pointcut-ref="pointcut"></aop:advisor> </aop:config> </beans>
-
测试
import com.zhangcl.service.UserService; import com.zhangcl.service.UserServiceImpl; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService", UserService.class); userService.add(); } }
5.2.2 方式二: 定义切面
自定义类, 被切面引用
-
Java的接口和类还用方法一中的, 也体现了AOP能"提高代码的复用"
-
自定义一个PointCut类: DiyPointCut.java
package com.zhangcl.diy; public class DiyPointCut { public void before(){ System.out.println("======方法执行之前======"); } public void after(){ System.out.println("======方法执行之后======"); } }
-
在Spring的核心配置文件中中配置AOP的切面: applicationContext.xml
<!--方式二: 自定义类 之前的UserService的bean还存在--> <bean id="diyPointCut" class="com.zhangcl.diy.DiyPointCut"></bean> <aop:config> <!--自定义切面, ref要引用的类--> <aop:aspect ref="diyPointCut"> <!--切入点--> <aop:pointcut id="pointcut" expression="execution(* com.zhangcl.service.UserServiceImpl.*(..))"/> <!--通知--> <aop:before method="before" pointcut-ref="pointcut"></aop:before> <aop:after method="after" pointcut-ref="pointcut"></aop:after> </aop:aspect> </aop:config>
-
测试: MyTest.java, 还是之前的
5.2.3 方式三: 注解
-
配置文件:
applicationContext.xml
<!--方式三: 注解--> <bean id="annotationPointCut" class="com.zhangcl.diy.AnnotationPointCut"></bean> <!--开启注解支持!--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
编写切面类
package com.zhangcl.diy; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * 使用注解方式使用AOP */ @Aspect // 标识此类为切面类 public class AnnotationPointCut { @Before("execution(* com.zhangcl.service.UserServiceImpl.*(..))") public void before(){ System.out.println("注解使用: 方法执行之前"); } /*这里我还测试了切入点定位到接口 结果一样*/ @After("execution(* com.zhangcl.service.UserService.*(..))") public void after(){ System.out.println("注解使用: 方式执行之后!"); } /*环绕增强中可以给定一个参数, 代表我们要获取处理切入的点 * 理解:*/ @Around("execution(* com.zhangcl.service.UserService.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("注解: 方法执行环绕前"); // 执行方法 Object proceed = jp.proceed(); System.out.println("注解: 方法执行环绕之后"); System.out.println("#####################################"); System.out.println("签名:" + jp.getSignature()); // 获得类的信息 System.out.println("proceed:" + proceed); // /* 打印结果 // 注解: 方法执行环绕前 // 注解使用: 方法执行之前 // 增加了一个User // 注解: 方法执行环绕之后 // 签名:int com.zhangcl.service.UserService.add() // proceed:1 // 注解使用: 方式执行之后! // 他怎么跑最后的最后了? 是不是说明around整个优先级太高了 // */ } }
-
测试, 还是之前的测试类
5.2.4 cglib开启
在applicationContext.xml
<!--开启注解支持!-->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
line 2: proxy-target-class="false"
, false或者不写, 则默认开启JDK代理, 如果true
, 开启cglib
6 Spring整合MyBatis
6.1 初步引入MyBatis
步骤:
-
导入jar包
- junit
- mybatis
- mysql
- spring相关
- aop织入
- mybatis-spring
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>spring-study</artifactId> <groupId>com.zhangcl</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>spring-09-mybatis</artifactId> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <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.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.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.8.13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency> </dependencies> </project>
-
编写配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.zhangcl.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.159.128:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="Ms_910613"/> </dataSource> </environment> </environments> <mappers> <mapper class="com.zhangcl.mapper.UserMapper"/> </mappers> </configuration>
-
接口
package com.zhangcl.mapper; import com.zhangcl.pojo.User; import java.util.List; public interface UserMapper { List<User> selectUser(); }
-
Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhangcl.mapper.UserMapper"> <select id="selectUser" resultType="User"> select * from user </select> </mapper>
-
先测试一下
import com.zhangcl.mapper.UserMapper; import com.zhangcl.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MyTest { @Test public void test() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sessionFactory.openSession(true); // 自动commit UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.selectUser(); for (User user : users) { System.out.println(user); } sqlSession.close(); } }
6.2 整合: 方式一
-
配置文件:
-
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.zhangcl.pojo"/> </typeAliases> <!-- <environments default="development">--> <!-- <environment id="development">--> <!-- <transactionManager type="JDBC"/>--> <!-- <dataSource type="POOLED">--> <!-- <property name="driver" value="com.mysql.jdbc.Driver"/>--> <!-- <property name="url" value="jdbc:mysql://192.168.159.128:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"/>--> <!-- <property name="username" value="root"/>--> <!-- <property name="password" value="Ms_910613"/>--> <!-- </dataSource>--> <!-- </environment>--> <!-- </environments>--> <!-- <mappers>--> <!-- <mapper resource="com/zhangcl/mapper/UserMapper.xml"/>--> <!-- </mappers>--> </configuration>
-
spring-dao.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 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"> <!--dataSource: 使用Spring的数据源替换Mybatis的配置, 使用Spring提供的jdbc --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://192.168.159.128:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"></property> <property name="username" value="root"></property> <property name="password" value="Ms_910613"></property> </bean> <!--sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--绑定MyBatis配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="mapperLocations" value="classpath:com/zhangcl/mapper/*.xml"></property> </bean> <!--sqlSessionTemplate 就是之前常用的sqlSession--> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能构造器注入, 因为没有setter--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> </beans>
-
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> <!--导入进来--> <import resource="classpath:spring-dao.xml"></import> <!--先实现类, 再进入此步骤注入--> <bean id="userMapper" class="com.zhangcl.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSessionTemplate"></property> </bean> </beans>
-
-
实现接口: UserMapperImpl.java, 获取并调用sqlSessionTemplate对象
package com.zhangcl.mapper; import com.zhangcl.pojo.User; import org.mybatis.spring.SqlSessionTemplate; import java.util.List; /** * 所有操作都使用sqlSession来执行, 也就是sqlSessionTemplate */ public class UserMapperImpl implements UserMapper { private SqlSessionTemplate sqlSession; // setter public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> getUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUser(); return users; } }
-
测试
import com.zhangcl.mapper.UserMapper; import com.zhangcl.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MyTest { @Test public void test() throws IOException { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); List<User> users = userMapper.getUser(); for (User user : users) { System.out.println(user); } } }
6.3 整合: 方式二
官网新版本,通过SqlSessionDaoSupport
获取sqlSession
-
通过继承这个类, 可获取sqlSession对象: UserMapperImpl.java
package com.zhangcl.mapper; import com.zhangcl.pojo.User; import org.apache.ibatis.session.SqlSession; import org.mybatis.spring.support.SqlSessionDaoSupport; import java.util.List; public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper { public List<User> getUser() { SqlSession sqlSession = getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUser(); return users; } }
-
注册给Spring. 需要注意注入的
SqlSessionFactory
是父类SqlSessionDaoSupport
所需要的, 官网的示例要求<bean id="userMapper2" class="com.zhangcl.mapper.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> </bean>
-
测试, 稍微改动原来的测试类即可
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
6.4 整合: 其他方式
还可以考虑MyBatisPlus或通用Mapper
7 声明式事务
事务: 都成功或都失败
"ACID原则": 原子性 | 一致性 | 隔离性 | 永久性
编程式事务对业务代码有侵犯, 轻易不考虑, 我们主要考虑声明式事务
为什么要配置事务?
- 如果不配置, 则可能存在数据提交不一致的情况
- 如果不在Spring中配置声明式事务, 则需要在代码中手动配置
- 事务在项目的开发中十分重要, 涉及到数据的一致性和完整性问题, 不容马虎
7.1 配置声明式事务
-
开启事务: spring-dao.xml, 按照官网文档, 但之后织入用的是秦老师自创的AOP织入.
文件头4 9 10引入约束, 35行配置事务
<?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"> <!--dataSource: 使用Spring的数据源替换Mybatis的配置, 使用Spring提供的jdbc --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://192.168.159.128:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=utf8"></property> <property name="username" value="root"></property> <property name="password" value="Ms_910613"></property> </bean> <!--sqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--绑定MyBatis配置文件--> <property name="configLocation" value="classpath:mybatis-config.xml"></property> <property name="mapperLocations" value="classpath:com/zhangcl/mapper/*.xml"></property> </bean> <!--sqlSessionTemplate 就是之前常用的sqlSession--> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能构造器注入, 因为没有setter--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <!--配置声明式事务--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
-
使用AOP织入: 同文件中
<!--结合AOP织入事务--> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--给哪些方法配置事务?--> <!--配置事务的传播特性--> <tx:attributes> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete"/> <tx:method name="update"/> <tx:method name="query"/> <tx:method name="*" propagation="REQUIRED"/><!--所有方法--> </tx:attributes> </tx:advice> <!--配置事务切入--> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.zhangcl.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor> </aop:config>
7.2 propagation (事务传播特性)
了解, 一般不需要改动.
声明式事务中, 配置切面时会用到propagation: 打算对这些方法怎样使用事务? 用还是不用
Spring 中 propagation 有7种配置:
将影响数据存储, 必须根据情况慎重选择
- REQUIRED - [default] 支持当前事务, 如果当前没有事务, 则新建一个事务. 最常见选择.
- SUPPORTS - 支持当前事务, 如果没有事务 就以非事务方式执行
- MANDATORY - 支持当前事务, 如果当前没有事务, 就抛出异常
- REQUIRES_NEW - 新建事务, 如果当前存在, 就把当前事务挂起
- NOT_SUPPORTED - 以非事务方式执行, 如果当前存在, 就要挂起
- NEVER - 以非事务方式执行, 如果当前存在, 则抛出异常
- NESTED - 支持当前事务, 如果当前存在事务, 则执行一个嵌套事务, 如果当前没有事务, 就新建一个事务
用REQUIRED和NESTED是确保事务正常存在的, 是比较谨慎的选择