1.Spring
1.1、简介
- 2002年首次推出Spring框架的雏形,Interface21框架
- 2004年3月24日,Spring框架在Interface21框架的基础上经过重新设计,终于发布了Spring1.0版本
- Spring Framework的创始人,他是悉尼大学的博士,他创造了Spring但是却不是计算机专业的,而是音乐学
- Spring:使现有的技术更加容易使用,本身是各种东西集合而成的,是一个大杂烩,整合了现有的技术框架
- 了解:
- SSH:Struct2+Spring+Hibernate!(之前)
- SSM:SpringMVC+Spring+Mybatis!(现在)
GitHub:https://github.com/spring-projects/spring-framework
相关依赖:由于maven强大的特性,会自动导入相关的依赖,故只需要导入以下依赖即可满足大部分使用
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.2</version>
</dependency>
1.2、优点
- Spring是一个免费的开源的框架!
- Spring是一个轻量级的,非入侵式的框架!
- 最大特点:控制反转(IOC),面向切面编程(AOP)
- 支持事务的处理以及框架的整合
- 方便程序的测试
- 降低Java EE API的使用难度
1.3、组成
Spring框架主要由七部分组成,分别是
- 容器模块(spring core)
- 应用上下文模块(spring context)
- AOP模块(spring aop)
- JDBC抽象和DAO模块(spring dao)
- 对象/关系映射集成模块(spring orm)
- Web模块(spring web)
- MVC模块(spring mvc)
1.4、拓展
Spring弊端:由于发展太久,导致Spring的配置十分繁琐!!
于是,就有了下面的框架:
- SpringBoot
- 一个快速开发的脚手架
- 使用SpringBoot可以快速开发一个微服务
- 约定大于配置!
- SpringCloud
- SpringCloud是基于SpringBoot实现的
注:学习SpringBoot的前提是学好Spring以及SpringMVC!!
2.IOC思想解析
IOC:Inversion of Control,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
2.1、场景模拟
假设,现在有一个公共的接口S,然后有四个类A,B,C,D都去实现了这个接口。
OK,业务来了,某一天,一个客户来到我这个地方,他说他需要A,行的,本着客户是上帝的原则,我给用户new了一个A的对象交给他使用,然后第二天,这个人又来了,他说他觉得A不好用,想用B了,ok,我收回了A,然后又new了一个B给他,我以为他到此为止了,但是事实证明用户的需求是时刻发生变化的,过了不久,他又来了,这次他说他想要C,我心里尽管十分的气愤,但是我还是忍了,于是又给他new了个C,但是我想着这样不是办法啊,这还是一个人,这还是才四个不同类型的对象,那万一以后有成千上万的类,那每个都要我来控制的话我不得累死,再加上如果每个用户都这么多事的话,那麻烦就大了。
相信到这里已经有人可以感觉到其中的麻烦之处了,对于一个优秀的程序来说,我们应该做到随你用户怎么变,我都有自己的一套应对方案,也就是所谓的少变动,如果因为用户的一个需求变动我们就需要去改动程序的话那么这个程序一定不够优秀。于是我想啊想,终于想到了一个好办法。
经历过上次的教训之后,我冥思苦想,终于想到了一个解决办法,人都是怕麻烦怕累的,但是有些东西不怕,那就是工具,人类是最擅长偷懒的了,为了方便我们发明出了许多工具,同样对于现在的情况我们也可以使用工具,于是我就找来了一个叫做“Spring”的工具,它可以自动的创建对象而不需要我自己去new,只需要用户告诉“Spring”他需要什么,然后由“Spring”给用户就好,好像我们在这个过程中没有出什么力,那就给自己安排一个控制人员的名头吧,由我将所有的类型“告诉”Spring,说的专业一点也就是注册,再设置好相应的程序即可,也就是告诉Spring怎么样去创建并且注入对象以及在什么时候需要给用户对象即可至于需要什么对象则由用户告诉Spring。
2.2、概念解析
到这里这个场景就模拟结束了,虽然文笔相当的烂,但是无法掩盖的一个事实就是原本创建对象的操作由我们程序员实现变成了由“Spring”容器实现。其实到这里我们可能依旧无法理解什么是控制反转,我们可能会想,控制反转难道不是控制权反转了吗,比如A控制B,控制反转不就是B控制A吗,也就是我们控制对象变成对象控制我们?好像不太像,那控制反转究竟是什么呢,博主百思不得其解,想啊想,终于有了一个勉强说服自己的解释:那就是控制反转也可以叫做控制权反转,假设现在有三方,程序员,对象,用户,如果把程序员和用户看成对立的两方的话,那么原本new对象的控制权是在我们这边的,但是现在,由于Spring容器的加入,使得创建对象的控制权变相的转移到了用户了,控制权也就发生了反转,本质上控制权是转交给了Spring容器,但是再继续想,是不是由用户告诉Spring容器自己需要那个对象,然后再由容器去创建,所以从某种程度上来说控制权反转到了用户也不是说不通。原本的对象的创建由程序员或者是原本的程序转交给了用户或者说Spring容器,程序员对立用户,原本的程序对立第三方的Spring容器,这么理解好像也行,当然仁者见仁智者见智,我写这些东西这是为了更好的去理解控制反转这个东西。
2.3、总结
- 对象的创建由原本的程序员主动new变成了根据需求由容器new再注入对象
- 程序由原本的主动创建对象变成了被动接收对象。
- 我们不再需要去程序中进行变动,从原本的将对象的new写死,改动的话牵一发而动全身变成了现在我们只需要改动配置文件中的bean,而用户只需要告诉容器他需要什么对象即可。
3.初涉Spring
3.1、 基础工作的准备
-
创建maven项目
-
导入相关依赖
-
编写相应的测试实体类,如下
public class Bean1 { Bean1(){ System.out.println("我是Bean1"); } } public class Bean2 { Bean2(){ System.out.println("我是Bean2"); } } public class Bean3 { Bean3(){ System.out.println("我是Bean3"); } }
3.2、配置文件编写
在resource目录下创建一个xml文件,名字不定,但一般为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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bean1" class="com.study.pojo.Bean1"/>
<bean id="bean2" class="com.study.pojo.Bean2"/>
<bean id="bean3" class="com.study.pojo.Bean3"/>
</beans>
- bean:由Spring容器创建管理的一个对象
- id:唯一标识一个bean
- class:类的全限定名,指定创建的bean的具体类型
3.3、Spring的使用
在测试类中初步使用bean获取对象
public void test(){
//加载Spring配置文件得到上下文对象,同时,在加载完Spring配置文件之后会创建在容器中所有的bean
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
//通过上下文对象的getBean方法,传入想要获取的对象的id获得对象,此处返回的对象是Object类型,需要强转
Bean1 bean = (Bean1) applicationContext.getBean("bean1");
//Bean2 bean = (Bean1) applicationContext.getBean("bean2");
//Bean3 bean = (Bean1) applicationContext.getBean("bean3");
bean.show();
}
4.Spring创建对象的方式
4.1、无参构造函数创建
<bean id="user" class="com.study.pojo.User">
</bean>
注:当使用该方法创建bean的时候,如果实体类中没有无参构造函数,那么会报错
4.2、有参构造函数创建
-
下标赋值:
<bean id="user1" class="com.study.pojo.User"> <constructor-arg index="0" value="小也"/> </bean>
-
类型赋值:
<bean id="user1" class="com.study.pojo.User"> <constructor-arg type="int" value="1"/> </bean>
注:不建议使用,并且当参数类型为引用类型的时候必须写全限定名
-
参数名赋值:
<bean id="user3" class="com.study.pojo.User"> <constructor-arg name="id" value="1"/> <constructor-arg name="name" value="小也"/> </bean>
5.Spring主要配置
5.1、alias
为bean对象创建一个别名,在使用时既可以使用别名,也可以使用原本的名字
<alias name="bean1" alias="bean12"/>
-
name:指定要创建的bean的id
-
alias:别名
5.2、bean(粗略)
由Spring容器创建一个bean对象
<bean id="user3" class="com.study.pojo.User" name="u2,u1 u4"/>
- id:唯一标识一个bean
- class:类的全限定名,指定创建的bean的具体类型
- name:为bean创建别名,并且可以多个,中间随意使用逗号或者空格分割
5.3、import
该标签一般由于团队开发,可以将多个配置文件导入合并到一个里面,例如存在以下配置文件:
- app1.xml
- app2.xml
- app3.xml
- app.xml
那么在使用的时候可以在app.xml里里面import其他三个配置文件,使用时加载app.xml即可
<import resource="app1.xml"/>
<import resource="app2.xml"/>
<import resource="app3.xml"/>
注:即是不同的配置文件有相同的bean也不影响
6.DI依赖注入
6.1、构造器注入
前文已讲,见第四大点Spring创建对象的方式
6.2、Set方式注入
-
环境搭建(各种复杂类型属性)
创建如下实体类:
public class People { private String name; private Address address; private String[] books; private List<String> hubbys; private Map<String,String> card; private Set<String> games; private String wife; private Properties info; } public class Address { private String address; }
另外还有Set和Get方法就不在多写。
-
注入
<bean id="address" class="com.study.pojo.Address"> <property name="address" value="四川绵阳"/> </bean> <bean name="people" class="com.study.pojo.People"> <!--第一种:普通类型注入,value--> <property name="name" value="小也"/> <!--第二种:对象类型注入,ref--> <property name="address" ref="address"/> <!--第三种:数组类型注入,array+value--> <property name="books"> <array> <value>三国演义</value> <value>西游记</value> <value>蛊真人</value> </array> </property> <!--第四种:list类型注入,list+value--> <property name="hubbys"> <list> <value>唱</value> <value>跳</value> <value>rap</value> </list> </property> <!--第五种:map类型注入,map+entry--> <property name="card"> <map> <entry key="id" value="001"/> <entry key="name" value="小也"/> <entry key="sex" value="男"/> </map> </property> <!--第六种:Set类型注入,set+value--> <property name="games"> <set> <value>王者荣耀</value> <value>云顶之弈</value> </set> </property> <!--第七种:空类型注入,null--> <property name="wife"> <null></null> </property> <!--第八种:属性注入,props+prop--> <property name="info"> <props> <prop key="身高">180cm</prop> <prop key="体重">120斤</prop> <prop key="年龄">22</prop> </props> </property> </bean>
-
测试
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml"); People people = (People) applicationContext.getBean("people"); System.out.println(people);
6.3、拓展方式注入
-
p命名空间注入
<bean id="people1" class="com.study.pojo.People" p:address-ref="address" p:card-ref="map"/> <bean id="map" class="java.util.HashMap"> <util:map> <entry key="id" value="002"/> </util:map> </bean>
-
在使用的时候需要加上:
xmlns:p="http://www.springframework.org/schema/p"
-
基本数据类型和String可以直接使用 p:属性名 设置值
-
其他复杂类型可以使用 p:属性名-ref 来引用
-
对应Set属性注入
-
-
c命名空间注入
<bean id="person" class="com.study.pojo.People" c:_0="小也" p:address-ref="address"/>
-
在使用的时候需要加上;
xmlns:c="http://www.springframework.org/schema/c"
-
基本数据类型和String可以直接使用 c:_index 或者 c:属性名 设置值
-
其他复杂类型可以使用 c:_index-ref 或者 c:属性名-ref 来引用
-
对应构造器注入
-
6.4、bean作用域
作用域 | 描述 |
---|---|
singleton | 默认值。当IOC容器一创建就会创建bean的实例,而且是单例的,每次得到同一个。 |
prototype | 原型的。当IOC容器一创建不再实例化该bean,每次调用getBean方法时再实例化该bean,而且每次调用都会返回一个新的实例 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session 共享一个Bean,不同的 HTTP Session 使用不同的Bean. 该作用域仅适用于WebApplicationContext环境。 |
application | 将单个bean定义的作用域限定为ServletContext的生命周期。仅在可感知网络的Spring ApplicationContext上下文中有效。 |
websocket | 将单个bean定义的作用域限定为WebSocket的生命周期。仅在可感知网络的Spring ApplicationContext上下文中有效。 |
-
单例模式(默认):
<bean id="bean1" class="com.study.pojo.Bean1" scope="singleton"/>
-
原型模式:
<bean id="bean2" class="com.study.pojo.Bean2" scope="prototype"/>
-
其他四个是需要在web开发中使用,具体的作用域与web开发中的作用域相差不多。
7.bean的自动装配
- 自动装配:Spring在上下文中自动寻找,并自动给bean装配
bean装配的三种方式:
- 在xml显示的配置
- 在java中显示的配置
- 隐式的自动装配(重要)
7.1、测试环境搭建
一个person类和两个宠物类
public class Person {
private Cat cat;
private Dog dog;
}
public class Cat {
public void shout(){
System.out.println("喵喵~");
}
}
public class Dog {
public void shout(){
System.out.println("汪汪~");
}
}
7.2、ByName自动装配
<bean id="cat" class="com.study.pojo.Cat"/>
<bean id="dog" class="com.study.pojo.Dog"/>
<!--byName:自动在容器的上下文中查找与和自己set方法后面名字对应的bean的id,如果存在则自动装配,如setDog查找dog-->
<bean id="person" class="com.study.pojo.Person" autowire="byName"/>
7.3、ByType自动装配
<bean id="cat" class="com.study.pojo.Cat"/>
<bean id="dog" class="com.study.pojo.Dog"/>
<!--byType:自动在容器的上下文中查找与和自己属性类型匹配的bean,如果存在则自动装配-->
<bean id="person" class="com.study.pojo.Person" autowire="byType"/>
小结:
- byName:使用的时候需要保证bean的id必须要与set方法后面的名字相同,且id必须是小写。大写不行
- byType:使用时需要保证只存在一个类型相匹配的bean,否则无法自动装配
7.4、使用注解的自动装配
在spring框架中推荐使用注解开发会相对便捷一些,使用xml比较繁琐,具体使用哪种视情况而定!!
使用注解开发需要引入注解的命名空间和约束,并且开启注解支持:
-
命名空间
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/>
7.4.1、@Autowired:
该注解是属于spring的
-
使用:
-
可以在属性上使用
@Autowired private Cat cat;
-
也可以在set方法上使用
@Autowired public void setCat(Cat cat) { this.cat = cat; }
注:
-
使用该注解的时候可以不用写set方法
-
在使用@Autowired时会先根据类型进行装配,如果只有一个符合类型的bean,那么装配该bean,如果存在多个符合类型的bean,那么会根据名字进行装配,但是与byName通过名字装配不同,byName是根据set方法后面的名字进行匹配,而该注解是根据属性的名字进行匹配,且与byName的id只能是小写不同,该注解完全按照属性名匹配,如果同时在xml文件使用自动装配以及@Autowired注解,那么xml的优先级更高
-
如果spring上下文中存在多个相同类型不好进行匹配的时候,那么可以结合另一个注解使用,该注解可以指定一个唯一的id
@Autowired @Qualifier("cat") private Cat cat1; //如果不使用该注解会先根据类型匹配,如果有多个类型相同的,则匹配cat1,如果使用该注解那么匹配cat
-
7.4.2、@Resource:
该注解是属于java的
使用:
-
可以在属性上使用
@Resource private Cat cat;
-
也可以在set方法上使用
@Resource public void setCat(Cat cat) { this.cat = cat; }
注:
-
使用该注解的时候可以不用写set方法
-
使用@Resource进行装配会优先根据名字进行匹配,与byName不同,它也是根据属性的名字进行匹配,如果名字没有匹配的,那么会根据类型进行匹配,如果只有一个该类型的bean那么会自动装配
-
该注解可以指定name
@Resource(name = "cat1") private Cat cat;
8.使用注解开发
8.1、注册bean
@Component
@Component("user1")
public class User {
...
}
- 使用该注解相当于在配置文件里面的bean
- 可以传入参数设定bean的id
- 如果不传入参数那么id为类名首字母小写
8.2、属性值注入
@Value("汪汪~")
private String name;
- 如果属性为复杂的类型,建议还是可以到配置文件中去注册bean
8.3、Component衍生注解
在web开发中我们常常使用衍生的注解,更加规范更加层次分明
-
@Repository:使用在dao层
@Repository public class UserDao { }
-
@Service:用在service层
@Service public class UserService { }
-
@Controller:用在controller层
@Controller public class UserController { }
注:功能与Component注解是一样的,都是相当于将类注册为一个bean
8.4、自动装配注解
- @Autowired
- @Resource
注:详细使用见第七大点bean的自动装配
8.5、作用域
@Scope("singleton")
public class UserController {
}
- 可以设定bean的作用域,与注解在配置文件里面设定作用相同
8.6、总结
xml与注解:
- xml虽然相对繁琐一点,但是任何情况都适应,并且维护简单方便!
- 注解虽然简单,但是某些复杂的操作却不易完成,且维护相对困难
建议方案:xml与注解结合使用
- 使用配置文件管理bean
- 使用注解完成属性的注入
注:在使用注解的时候需要开启注解的支持
<!--指定要扫描的包,指定的包下注解会生效-->
<context:component-scan base-package="com.study"/>
<!--开启注解-->
<context:annotation-config/>
9.使用java配置Spring(浅尝)
在现在的Spring的使用中完全可以不用配置文件,转而通过一个配置类来实现配置!
关键点在于一个注解@Configuration!!
-
实体类
public class User { private String name; private List<String> books; }
注:set方法等等没有写
-
配置类
//该类也会注册到Spring的容器中,因为注解本身也使用了@Component //使用该注解代表一个配置类,相当于之前的applicationContext.xml @Configuration //扫描注解配置的包 @ComponentScan("com.study") //相当于配置文件里的import标签 @Import(Config1.class) public class Config { //注册一个bean,相当于bean标签 @Bean //方法名相当于id //返回类型相当于class public User User() { //方法里面的赋值操作相当于之前的属性注入 User user = new User(); user.setName("xaioye"); List<String> books=new ArrayList<>(); books.add("三国演义"); books.add("西游记"); books.add("蛊真人"); user.setBooks(books); return user; } }
-
使用
public class MyTest { @Test public void test(){ //要想使用配置类必须使用AnnotationConfigApplicationContext去加载配置类,通过配置类的class获取到上下文对象 ApplicationContext context = new AnnotationConfigApplicationContext(Config.class); User user = (User) context.getBean("User"); System.out.println(user); } }
注:这种使用纯java方式的Spring随处可见!!!
10.代理模式
为什么要学习代理模式?
因为他是SpringAOP的底层!!
代理模式分类:
- 静态代理
- 动态代理
10.1、静态代理
10.1.1、角色分析
- 抽象角色:一般使用接口或者抽象类
- 真实角色:被代理的对象
- 代理角色:代理对象,可以帮助真实对象拓展一些业务
- 客户:访问代理对象
10.1.2、代码模拟
-
抽象角色:
public interface Rent { public void rent(); }
- 抽象的业务:出租房子
-
真实角色:
public class Landlord implements Rent{ @Override public void rent() { System.out.println("房东:我要出租房子"); } }
- 真实角色房东需要出租房子,实现出租接口
-
代理角色:
public class Intermediary implements Rent{ Landlord landlord; public Intermediary(Landlord landlord) { this.landlord = landlord; } @Override public void rent() { houseViewing(); landlord.rent(); signContract(); agencyFee(); } //看房子 public void houseViewing(){ System.out.println("中介:先看看房子"); } //签合同 public void signContract(){ System.out.println("满意就签合同"); } //中介费 public void agencyFee(){ System.out.println("支付中介费"); } }
- 代理角色中介,帮助房东出租房子,并且在出租房子的基础上拓展业务
-
客户:
public class Customer { public static void main(String[] args) { Landlord landlord = new Landlord(); Intermediary intermediary = new Intermediary(landlord); intermediary.rent(); } }
- 客户,访问代理对象中介租房子
10.1.3、静态代理的优缺点
静态代理模式的好处:
- 让真实角色的操作更加纯粹,不用关心公共的业务!
- 公共部分通过代理角色来实现,实现分工!
- 在拓展公共的业务时,集中起来从而让管理更加的方便!
静态代理模式的缺点:
- 一个真实角色就会产生一个代理角色,导致代码量大大增加!
10.2、动态代理
10.2.1、了解动态代理
- 动态代理的角色与静态代理一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:
- 基于接口:JDK动态代理(这里使用这个)
- 基于类:cglib
- 额外:java字节码实现:javassist
需要了解两个类:
- Proxy:代理
- InvocationHandler:调用处理程序
10.2.2、代码实现
-
抽象角色:
public interface Rent { public void rent(); }
- 抽象的业务:出租房子
-
真实角色:
public class Landlord implements Rent{ @Override public void rent() { System.out.println("房东:我要出租房子"); } }
- 真实角色房东需要出租房子,实现出租接口
-
代理角色:动态代理类没有现成的代理类,而是通过Proxy类动态的生成一个代理类并且通过InvocationHandle类对呗代理的对象的方法进行加强
public class ProxyUtils implements InvocationHandler { //被代理的对象 private Object target; //通过set方法注入需要被代理的对象 public void setTarget(Object target) { this.target = target; } //Proxy.newProxyInstance:通过该方法可以根据传入的参数得到一个具体的代理类 //参数解读: //this.getClass().getClassLoader():通过类加载器来定义代理类,这里直接传入本类的类加载器 //target.getClass().getInterfaces():被代理对象的接口接口类型 //this:调度方法调用的调用处理函数,由于本类实现了InvocationHandler接口,故在这里直接传入this //方法作用:生成一个代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this); } //参数解读: //proxy: //method:要被加强的方法 //args:方法所需要的参数 //通过该方法处理代理对象,加强代理对象的原本的方法并返回一个结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //加强方法 log(method.getName()); //通过反射调用method,需要传入该方法的对象也即是通过set获得被代理对象的target Object result = method.invoke(target, args); return result; } //编写加强方法的方法 public void log(String msg){ System.out.println("使用了"+msg+"方法"); } }
- 在加强方法的时候只需要在按照log方法的方式加强即可
-
客户:
public class Customer { public static void main(String[] args) { //需要被代理的对象 Landlord landlord = new Landlord(); //获得创建动态代理类的工具类对象 ProxyUtils proxyUtils = new ProxyUtils(); //通过工具类对象的set方法传入需要被代理的对象 proxyUtils.setTarget(landlord); //生成动态代理类,此处只能是接口类型 Rent proxy = (Rent) proxyUtils.getProxy(); proxy.rent(); } }
小结:
动态代理使用步骤:
- 创建需要被代理的对象
- 通过需要的代理的对象创建代理类对象
- 通过代理类对象调用被加强过后的方法
10.2.3、动态代理的优点
- 让真实角色的操作更加纯粹,不用关心公共的业务!
- 公共部分通过代理角色来实现,实现分工!
- 在拓展公共的业务时,集中起来从而让管理更加的方便!
- 一个动态代理类可以代理多个类,只要实现了同一个接口就行
11.AOP
11.1、什么是AOP
面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
11.2、AOP在Spring中的作用
-
AOP中的一些专业术语
- Aspect:跨多个类的关注点的模块化。事务管理是企业 Java 应用程序中横切关注点的一个很好的例子。在 Spring AOP 中,方面是通过使用常规类(基于模式的方法)或使用
@Aspect
注解注释的常规类(@AspectJ 样式)来实现的。 - 连接点:程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
- 通知:方面在特定连接点采取的行动。不同类型的建议包括“周围”、“之前”和“之后”建议。(通知类型将在后面讨论。)包括 Spring 在内的许多 AOP 框架将通知建模为拦截器,并在连接点周围维护一个拦截器链。
- 切入点:匹配连接点的谓词。Advice 与切入点表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配的连接点的概念是 AOP 的核心,Spring 默认使用 AspectJ 切入点表达式语言。
- 切面:代表一个类型声明额外的方法或字段。Spring AOP 允许您向任何建议的对象引入新接口(和相应的实现)。例如,您可以使用介绍使 bean 实现
IsModified
接口,以简化缓存。(介绍在 AspectJ 社区中称为类型间声明。) - 目标对象:一个或多个方面建议的对象。也称为“建议对象”。由于 Spring AOP 是使用运行时代理实现的,因此该对象始终是代理对象。
- AOP 代理:由 AOP 框架创建的对象,用于实现方面协定(建议方法执行等)。在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
- 编织:将方面与其他应用程序类型或对象链接以创建建议对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。
- Aspect:跨多个类的关注点的模块化。事务管理是企业 Java 应用程序中横切关注点的一个很好的例子。在 Spring AOP 中,方面是通过使用常规类(基于模式的方法)或使用
-
通知五种类型:
1、前通知:方法执行之前
2、后通知:方法执行之后,又称最终通知,无论如何都执行
3、返回后通知:成功返回后,有异常时不执行
4、异常通知:发生异常后,只有异常抛出时才执行,不能try…catch异常
5、环绕通知:在方法的执行前后进行一些增强,在方法的执行前后进行一些增强 =前通知+后通知
11.3、Spring实现AOP
注:使用前需要导入依赖包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
11.3.1、使用配置文件方式实现
11. 3.1.1、使用SpringAPI接口实现
-
实现接口:
-
前通知:
public class befor implements MethodBeforeAdvice { //method:要执行的目标对象的方法 //objects:方法所需的参数 //o:目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("这是前通知~"); } }
-
后通知:
public class After implements AfterAdvice { public void after() throws Throwable{ System.out.println("这是后通知~"); } }
-
环绕通知:
public class Around implements MethodInterceptor { @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("这是环绕通知"); //执行原本的方法 methodInvocation.proceed(); System.out.println("这是环绕通知"); return null; } }
-
异常通知:
public class Throws implements ThrowsAdvice { public void throwAdvice(Exception e) { System.out.println("这是异常通知~"); } }
-
返回后通知:
public class AfterReturn implements AfterReturningAdvice { @Override public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("这是返回后通知~"); } }
- o为返回值
- method为方法封装对象
- o1为当前目标对象
-
-
配置AOP
-
导入约束和命名空间
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
-
配置
<bean id="ago" class="Aop.befor"/> <bean id="after" class="Aop.After"/> <bean id="around" class="Aop.Around"/> <bean id="throws" class="Aop.Throws"/> <bean id="afterR" class="Aop.AfterReturn"/> <bean id="userService" class="Aop.UserServiceImpl"/> <aop:config> <aop:pointcut id="test1" expression="execution(* Aop.UserServiceImpl.*(..))"/> <aop:advisor advice-ref="ago" pointcut-ref="test1"/> <aop:advisor advice-ref="after" pointcut-ref="test1"/> <aop:advisor advice-ref="afterR" pointcut-ref="test1"/> <aop:advisor advice-ref="throws" pointcut-ref="test1"/> <aop:advisor advice-ref="around" pointcut-ref="test1"/> </aop:config>
- aop:config:配置一个AOP
- aop:pointcut:创建一个切入点,可以同时创建多个
- expression:表达式,形如:execution(* 切入点.*(..))
- execution(): 表达式主体。
- 第一个星号:表示返回类型,*号表示所有的类型。
- 切入点:表示需要配置的包或者类,可以具体到包或者具体到类
- 第二个星号:表示类名,*号表示所有的类。
- *(..):最后这个星号表示方法名,星号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
- aop:advisor:在指定切入点进行方法增强,advice-ref指定引用哪个类切入
-
-
测试
public class MyTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); //注:在获取实现了AOP的类时,返回类型应该是接口 UserService userService = context.getBean("userService", UserService.class); userService.show(); } }
11.3.1.2、使用自定义类实现
-
定义切面类
public class diyProxy { public void befor(){ System.out.println("这是前置通知"); } public void after(){ System.out.println("这是后置通知"); } //当有环绕通知的时候,必须手动执行原本未被加强的方法,可以通过传入一个ProceedingJoinPoint类型的参数,通过该参数的proceed()方法执行 public void around(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("这是环绕通知之前"); try { proceedingJoinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("这是环绕通知之后"); } public void afterR(){ System.out.println("这是返回后通知"); } public void error(){ System.out.println("这是异常通知"); } }
-
配置
<bean id="diy" class="com.study.Aop.diyProxy"/> <aop:config> <aop:aspect ref="diy"> <aop:pointcut id="point" expression="execution(* Aop.UserService.show(..))"/> <!--通知,method对应切面类里面的方法--> <aop:after method="after" pointcut-ref="point"/> <aop:before method="befor" pointcut-ref="point"/> <aop:after-returning method="afterR" pointcut-ref="point"/> <aop:around method="around" pointcut-ref="point"/> <aop:after-throwing method="error" pointcut-ref="point"/> </aop:aspect> </aop:config>
11.3.2、使用注解方式实现
-
编写切面类
@Component @Aspect//标注一个类为切面 public class AnnotationPointCut { @Before("execution(* Aop.UserServiceImpl.show(..))") public void before(){ System.out.println("这是前置通知"); } @After("execution(* Aop.UserServiceImpl.show(..))") public void after(){ System.out.println("这是后置通知"); } @Around("execution(* Aop.UserServiceImpl.show(..))") public void around(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("这是环绕通知之前"); try { proceedingJoinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("这是环绕通知之后"); } @AfterReturning("execution(* Aop.UserServiceImpl.show(..))") public void afterR(){ System.out.println("这是返回后通知"); } @AfterThrowing("execution(* Aop.UserServiceImpl.show(..))") public void error(){ System.out.println("这是异常通知"); } }
-
开启注解
<!--指定要扫描的包,指定的包下注解会生效--> <context:component-scan base-package="Aop"/> <!--开启注解--> <context:annotation-config/> <bean id="userService" class="Aop.UserServiceImpl"/> <!--开启aop注解--> <aop:aspectj-autoproxy/>
12.整合Mybatis
官方文档:http://mybatis.org/spring/zh/index.html
导入相应的依赖
-
junit
-
mysql相关
-
mabtis相关
-
Spring相关
-
aop织入
-
mybatis-Spring(新!)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.9</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.8</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.2</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.9</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency>
12.1、整合mybatis两种方式
12.1.1、方式一
-
整合数据库配置
<!--我们使用Spring提供的JDBC去配置数据源,通过:org.springframework.jdbc.datasource.DriverManagerDataSource--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="18227022334a"/> </bean>
-
获取SqlSessionFactory
<!--通过mybatis-spring包下的SqlSessionFactoryBean来获得SqlSessionFactory的bean实例--> <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!--在这里面可以完成mybatis配置文件的一些功能--> <!--绑定mybatis配置文件,可以使用mybatis文件里的配置--> <property name="configLocation" value="classpath:mybatis.xml"/> <!--注册Mapper.xml配置文件--> <property name="mapperLocations" value="classpath:com/study/dao/UserMapper.xml"/> </bean>
-
获取SqlSessionTemplate(也就是SqlSession)
<!--通过mybatis-spring包下SqlSessionTemplate获取SqlSessionTemplate的bean实例,也就是我们的SqlSession--> <bean id="SqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能通过构造注入的方式,因为该类里面没有对应的set方法--> <constructor-arg index="0" ref="SqlSessionFactory"/> </bean>
-
编写接口实现类(相比于mybatis多出来的东西)
public class UserServiceImpl implements UserService{ //之前我们都是使用SqlSession来完成所有操作,现在我们使用SqlSessionTemplate private SqlSessionTemplate sqlSession; //通过set方法注入 public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> queryUser() { UserServiceImpl mapper = sqlSession.getMapper(UserServiceImpl.class); return mapper.queryUser(); }
-
在spring里面注册实现类的bean
<bean id="user" class="com.study.dao.UserServiceImpl"> <!--将sqlSession注入--> <property name="sqlSession" ref="SqlSession"/> </bean>
-
测试
public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserServiceImpl user = context.getBean("user", UserServiceImpl.class); List<User> users = user.queryUser(); for (User user1 : users) { System.out.println(user1); } }
12.1.2、方式二(简化)
-
整合数据库配置(同上)
-
获取SqlSessionFactory(同上)
-
实现类继承SqlSessionDaoSupport
public class UserServiceImplT extends SqlSessionDaoSupport implements UserService{ @Override public List<User> queryUser() { return getSqlSession().getMapper(UserService.class).queryUser(); } }
- 在SqlSessionDaoSupport类中有一个getSqlSession()方法可以直接获得SqlSessionTemplate
-
spring中注册实现类
<bean id="user1" class="com.study.dao.UserServiceImplT"> <property name="sqlSessionFactory" ref="SqlSessionFactory"/> </bean>
-
测试
public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserServiceImplT user = context.getBean("user1", UserServiceImplT.class); List<User> users = user.queryUser(); for (User user1 : users) { System.out.println(user1); } }
12.2、总结
整合后的mybatis的内容变化:
- mybatis配置文件可以被取代,其中的数据库配置和其他一些设置可以在spring配置文件中实现
- 接口保留
- 接口的Mapper.xml文件保留
- mybatis获取SqlSession的工具类被取代,也在spring配置文件中实现
整合后spring的内容变化:
- spring配置文件多出了数据源的bean,SqlSessionFactory的bean以及SqlSession的bean
- 在SqlSessionFactory的bean可以完成一些mybatis配置文件中的设置,如mapper的注册等等
- 多了一个接口实现类,因为原本的mybatis的接口无法再spring容器中生成一个bean实例
13.声明式事务
13.1、事务
要么都执行,要么都不执行
事务的ACID原则:
-
原子性:要么都成功,要么都失败,即不会出现钱转出去了但是没有收到的情况,即转钱和收钱两个操组合作为一个不可拆分的操作
-
一致性:事务前后数据完整性一致,比如转钱转钱一共2000,那么转钱之后一共也是2000
-
持久性-----事务提交:事务提交后不可逆,被持久化保存到数据库中
-
隔离性:多个事务之前是相互隔离的,互不影响。数据库允许多个并发同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致的数据不一致。
比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
13.2、Spring事务管理
- 声明式事务:AOP
- 编程式事务:在代码中进行事务的管理
13.2.1、手动声明式事务实现
-
添加约束和命名空间
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
-
配置事务管理器
<!--配置声明式事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <constructor-arg ref="dataSource" /> </bean>
-
配置事务的传播特性
<!--结合AOP实现事务的织入--> <!--配置事务的通知--> <tx:advice id="transactionInterceptor" transaction-manager="transactionManager"> <!--给哪些方法配置事务--> <tx:attributes> <tx:method name="query*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
- 注: tx:attribute标签所配置的是作为事务的方法的命名类型。如<tx:method name="save**" propagation="REQUIRED"/>*
- 其中*为通配符,即代表以save为开头的所有方法,即表示符合此命名规则的方法作为一个事务。
- propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- 注: tx:attribute标签所配置的是作为事务的方法的命名类型。如<tx:method name="save**" propagation="REQUIRED"/>*
-
事务织入
<!--事务织入--> <aop:config> <aop:pointcut id="pointCut" expression="execution(* com.study.dao.UserService.*(..))"/> <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="pointCut"/> </aop:config> <!--很重要,不开启的话会报错,因为实现事务的类没有使用接口方式实现AOP,使用会使用cglib的方式实现动态代理,而spring默认的是JDK方式的动态代理实现,使用需要开启cglib--> <aop:aspectj-autoproxy proxy-target-class="true"/>
13.2.2、自动声明式事务实现
-
添加约束和命名空间
-
配置事务管理器
-
在需要实现事务的方法上使用@Transactional注解
@Transactional @Override public List<User> queryUser() { insertUser(new User(10,"小也","12313")); deleteUserById(10); UserService mapper = sqlSession.getMapper(UserService.class); return mapper.queryUser(); }
-
在spring配置文件开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"/> <aop:aspectj-autoproxy proxy-target-class="true"/>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律