回头来看 spring5
spring 是一个轻量级的非侵入式的开源框架
它的的两大特点,控制反转(IOC) 和 面向切面编程(AOP)
现在spring已经在 java后端这一块形成大一统了。
IOC理论推导
引入相关依赖,因为spring模块较多,我们引入一个较大的springWebMvc,这里maven会自动帮我们导入需要的jar 包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
首先我们创建一个 UserDao接口,这个接口很简单,他就只有一个getUser的方法
public interface UserDao {
void getUser();
}
然后呢?我们再去搞一个实现类,就叫UserDaoImpl这个吧
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("获取用户数据");
}
}
同样的 我们需要再写一个 UserService 和 UserServiceImpl
UserService:
public interface UserService {
void getUser();
}
UserServiceImpl:
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMysqlImpl();
public void getUser() {
userDao.getUser();
}
}
假如前端现在需要从 UserController 也就是我们的后台接口来获取一个 用户信息
那我们用main 方法来替代
public class MyTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
那这样我们是不是就能获取到用户信息了呢?
答案是当然的。但是,这种方法有什么问题呢?
我们是不是将UserDao 的代码写死了? 对的,如果这个时候我们需要扩展了,假如说,我们需要从mysql数据库来获取数据了
我们还通过这种方法来写,那当然没问题,但是如果再多一种 Oracle的获取方式呢,再多一种 sqlserver的获取方式呢?
我们总不能每次都将我们的代码 在 service 层修改吧?
那么现在我们 给 Service 层新增一个SetUserDao 方法,当然你也可以在构造方法中传入UserDao这都是没问题的
修改后的 UserService
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMysqlImpl();
//利用set 进行动态注入值
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
这个时候,其实我们如果需要对获取用户数据源的方法进行修改的话
我们是不是只需要调用在 main方法中 调用set方法就行了?
修改后:
public class MyTest {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
其实这样我们的程序已经发生了革命性的变化,那就是原来程序的控制权是在我们的service手上,也就是我们程序员手上。
代码修改后呢?
其实控制器已经从我们程序员手上,跑到了用户手上?为什么这么说呢?
因为main方法(现在充当我们的controller)来接受参数,我们在controller 里就可以修改 Dao 的实现类了,而我们的 service 的代码并没有发生改变
这种思想从本质上解决了问题,我们程序员不需要再去管创建什么对象了,系统的耦合性大大降低,我们可以更加专注于业务上,这就是控制反转ioc 的原型
spring hello (依赖注入)
Hello.java
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
application.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">
<!-- 使用 spring 来创建 对象 -->
<bean id="hello" class="cn.edu.zzuli.pojo.Hello">
<property name="str" value="hello spring"/>
</bean>
</beans>
main
public class MyTest {
public static void main(String[] args) {
//获取 spring 的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//那我们现在对象都被 spring 管理了,我们如果需要,直接从里边取出来就可以了
Object hello = context.getBean("hello");
System.out.println(hello);
}
}
那么思考一下,Hello 这个对象是谁创建的?
毫无疑问,spring帮我们创建的,这也是控制反转
控制: spring 来控制这些对象,帮我创建好
反转: 我们从主动创建对象,变成了被动的接受对象
依赖注入: 通过set方法 为对象赋值
关于 spring 的配置文件
常用标签
bean 标签的属性
- id: 唯一标识
- class: 全类名
- name: 别名 可以有多个,可以空格分隔,也可以,分割
我们可以通过 set方式为属性赋值
也可以通过构造器的方式为属性赋值:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- <bean id="user" class="cn.edu.zzuli.pojo.User">-->
<!-- <property name="name" value="halo"/>-->
<!-- </bean>-->
<bean id="user" class="cn.edu.zzuli.pojo.User" name="user2,user3 user4">
<constructor-arg name="name" value="hello"/>
</bean>
<alias name="user" alias="getUserByConstructor"/>
</beans>
alias 是设置别名,与bean标签的name属性相同(spring:你可以不用,但我不能没有)
import:
当我们项目较大的时候,多人编写多个配置文件,但是我们在创建ApplicationContext对象的时候,只获取一个xml配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
但是我们需要在这个配置文件里,引入其他配置文件,这个时候就可以使用到 import 标签了
<?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">
<!-- 引入其他 xml 文件 -->
<import resource="beans.xml"/>
</beans>
依赖注入(DI)的复杂类型
Student.java
@Data
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbies;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
Address.java
@Data
public class Address {
private String address;
}
appliaction.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="cn.edu.zzuli.pojo.Student">
<property name="name" value="hello"/>
<property name="address" ref="address"/>
<property name="books">
<array>
<value>java</value>
<value>spring</value>
<value>mybatis</value>
</array>
</property>
<property name="hobbies">
<list>
<value>读书</value>
<value>看报</value>
<value>睡觉</value>
</list>
</property>
<property name="card">
<map>
<entry key="stuId" value="541713140206"/>
<entry key="id" value="410XXXXXXXXXXXXXXXXX"/>
</map>
</property>
<property name="games">
<set>
<value>GTA</value>
<value>DNF</value>
<value>刺客信条</value>
</set>
</property>
<property name="wife">
<null/>
</property>
<property name="info">
<props>
<prop key="driver">com.jdbc.mysql.driver</prop>
</props>
</property>
</bean>
<bean id="address" class="cn.edu.zzuli.pojo.Address">
<property name="address" value="zzuli"/>
</bean>
</beans>
命名空间 c 和 p
c: constructor 构造器注入扩展
p: params 参数注入扩展
具体使用如下
记得引用 xml 的扩展
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="cn.edu.zzuli.pojo.User" p:age="18" p:name="hello"/>
<bean id="user2" class="cn.edu.zzuli.pojo.User" c:age="20" c:name="halo"/>
</beans>
bean 的作用域
- singleton : 单例,在xml中配置的 bean 全局只有共享一个(唯一)。spring 默认就是单例模式
- prototype :原型模式,每获取一次对象,就创建一个对象
- request,session,application,websocket 对应web的作用域
我们可以在 bean 的标签里通过 scope 属性修改作用域
<!-- 默认的单例模式 -->
<bean id="user" class="cn.edu.zzuli.pojo.User" p:age="18" p:name="hello"/>
<!-- 原型模式 -->
<bean id="user2" class="cn.edu.zzuli.pojo.User" c:age="20" c:name="halo" scope="prototype"/>
我们可以在代码中进行测试
@Test
public void testBeanScope() {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
User user = (User) context.getBean("user");
User user1 = (User) context.getBean("user");
System.out.println(user == user1);
User user2 = (User) context.getBean("user2");
User user3 = (User) context.getBean("user2");
System.out.println(user2 == user3);
}
可以看到输出结果
true
false
bean 的自动装配
之前,我们注册bean 都是在xml属性中,手动装配。
但是在 spring 中spring可以通过上下文寻找,自动装配
可以通过 @Autowired 来自动装配
也可在 xml 文件中的 bean 标签的属性 autowire 开启自动注入
- byName 自动在上下文寻找属性名对应的值,没有的话报空指针异常
- byType 自动在上下文寻找对象类型对应的值,但是在配置文件中,只能有一个本类型的对象
在配置文件中加入标签开启注解
<?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/>
<bean id="address" class="cn.edu.zzuli.pojo.Address" p:address="zzuli"/>
<bean id="user" class="cn.edu.zzuli.pojo.User" p:name="helllo"/>
</beans>
User.java
@Data
public class User {
private String name;
@Autowired
private Address address;
}
Address.java
@Data
public class Address {
private String address;
}
这样直接输出User 会发现,spring 已经帮我们自动注入了 Address 对象
@AutoWired 默认采用的是 byType方式,如果有多个同类型的对象,那么采取 byName的方式
可以测试一下。
<bean id="address" class="cn.edu.zzuli.pojo.Address" p:address="zzuli"/>
<bean id="address1" class="cn.edu.zzuli.pojo.Address" p:address="zzuli-1111"/>
<bean id="user" class="cn.edu.zzuli.pojo.User" p:name="helllo"/>
那么这个时候我们想获取第二个 address1对象该怎么办呢?
我们就要用到 @Qualifier 了,它的 value 值即我们要寻找bean的名字(id)
@Autowired
@Qualifier(value = "address1")
private Address address;
另外,如果想实现自动装配,其实java也为我们提供了一个注解
@Resource
默认情况下他去 byName ,如果没有对应的,他会去byType
spring 的注解开发
在 spring 4 之后,要使用注解,必须要导入 aop这个依赖。
而且,在java8出来后,注解开发,越来越流行了,spring也从配置文件的方式,渐渐转为注解的方式,在 springboot 中你可以明显的享受到注解的快乐
@Data
@Component
public class User {
@Value("helllo")
private String name ;
@Value("20")
private Integer age;
}
@Component 注解,意味着,这个类,你会被 IOC容器所管理
<context:component-scan base-package="cn.edu.zzuli.pojo"/>
我们可以通过配置文件来 对整个包进行扫描,扫描后,我们在对应的类上加上@Component 注解,这个类就会被spring的 IOC 容器所管理。
同样
@Scope(“singleton”) 可以来进行作用域的修改
@Data
@Component
@Scope("singleton")
public class User {
}
使用Java类的方式,配置spring
@Configuration标记的类叫做配置类,它相当于xml文件的beans标签,这个类里面需要注册bean,相当于在beans中注册bean。
@Configuration
public class AppConfig {
}
bean注册是有两种方式
第一种,
使用@Bean标记一个返回javabean的方法,返回的javabean作为bean注册到beans中。
@Configuration
public class AppConfig {
@Bean
public Person getPerson() {
return new Person();
}
}
但是要注意,这个时候,spring为我们注册的bean 的名字是方法名
如果你要让他的名字为 person 的话
你需要让注解的name 属性为 person
@Bean(name = "person")
第二种,使用@Component+@ComponentScan的方式:@Component标记在要注册bean的类上,@ComponentScan标记在配置类上用于扫描组件。
AppConfig.java :
@Configuration
@ComponentScan("cn.edu.zzuli.pojo")
public class AppConfig {
//采用第二种方式,就不需要自己再手动注册Bean了
// @Bean(name = "person")
// public Person getPerson() {
// return new Person();
// }
}
Person.java
@Data
@Component
public class Person {
@Value("helllo")
private String name ;
@Value("20")
private Integer age;
}
Ps:( 如果你两个方法一起使用,并且没有在@Bean注解里指定名字
那么 spring 会注册两个bean。一个叫 person 一个叫 getPerson )
如果这个时候我们还需要采用多个配置文件的话,那么我们可以使用@import注解在我们的配置类上引用 其他配置文件的.class 属性
@Configuration
@ComponentScan("cn.edu.zzuli.pojo")
@Import(App2Config.class)
public class AppConfig {
}
AOP
在 AOP 之前,我们可以先来看看静态代理和动态代理
静态代理
静态代理需要抽象出真实角色,代理角色,还要有访问代理角色
具体例子
- 房东:出租房 (真实角色(被代理角色))
- 中介:帮你租房,帮房东出租房 (代理角色)
- 你:租房 (访问代理角色)
具体代码:
租房接口:(抽象出的业务行为)
public interface Rent {
void Rent();
}
房东:
public class Host implements Rent {
public void Rent() {
System.out.println("房东出租房子");
}
}
中介:
public class Proxy implements Rent {
//被代理的房东
private Host host;
public Proxy() {}
public Proxy(Host host) {
this.host = host;
}
public void Rent() {
sign();
visitHost();
host.Rent();
}
//中介的增强功能
public void sign() {
System.out.println("签合同");
}
//中介的增强功能
public void visitHost() {
System.out.println("看房");
}
}
你:
public class Cilent {
public static void main(String[] args) {
Proxy proxy = new Proxy(new Host());
//你找中介租房
proxy.Rent();
}
}
静态代理的优点:
- 可以使真实角色的操作更存粹
- 通过代理角色,可以做一些公共业务基础上的功能增强,也实现了业务的分工。
- 公共业务发生扩展时方便集中管理。
🌰
- 假如有多个房东,我们需要添加房屋维修的方法,这个时候我们只需要在代理对象,也就是中介这个对象里添加维修房屋的方法就可以了,不需要再每一个房东里去加
- 假如我们需要在真实对象的每个方法中都加入日志功能,那么我们在代理类中实现就好,虽然代码增加了,但是解耦了,而且保证了原有业务代码不变。
记住最最最最重要的一点就是,我们尽量不改变原有的代码,而是再代理类里边坐增强。
但是它也有缺点
- 一个真实角色就会产生一个代理角色,代码里翻倍,开发效率变低
而解决这个缺点的就是动态代理
动态代理
动态代理和静态代理角色一样,但是它的代理类是动态生成的,不是我们一开始就写好的。
动态代理分为两大类,基于接口的动态代理,基于类的动态代理
- 基于接口的动态代理—jdk 的动态代理
- 基于类的动态代理: cglib (不用在意,我们一般都用jdk的)
需要了解两个类,
Proxy: 代理类
newProxyInstance proxy 的静态方法,用来获取一个Proxy(代理类),
参数分别为,(类加载器,实现的接口,InvocationHandler)
InvocationHandler:
调用处理程序,用来动态生成代理类
其中有一个invoke方法,当代理类调用方法时,处理程序类会帮我们委派到 invoke方法里
我们现在要为 用户业务逻辑接口 的每一个方法都添加日志功能
UserService
public interface UserService {
void add();
void delete();
void update();
void query();
}
UserServiceImpl
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("添加一个用户");
}
public void delete() {
System.out.println("删除一个用户");
}
public void update() {
System.out.println("修改一个用户");
}
public void query() {
System.out.println("查找一个用户");
}
}
InvocationHandler:程序处理类
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的类
//如果要具体的到某一个类的话,最好使用它实现的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//动态生成代理类
//newProxyInstance proxy 的静态方法,用来获取一个Proxy(代理类),
//参数分别为,(类加载器,实现的接口,InvocationHandler)
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//利用反射来获取方法名字
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
//添加日志
public void log(String methodName) {
System.out.println("使用了" + methodName + "方法");
}
}
main
public class Cilent {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
//创建程序处理类,用来动态生成代理类
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler();
//设置被代理对象
invocationHandler.setTarget(userService);
//获取代理类
UserService proxy = (UserService) invocationHandler.getProxy();
//调用方法时,程序处理类会帮我们委派到invoke方法里。
proxy.add();
}
}
动态代理的好处:
- 一个动态代理类,代理的是一个接口,改代码的成本很低,代码量也少
AOP xml 方式
什么是aop呢?它叫做面向切面编程,是面向对象的扩展,一般当我们在处理业务逻辑前后有需要执行的操作的时候,比如说日志或者权限验证
这时我们会选择横向的来扩展,在原业务代码外来处理新的逻辑,不改变原有代码,这种方式就是 aop 编程
而spring 的aop呢,它就是基于 动态代理的基础上进行实现的
一些重要的名词解释
- 目标 被通知的对象
- 代理 向目标对象通知之后创建的对象
- 连接点
- 基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点
- 切入点
- 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
- 通知
- 切面必须要实现的功能,也是类的方法,就比如你想在方法执行前这个连接点干点啥
- 切面
- 具体可以看成类,是通知和切入点的结合。,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义
- 织入
把切面应用到目标对象来创建新的代理对象的过程。spring采用的是运行时
关键就是:切点定义了哪些连接点会得到通知
在此感谢大佬的总结:aop名词解释
首先要使用aop,我们必须导入 aspectjweaver 依赖
方式一
<dependencies>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
其次,我们创建一个日志类,让他实现 MethodBeforeAdvice
MethodBeforeAdvice,在目标方法执行之前执行
- method 要执行的目标方法
- objects 方法参数
- target 目标对象
public class Log implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+ "的"+ method.getName() + "被执行了");
}
}
UserServiceImpl 和 UserSerive同上相同
public interface UserService {
void add();
void delete();
void update();
void query();
}
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("添加一个用户");
}
public void delete() {
System.out.println("删除一个用户");
}
public void update() {
System.out.println("修改一个用户");
}
public void query() {
System.out.println("查找一个用户");
}
}
然后再 application.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="log" class="cn.edu.zzuli.log.Log"/>
<bean id="userService" class="cn.edu.zzuli.service.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcut"
expression="execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
</aop:config>
</beans>
然后 main 方法中执行测试,发现结果却是如我们所料
cn.edu.zzuli.service.UserServiceImpl的add被执行了
添加一个用户
方式二
初次之外,我们还可以自定义切面来进行环绕通知
切面类
public class DiyLogPointCut {
public void before() {
System.out.println("方法执行前----->");
}
public void after() {
System.out.println("<-----方法执行后");
}
}
配置文件修改为:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="log" class="cn.edu.zzuli.log.Log"/>
<bean id="userService" class="cn.edu.zzuli.service.UserServiceImpl"/>
<bean id="diyLogPointCut" class="cn.edu.zzuli.diy.DiyLogPointCut"/>
<aop:config>
<aop:aspect id="aspect" ref="diyLogPointCut">
<aop:pointcut id="pointcut"
expression="execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
效果相同
AOP 注解方式
既然使用注解了,那当然要使用配置类了。
记得在配置类上开启 @EnableAspectJAutoProxy aspect支持啊
AppConfig.java
@Configuration
@ComponentScans({@ComponentScan("cn.edu.zzuli.service"),
@ComponentScan("cn.edu.zzuli.annotation")})
@EnableAspectJAutoProxy
public class AppConfig {
}
AnnoPointCut 切面:
//标注这个类是一个切面
@Aspect
@Component
public class AnnoPointCut {
private final String pointcut = "execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))";
//可以使用变量的方式
@Before(pointcut)
public void before() {
System.out.println("方法执行前----->");
}
//同样可以直接写切入点
@After("execution(* cn.edu.zzuli.service.UserServiceImpl.*(..))")
public void after() {
System.out.println("<-----方法执行后");
}
@AfterReturning(pointcut)
public void afterReturning() {
System.out.println("<-----返回参数后----->");
}
//在环绕时,我们可以给定一个参数
//ProceedingJoinPoint 代表我们要获取的切入点
@Around(pointcut)
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
//获取签名(哪个方法被执行了)
//void cn.edu.zzuli.service.UserService.add()
Signature signature = joinPoint.getSignature();
System.out.println(signature);
//执行方法,你可以将它想象成一个过滤器
Object proceed = joinPoint.proceed();
System.out.println(proceed);
System.out.println("环绕后");
}
}
service类同上
然后再main 方法执行结果也同上。
整合mybatis
这里只写最重要的一部分代码
配置文件的方式
根据官方的快速入门可以这样来做
public interface UserMapper {
@Select("select * from users")
List<User> getUsers();
}
xml 文件创建放在 resource/mapper 下可以了
而且这里要添加一个实现类,其实在我看来这里可以看成service,但这样看着就像多封装了一层一样,所以我还觉得 使用 java类的方式更好一点。
public class UserMapperImpl implements UserMapper {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> getUsers() {
return sqlSession.getMapper(UserMapper.class).getUsers();
}
}
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF8"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="userMapper" class="cn.edu.zzuli.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
javaConfig 方式
其实这种方式还是较为支持的。不复杂而且springboot应该也是采用了这种方式
@Configuration
@MapperScan("cn.edu.zzuli.mapper")
@ComponentScan("cn.edu.zzuli.bean")
public class AppConfig {
@Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF8");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate SqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
}
声明式事务
现在有spring了,我们再也不需要自己在写事务了,我们可以全权交给spring来管理。
这里还要说明一下声明式事务和编程式事务的区别
声明式事务:不侵入原有业务代码,在外部进行事务管理
编程式事务:在原有业务代码中进行事务处理
spring中,使用aop的方式来进行声明式事务
@EnableTransactionManagement 来开启注解式事务
@Configuration
@MapperScan("cn.edu.zzuli.mapper")
@ComponentScans({
@ComponentScan("cn.edu.zzuli.bean"),
@ComponentScan("cn.edu.zzuli.service")})
@EnableTransactionManagement
public class AppConfig {
@Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mybatis?serverTimezone=Asia/Shanghai&useUnicode=yes&characterEncoding=UTF8");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate SqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
@Bean(name="transactionManager")
public PlatformTransactionManager transactionManager(){
//这两个的区别是:DataSourceTransactionManager是配置本地单数据源的数据库事务管理,
//而JtaTransactionManager是为配置分布式数据库的
//return new JtaTransactionManagerFactoryBean().getObject();
return new DataSourceTransactionManager(dataSource());
}
}
在方法或类上来使用 @Transactional 来开启事务,它会通知spring的事务切面来织入事务
在方法抛出RuntimeException或者Error时会触发事务的回滚,在平常我们自己处理事务回滚的时候,我们可以手动抛异常来出发回滚。
同样该注解还可以指定异常类型 rollbackFor = Exception.class
Spring定义了7中传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
一般我们使用默认的 propagation_requierd,如果执行查询操作的时候,我们会加上 readOnly=true
@Transactional(readOnly=true)
public List<User> getUsers() {
return userMapper.getUsers();
}
@Transactional(rollbackFor = java.lang.ArithmeticException.class)
public void addUser(User user) {
userMapper.addUser(user);
//这里故意报错
int a = 0, b = 0;
System.out.println(a/b);
}
其次spring 还可以通过 isolation 属性来控制事务的隔离级别
spring 事务的传播属性和隔离级别
@Transactional 注解的生效和失效场景