目录
1、什么是Spring?
Spring : 春天 —>给软件行业带来了春天
2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。
2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。
很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
SSH:Struct2+Spring+Hibernate(全自动持久化层)
SSM:SpringMVC+Sprin+Mybatis(半自动持久化层)
1.1、优点
1、Spring是一个开源免费的框架 , 容器 .
2、Spring是一个轻量级的框架 , 非侵入式的 .
3、控制反转 IoC , 面向切面 Aop
4、对事物的处理 , 对框架整合的支持
…
一句话概括:
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。
1.2、7大组成
7模块内容:
- SpringCore(核心容器):核心容器提供Spring的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- SpringContext上下文:Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮箱、国际化、校验和调度功能。
- SpringAOP(面向切面编程):通过配置管理特性,SpringAOP模块直接将面向切面编程集成到了Spring框架中,所以,可以很容易地使Spring框架管理任何支持AOP的对象。SpringAOP模块为基于Spring的应用程序中的对象提供了事务管理服务。通过使用SpringAOP,不用依赖组件就可以将声明性的事务管理集成到应用程序中。
- SpringDAO:JDBC DAO抽象层提供了有意义的异常管理层次结构,可用该结构来管理异常处理和不同的数据库供应商抛出的错误信息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量。SpringDAO的面向JDBC的 异常遵从通用的DAO异常层次结构。
- SpringORM:Spring框架插入了若干个ORM框架,从而提供了ORM的对象关系工具,其中包括JDO、Hibernate和iBatis SQL Map。所有这些都遵从Spring的通用事务和DAO异常层次结构
- SpringWeb模块:Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供上下文。所以,Spring框架支持与Jakarta Struts的集成。web模块还简化了处理多部分请求以及将请求参数帮定到域对象的工作。
- SpringMVC框架:MVC框架是 一个全功能的构建web应用程序的MVC实现。通过策略接口,MVC框架变成高度可配置的,MVC容纳了大量的视图技术,其中包括JSP、Velocity、Tiles、iText和POI.
1.3、拓展
Spring Boot与Spring Cloud
- Spring Boot是Spring的一套快速配置脚手架,可以基于Spring Boot快速开发单个微服务;
- Spring Cloud是基于Spring Boot实现的
- Spring Boot专注于快速、方便集成的单个微服务个体,Spring Cloud关注全局的服务治理框架
- Spring Boot 使用了约束优于配置的理念,很多集成方案已经帮你选择好了,能不配置就不用配置,SpringCloud 很大一部分是基于Spring Boot 来实现的,Spring Boot可以离开Spring Cloud独立使用开发项目,但是Spring Cloud就需要依赖于Spring Boot
- Spring Boot 在SpringCloud中起到承上启下的作用,如果想学习SpringCloud必须学习SpringBoot
2、IOC理论推导
IOC基础
1、UserDao接口
2、UserDaoImp实现类
3、UserService业务接口
4、UserService业务实现类
在之前的业务中,我们根据用户需求修改源代码,修i该成本大。
使用一个set接口:
public class UserServiceImp implements UserService{
private UserDao userDao ;
//使用set动态实现需求
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
- 之前程序是主动创建对象,控制权在开发者
- 使用set注入后,程序变得被动接收对象
这种思想使得系统的耦合性大大降低,我们可以更专注在业务的实现上,这就是IOC原型
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
IOC是Spring框架的核心内容,使用多种方式完美的实现了IOC,可以用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。
Spring容器在初始化时先读取配置文件,根据配置文件或源数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实体类中,从而达到零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入
3、helloSpring
1、新建一个 实体类Hello
public class Hello {
private String name;
public void show(){
System.out.println("hello:"+name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2、新建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">
<!--使用Spring创建对象,在spring中这些对象称为bean
类型 变量名 = new 类型();
Hello hello = new Hello();
id = 变量名;
class = new的对象
property给属性赋值
-->
<bean id="hello" class="com.zhang.pojo.Hello">
<property name="name" value="哈喽Spring"/>
</bean>
</beans>
3、测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean("hello")这里的hello是id
Hello hello = (Hello) context.getBean("hello");
hello.show();
}
}
- Hello对象是由Spring创建的
- hello对象的属性是由spring容器设置的
- 这个过程叫做控制反转
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象由Spring创建
- 反转:程序本身不创建对象,而变成被动的接收对像
- 依赖注入:就是利用set方法进行注入
IOC的思想是主动的编程变成被动的接收
4、IOC创建对象方式
- 默认是通过无参构造函数创建对象。
- 可以使用有参构造函数创建
使用有参构造函数创建的三种方式:
1、下标赋值:
<constructor-arg index="0" value="下标赋值"/>
2、通过类型赋值(不建议使用,参数只有一个时可以用)
如果多个构造参数都是同一个类型呢?
答:会根据标签的顺序对同一类型的参数进行从前到后赋值。
<constructor-arg type="java.lang.String" value="类型赋值"/>
3.、直接通过参数名字赋值(最常用)
<constructor-arg name="name" value="直接用名字"/>
在配置文件加载的时候,容器中管理的对象就已经初始化了!
IOC默认创建对象都是单例模式的,一个类只能拿到一个实例
5、Spring配置
5.1、别名
<!--如果添加了别名,我们也可以使用别名获取到这个对象-->
<alias name="user" alias="newUser"/>
5.2、bean的配置
id:bean的唯一标识符,也就是对象名字 class:bean对象所对应的全限定名:包名+类名 name:也就是别名,可以取多个别名,多个别名之间使用空格或者“,”或者“;”隔开 <bean id="user2" class="com.zhang.pojo.UserT" name="u1 u2,u3;u4"> <property name="name" value="user2"/> </bean>
5.3、import
团队开发使用,可以将多个配置文件导入合并到一个
假如团队有bean2.xml,bean3.xml,beans.xml和applicationContext.xml,我们可以在applicationContext.xml里使用import将其他的三个bean合并在applicationContext.xml里
<import resource="beans.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>
使用时,直接用总成的就可以。
注:如果这些bean中有重复的对象名或对象名的别名,spring会自动合并不影响使用。
6、DI依赖注入
6.1、构造器注入
6.2、Set方式注入(重点)
- 依赖:bean的创建依赖于容器
- 注入:bean中的所有属性,由容器来注入
测试环境搭建:
1、创建两个实体类address、student
public class Student {
private String name;
private Address Address;
private String[] books;
private List<String> hobbys;
private Map<String, String> card;
private Set<String> games;
private String wife;
private Properties info;
}
public class Address {
private String address;
}
2、创建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="address" class="com.zhang.pojo.Address">
<property name="address" value="北京"/>
</bean>
<bean id="student" class="com.zhang.pojo.Student">
<!--普通值注入,value-->
<property name="name" value="张三"/>
<!--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>
<value>旅游</value>
<value>听歌</value>
</list>
</property>
<!--map-->
<property name="card">
<map>
<entry key="ID" value="411503200301230716"/>
<entry key="phone" value="123456789"/>
<entry key="银行" value="41150685877230716"/>
</map>
</property>
<!--set-->
<property name="games">
<set>
<value>LOL</value>
<value>CSGO</value>
<value>王者荣耀</value>
</set>
</property>
<!--null-->
<property name="wife" value=""/>
<!--properties-->
<property name="info">
<props>
<prop key="学号">20220213</prop>
<prop key="性别">男</prop>
<prop key="班级">软工二班</prop>
</props>
</property>
</bean>
</beans>
3、测试
@Test
public void t(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
/*
* Student{
* name='张三',
* Address=Address{address='北京'},
* books=[红楼梦, 西游记, 三国演义, 水浒传],
* hobby's=[女, 电影, 游戏, 旅游, 听歌],
* card={
* ID=411503200301230716,
* phone=123456789,
* 银行=41150685877230716},
* games=[LOL, CSGO, 王者荣耀],
* wife='',
* info={学号=20220213, 性别=男, 班级=软工二班}}
* */
}
6.3其他方式注入
P命名空间和C命名空间
想要使用先导入xml约束:
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
使用:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接写入属性值:properties-->
<bean id="user" class="com.zhang.pojo.User" p:name="李四"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user1" class="com.zhang.pojo.User" p:name="王五"/>
</beans>
测试:
@Test
public void t1(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBean.xml");
User user = context.getBean("user1", User.class);
System.out.println(user);
}
6.4、bean的作用域
1、单列模式(spring默认模式)
<bean id="user" class="com.zhang.pojo.User" p:name="李四" scope="singleton"/>
2、原型模式
每次从容器里get的时候都会产生一个新对象。
<bean id="user" class="com.zhang.pojo.User" p:name="李四" scope="prototype"/>
3、其他的request、session、application这些只能在web的开发中使用。
7、bean的自动装配
spring会自动在上下文中寻找,并自动给bean装配属性。
Spring中属性有三种装配方式
- 在xml中显示配置;
- 在Java中显示配置;
- 隐式的自动装配
测试:一个人俩宠物,猫和狗
7.1、byName自动装配
使用自动装配之前:
<bean id="cat" class="com.zhang.pojo.Cat"/>
<bean id="dog" class="com.zhang.pojo.Dog"/>
<bean id="people" class="com.zhang.pojo.People">
<property name="name" value="张三"/>
<property name="dog" ref="dog"/>
<property name="cat" ref="cat"/>
<bean/>
使用自动装配后:
<bean id="cat" class="com.zhang.pojo.Cat"/>
<bean id="dog" class="com.zhang.pojo.Dog"/>
<!--
byName:会自动在spring容器上下文中寻找和自己对象set方法后参数对应的beanID
-->
<bean id="people" class="com.zhang.pojo.People" autowire="byName">
<property name="name" value="张三"/>
</bean>
byName:会自动在spring容器上下文中寻找和自己对象set方法后参数对应的beanID.
如果把其中一个引用属性的beanid改掉就会抛空指针异常:
所以需要保证beanid的唯一性
<bean id="dog1" class="com.zhang.pojo.Dog"/>
7.2、byType自动装配
byType:会自动在spring容器上下文中寻找和自己对象属性类型相同的bean
使用byType自动装配不需要beanid,但要保证属性类型的唯一性。
<bean class="com.zhang.pojo.Cat"/>
<bean class="com.zhang.pojo.Dog"/>
<!--
byName:会自动在spring容器上下文中寻找和自己对象set方法后参数对应的beanID
byType:会自动在spring容器上下文中寻找和自己对象属性类型相同的bean
-->
<bean id="people" class="com.zhang.pojo.People" autowire="byType">
<property name="name" value="张三"/>
</bean>
7.3、使用注解实现自动装配
基于注解的配置提供了 XML 设置的替代方法,该配置依赖字节码元数据来连接组件,而不是尖括号声明。通过使用相关类,方法或字段声明上的 注解,开发人员无需使用 XML 来描述 bean 的连接,而是将配置移入组件类本身。
使用注解开发的步骤:
- 导入约束:context约束
- 配置注解的支持: <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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
注解:@Autowired
在实体类中的属性或者set方法上使用(前提是这个属性在IOC容器中存在,且符合byName的规范),在属性上使用时set方法可以不用写,但get要保留。
@Nullable 标记了这个注解表示 这个字段可以为null
public @interface Autowired {
boolean required() default true;
}
//如果显性的定义了Autowired的required为false,说明这个对象可以为空,否者不能为空。
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
private String name;
如果@Autowired的装配环境比较复杂,即有多个相同类型的bean,自动装配不能通过一个注解(Autowired)完成时,我们可以添加限定符,使用@Qualifier(value = "XXX")去指定哪一个bean。
@Autowired(required = false)
@Qualifier(value = "cat1")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
@Resource
@Resource(name = "cat1")
private Cat cat;
@Resource
private Dog dog;
小结:@Autowired 和@Resource的区别
- 相同点:都用来自动装配,都放在属性字段上;
- 区别:@Autowired是先通过byType方式实现,再通过byName 必须要求这个对象存在;
- @Resource默认通过byName方式实现,如果找不到唯一的bean就使用byType方式找,如果还没有唯一的bean就报错;
- 执行顺序不同:@Autowired是先通过byType方式,再通过byName;@Resource是先byName再byType;
8、Spring注解开发
使用Spring注解开发前需要保证aop包已经导入,还需要导入context约束,增加注解的支持
8.1、bean的注解
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.zhang.pojo"/>
<!--开启注解支持-->
<context:annotation-config/>
//Component组件,相当于<bean id="user" class="com.zhang.pojo.User"/>
@Component
public class User {
public String name = "张三";
}
8.2、属性注入
@Component
public class User {
//@Value相当于<property name="" value="zhangshan"/>
@Value("zhangshan")
private String name ;
public String getName() {
return name;
}
}
8.3、衍生注解
Component有几个衍生注解,我们在web开发中会按照MVC三层架构分层。
- dao(@Repository)
- service(@Service)
- controller(@controller)
这四个注解一样,都是将某个类注册到Spring中,装配bean
8.4、作用域
@Component
@Scope("prototype")
public class User {
//@Value相当于<property name="" value="zhangshan"/>
@Value("zhangshan")
private String name ;
public String getName() {
return name;
}
}
8.5、小结
xml与注解
- xml更万能适合于任何场合,维护方便;
- 注解不是自己的类不能使用,维护比较复杂;
xml与注解最佳实践
- xml用来管理bean;
- 注解只负责属性的注入;
- 使用过程中,必须要让注解生效,就需要开启注解的支持;
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.zhang"/>
<!--开启注解支持-->
<context:annotation-config/>
9、使用Java的方式配置spring
不使用spring的xml配置,全权交给Java。
JavaConfig原本是spring的一个子项目,spring4以后变成核心功能。
实体类:
//@Component仍然代表这个类被spring托管了,注册到Spring容器中了
@Component
public class User {
@Value("张三")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类:
//这个类也会被spring托管,注册到容器中,因为它本来就是@Component
//Configuration代表这是一个配置类,它对应的就相当于原来的ApplicationContext.xml(beans.xml)等价
@ComponentScan("com.zhang.pojo")
@Configuration
@Import(Config2.class)
public class Config {
//注册一个bean,就相当于之前的bean标签
//这个方法的名字就相当于bean标签中的id属性
//这个方法的返回值就相当于bean标签的class属性
@Bean
public User getUser(){
return new User();
}
}
@Bean 可以用于通过方法获取数据库连接池Connection这种对象
注意:1. 如果开启包扫描,加载配置类以后就可以通过反射拿到配置类中的对象了
2. @Bean只写在方法上,返回的是一个对象,但一般不获取已经在容器中的对象
测试:
public class MyTest {
@Test
public void t1(){
//如果完全使用了配置类的方法,我们就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
User user = (User) context.getBean("getUser");
System.out.println(user.getName());
}
}
10、代理模式
代理模式是SpringAOP的底层
代理模式的分类:
- 静态代理
- 动态代理
10.1、静态代理
角色:
- 抽象角色:一般使用接口或抽象类来解决
- 真实角色:被代理的对象
- 代理角色:代理真实角色后,会做一些附属性操作
- 客户:访问代理对象的人
测试:
1、接口
public interface Rent {
public void rent();
/* public void rent1();
public void rent2();*/
}
2、真实角色
public class Landlord implements Rent {
//真实对象,房东即被代理者
@Override
public void rent() {
System.out.println("我是房东,我想出租房子!");
}
/*@Override
public void rent1() {
System.out.println("我是房东2,我也想出租房子");
}
@Override
public void rent2() {
System.out.println("我是房东3,我也想出租房子");
}*/
}
3、代理角色
public class Poxy implements Rent{
private Landlord landlord;
public Poxy(Landlord landlord) {
this.landlord = landlord;
}
public Poxy(){
}
@Override
public void rent() {
this.visit();
landlord.rent();
this.contract();
this.IntermediaryFees();
}
/*@Override
public void rent1() {
this.visit();
landlord.rent1();
this.contract();
this.IntermediaryFees();
}
@Override
public void rent2() {
this.visit();
landlord.rent2();
this.contract();
this.IntermediaryFees();
}
*/
//其他公共业务
public void visit(){
System.out.println("中介带我参观房子!");
}
public void contract(){
System.out.println("中介让我签合同!");
}
public void IntermediaryFees(){
System.out.println("支付中介费!");
}
}
4、客户访问代理角色
public class Client{
public static void main(String[] args) {
//房东想出租房子
Landlord landlord = new Landlord();
//进行代理,通过中介帮房东出租,然后还会有代理的附加操作
Poxy poxy = new Poxy(landlord);
//你不需要去找房东,直接去找中介就好
poxy.rent();
/* poxy.rent1();
poxy.rent2();
*/
}
}
代理模式的好处:
- 可以使真实角色的操作更加纯粹,只需要做分内之事即可,不用操心公共业务;
- 公共业务交给代理,实现了业务的分工;
- 公共业务发生拓展的时候,方便集中管理;
缺点:一个真实角色就会产生一个代理角色 代码量翻倍效率低
10.2、深入理解静态代理模式
业务模块示例:
1、接口:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
2、实现类:
public class UserServiceImp implements UserService{
@Override
public void add() {
System.out.println("添加");
}
@Override
public void delete() {
System.out.println("删除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void query() {
System.out.println("查询");
}
}
3、代理实现类:
//代理
public class UserPoxy implements UserService {
//代理UserService
private UserServiceImp userServiceImp;
public UserPoxy(){}
public UserPoxy(UserServiceImp userServiceImp) {
this.userServiceImp = userServiceImp;
}
@Override
public void add() {
log("add");
userServiceImp.add();
}
@Override
public void delete() {
log("delete");
userServiceImp.delete();
}
@Override
public void update() {
log("update");
userServiceImp.update();
}
@Override
public void query() {
log("query");
userServiceImp.query();
}
//附加业务输出日志
public void log(String str){
System.out.println("方法"+str+"正在执行---");
}
}
4、客户调用代理:
public class Client {
public static void main(String[] args) {
UserServiceImp userServiceImp = new UserServiceImp();
UserPoxy userPoxy = new UserPoxy(userServiceImp);
userPoxy.add();
}
}
AOP机制:
10.3、动态代理
- 动态代理和静态代理角色是一样的
- 动态代理的代理类是动态生成的,不是我们直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。
- 基于接口:JDK动态代理(使用的就是这个)
- 基于类:cglib
- Java字节码实现:Javasist
需要了解两个类:Proxy、InvocationHandler
Proxy:生成得到代理类
//生成得到代理类 newProxyInstance代理实例
public Object getProxy() {
return
Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
InvocationHandler:处理代理实例返回结果
//处理代理实例并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理机制的实现就是使用反射实现
Object result = method.invoke(rent, args);
return result;
}
动态代理优点:
- 可以使真实角色的操作更加纯粹,只需要做分内之事即可,不用操心公共业务;
- 公共业务交给代理,实现了业务的分工;
- 公共业务发生拓展的时候,方便集中管理;
- 一个动态代理类代理的是一个接口,一般对应着一个业务类;
- 一个动态代理类可以动态代理多个真实类,只要是实现了同一个接口即可;
11、AOP
11.1、什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高
了开发的效率。
(1)面向切面编程(方面),利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
(2)也就是:不通过修改源代码方式,在主干功能里面添加新功能
(3)了解一些关键词:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
- 切面(Aspect):横切关注点 被模块化 的特殊对象。即,它是一个类。
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
- 目标(Target):被通知对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
Weaving
(织入):将Aspect
和其他对象连接起来, 并创建Advice
d object 的过程
11.2、使用Spring实现AOP
使用AOP织入需要导包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.9</version>
</dependency>
11.2.1、方式一:使用Spring的API接口
日志包中建前置通知类和后置通知类:
前置通知:
//前置日志,方法前置通知
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//args:参数
//target:目标对象
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
}
}
后置通知:
//后置日志通知
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法。返回了"+returnValue);
}
}
业务接口及实现类:
接口:
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
实现:
public class UserServiceImp implements UserService{
@Override
public void add() {
System.out.println("添加");
}
@Override
public void delete() {
System.out.println("删除");
}
@Override
public void update() {
System.out.println("修改");
}
@Override
public void select() {
System.out.println("查询");
}
}
ApplicationContext.xml 注册bean及配置aop、设置切入点、执行环绕增加
<?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: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
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="log" class="com.zhang.log.Log"/>
<bean id="userService" class="com.zhang.service.UserServiceImp"/>
<bean id="afterLog" class="com.zhang.log.AfterLog"/>
<!--方式一:使用原生Spring API接口-->
<!--配置aop: 需要导入aop约束-->
<aop:config>
<!--切入点:pointcut id是给切入点起的名字
expression:表达式
execution(需要执行的位置 *修饰词 *返回值 *类名 *方法名 *参数)
*(所有的修饰词) com.zhang.service.UserServiceImp(这个类).*(这个类下的所有方法)(..)(参数)
-->
<aop:pointcut id="pointcut" expression="execution(* com.zhang.service.UserServiceImp.*(..))"/>
<!--执行环绕增加 advisor环绕-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//动态代理代理的是接口,不是实现类(注意)
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
11.2.2、方式二:自定义实现AOP
新建DiyPointCut自定义切面类:
public class DiyPointCut {
public void Before(){
System.out.println("-----方法执行前-----");
}
public void After(){
System.out.println("-----方法执行后-----");
}
}
注册bean后:
<!--方式二:自定义实现AOP-->
<aop:config>
<!--自定义切面: ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.zhang.service.UserServiceImp.*(..))"/>
<!--通知-->
<aop:before method="Before" pointcut-ref="point"/>
<aop:after method="After" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
11.2.3、方式三:使用注解实现AOP
新建AnnotationPointCut类:
//方式三:使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.zhang.service.UserServiceImp.*(..))")
public void before(){
System.out.println("----执行方法前----");
}
@After("execution(* com.zhang.service.UserServiceImp.*(..))")
public void after(){
System.out.println("----执行方法后----");
}
//在环绕增加中,我们可以给定一个参数,代表我们要获取处理切入的点
@Around("execution(* com.zhang.service.UserServiceImp.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
Signature signature = jp.getSignature();//获得签名
System.out.println("签名:"+signature);
Object proceed = jp.proceed();//执行方法
System.out.println("环绕后");
}
}
配置xml:
<!--方式三:使用注解-->
<!--注册bean-->
<bean id="annotationPointCut" class="com.zhang.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
顺序:
12、整合MyBatis
步骤:
1、导jar包:
junit
mybatis
mysql数据库
spring相关
aop织入
mybatis-spring
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!--spring操做数据库需要导:spring-jdbc-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.18</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.1.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
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核心配置文件-->
<configuration>
<typeAliases>
<package name="com.zhang.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.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="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.zhang.mapper.UserMapper"/>
</mappers>
</configuration>
UserMapper.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.zhang.mapper.UserMapper">
<select id="selectUser" resultType="user">
select *from mybatis.user
</select>
</mapper>
3、测试
提取工具类:
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用Mybatis获取sqlSession对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//自动提交事务设置
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
}
test:
@Test
public void t1(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.selectUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
12.1、整合方式一:
1、编写配置
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核心配置文件-->
<configuration>
<typeAliases>
<package name="com.zhang.pojo"/>
</typeAliases>
<!-- 设置放这里就好-->
</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的配置 c3p0 dbcp druid
这里使用spring的jdbc:rg.springframework.jdbc.datasource
-->
<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?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--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/zhang/mapper/UserMapper.xml"/>
</bean>
<!--SqlSessionTemplate:就是我们用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器方法注入sqlSessionFactory-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<!--注册实现类-->
<bean id="userMapper" class="com.zhang.mapper.UserMapperImp">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
</beans>
2、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/zhang/mapper/UserMapper.xml"/>
</bean>
3、sqlSessionTmplate
<!--SqlSessionTemplate:就是我们用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--只能使用构造器方法注入sqlSessionFactory-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
4、需要给接口加实现类
public class UserMapperImp implements UserMapper{
//原来我们所有的操作都使用SqlSession执行,现在使用SqlSessionTemplate
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
5、将实现类注入spring中
<!--注册实现类-->
<bean id="userMapper" class="com.zhang.mapper.UserMapperImp">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
6、测试
public void t1(){
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.selectUser()) {
System.out.println(user);
}
}
12.2、整合方式二
让接口实现类去继承SqlSessionDaoSupport
SqlSessionDaoSupport
是一个抽象的支持类,用来为你提供 SqlSession
。调用 getSqlSession()
方法你会得到一个 SqlSessionTemplate
,之后可以用于执行 SQL 方法
例:
public class UserMapperImpTwo extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
}
注册:给其父类属性注册sqlSessionFactory
<bean id="userMapper2" class="com.zhang.mapper.UserMapperImpTwo">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
在这个类里面,通常更倾向于使用 MapperFactoryBean
,因为它不需要额外的代码。但是,如果你需要在 DAO 中做其它非 MyBatis 的工作或需要一个非抽象的实现类,那么这个类就很有用了。
SqlSessionDaoSupport
需要通过属性设置一个 sqlSessionFactory
或 SqlSessionTemplate
。如果两个属性都被设置了,那么 SqlSessionFactory
将被忽略。
13、声明式事物
13.1、回顾事务:
- 要么都成功要么都失败
- 事务在开发中涉及数据一致性,非常重要
- 确保完整性与一致性
事务ACID原则:
- 原子性Atomicity
- 一致性Consistency
- 隔离性Isolation (多个业务可能操作同一个资源,防止数据损坏)
- 持久性Durability (事务一旦提交,无论系统发生什么问题,结果都不会受到影响,被持久化写入)
13.2、spring中的事务
- 声明式事务管理:AOP切面、不用改原有代码的。
- 编程式事务管理:需要在代码中修改,进行管理。
声明式事务配置:
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--结合AOP实现事务的织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给实现类里的方法配置事务-->
<!--配置事务的传播特性:propagation默认是REQUIRED
REQUIRED:支持当前事务,如果没有当前事务就新建一个事务,最常用也是默认的
-->
<tx:attributes>
<tx:method name="addUser" propagation="REQUIRED"/>
<tx:method name="deleteUser" propagation="REQUIRED"/>
<tx:method name="query" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.zhang.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
接口再加两个方法:
public class UserMapperImpTwo extends SqlSessionDaoSupport implements UserMapper{
@Override
public List<User> selectUser() {
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
User user = new User(8, "老八", "88888");
addUser(user);
deleteUser(8);
return mapper.selectUser();
}
@Override
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
这时事务就开启了,出现错误就无法插入。