1.简介
- spring是一种用于简化复杂的企业开发的轻量级框架(从目的来说),是一种轻量级IOC(控制反转)和AOP(面向切面)的容器框架
- spring官网 Spring | Home
- spring官方中文文档官网 https://www.docs4dev.com/docs/zh/spring-framework/
软件开发原则
-
ocp开闭原则
在软件开发过程中应当对扩展开放(open),对修改关闭(close)。上图一旦下层改动,那么上层也要改动,同时也违背了另一个·原则 依赖倒置原则
-
依赖倒置原则(DIP)
-
依赖倒置原则(Dependence Inversion Principle),简称DIP,
-
主要倡导面向抽象编程,面向接口编程,不要面向具体编程,让上层不再依赖下层,下面改动了,上面的代码不会受到牵连。
-
这样可以大大降低程序的耦合度,耦合度低了,扩展力就强了,同时代码复用性也会增强。(软件七大开发原则都是在为解耦合服务)
- 软件开发的七大原则包括:
-
-
-
-
- 单一职责原则(SRP):一个类应该只有一个引起它变化的原因。
- 开闭原则(OCP):软件实体应当对扩展开放,对修改关闭。
- 里氏替换原则(LSP):子类对象应该能够替换父类对象而不影响程序的正确性。
- 接口隔离原则(ISP):不应该强迫客户端依赖它不需要的接口。
- 依赖倒置原则(DIP):高层模块不应该依赖低层模块,两者都应该依赖抽象。
- 迪米特法则(LoD):一个对象应该对其他对象有尽可能少的了解。
- 合成复用原则(CRP):尽量使用对象组合,而不是继承。12
-
-
2.Spring8大模块(组成)
注意:Spring5版本之后是8个模块。在Spring5中新增了WebFlux模块。
- Spring Core模块 (ioc控制反转)也是容器
- 这是Spring框架最基础的部分,它提供了依赖注入(DependencyInjection)特征来实现容器对Bean的管理。
- 核心容器的主要组件是 BeanFactory,BeanFactory是工厂模式的一个实现,是任何Spring应用的核心。
- 它使用IoC将应用配置和依赖从实际的应用代码中分离出来。
- Spring Context模块 (上下文)
- spring 的 ioc 容器也是程序呀,那它的执行也肯定需要依赖一个上下文
-
如果说核心模块中的BeanFactory使Spring成为容器的话,那么上下文模块就是Spring成为框架的原因。
-
这个模块扩展了BeanFactory,增加了对国际化(I18N)消息、事件传播、验证的支持。
-
另外提供了许多企业服务,例如电子邮件、JNDI访问、EJB集成、远程以及时序调度(scheduling)服务。
-
也包括了对模版框架例如Velocity和FreeMarker集成的支持
- Spring AOP模块(面向切面编程)
- Spring在它的AOP模块中提供了对面向切面编程的丰富支持,Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。
- 通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中,可以自定义拦截器、切点、日志等操作。
- Spring DAO模块
- 提供了一个JDBC的抽象层和异常层次结构,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析,用于简化JDBC
- Spring ORM模块
- Spring提供了ORM模块。Spring并不试图实现它自己的ORM解决方案,而是为几种流行的ORM框架提供了集成方案,
- 包括Hibernate、JDO和iBATIS SQL映射,这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web MVC模块
- Spring为构建Web应用提供了一个功能全面的MVC框架。
- 虽然Spring可以很容易地与其它MVC框架集成,例如Struts,但Spring的MVC框架使用IoC对控制逻辑和业务对象提供了完全的分离。
- Spring WebFlux模块
-
Spring Framework 中包含的原始 Web 框架 Spring Web MVC 是专门为 Servlet API 和 Servlet 容器构建的。
-
反应式堆栈 Web 框架 Spring WebFlux 是在 5.0 版的后期添加的。
-
它是完全非阻塞的,支持反应式流(Reactive Stream)背压,并在Netty,Undertow和Servlet 3.1+容器等服务器上运行。
-
- Spring Web模块
- Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文,提供了Spring和其它Web框架的集成,比如Struts、WebWork。
- 还提供了一些面向服务支持,例如:实现文件上传的multipart请求。
2.1 ioc理论推导
- UserDao 接口
- UserDaoImpl 实现类
- UserService 业务接口
- UserServcie 业务实现类
就是这个样子
在我们之前的业务在,用户需求可能会影响我们原来的代码 我们需要根据用户的需求修改原代码!
如果程序代码量很大,修改一次成本很高
我们使用一个Set接口实现:不同需求的动态注入
private UserDao userDao; public void setUserDao(UserDao userDao){ this.userDao =userDao; }
- 之前,程序式主动创建对象!控制权在程序员手上
- 使用set注入后,程序不再具有主动性,而是变成被动的接收对象
这种思想,从本质上解决问题,程序员不用在去管理对象的创建了。系统的耦合性大大降低,可以更加专注在业务的实现上 这是ioc的原型
2.2 ioc本质
控制反转ioc,是一种设计思想,DI(依赖注入)是实现ioc的一种方法
没有ioc的程序,我们使用面向对象编程,对象的创建与对象之间的依赖关系完全硬编码在程序中
对象的创建由程序自己控制,控制反转后将对象的创建交给第三方
ioc是spring框架的核心内容
使用多种方式完美的实现ioc
- 可以使用xml配置
- 也可以使用注解
- spring也可以零配置实现ioc
spring容器在初始化时先读取配置文件,根据配置文件或者元数据创建与组织对象存入容器中,程序使用再从ioc容器取需要的对象
采用xml方式配置bean的时候,bean的定义信息是和实现分离的,采用注解的方式可以把二者结合为一体
bean的定义信息直接以注解的形式在实体类中,从而达到零配置的目的
控制反转时一种通过描述(xnl或者注解)并通过的三种方式去生产会或者获取特定对象的方式
在spring中实现控制反转的ioc容器,其实现方法时依赖注入(DI)
3.Spring特点
- 轻量
- 大小开支都是轻量级。完整spring框架大小只有1mb
- Spring是非入侵式的:Spring应用中的对象不依赖Spring特定的类
- 控制反转
- 通过控制反转(ioc)的技术促进解耦
- 一个对象依赖的其他对象会通过被动的方式传递进来,而不是这个对象自己创建或查找依赖对象
- 面向切面
- 允许通过分离应用的业务逻辑与系统级服务进行内聚性开发
- 容器
- spring包含了并管理应用对象的配置和生命周期,在这个意义上他是个容器
- 你可以配置你的每个bean如何被创建
- 框架
- spring可以将简单的组件配置,组合成复杂的应用。例:好多配置文件都可以在写一个xml中
- spring也提供了很多基础功能(事务管理,持久化框架集成等),将应用逻辑开发留给我们
4.入门sprint
- 创建一个maven项目
- 导入sprint的依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.0.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.0.RELEASE</version> </dependency>
- 编写实体类
public class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("你好"+name ); } }
- 编写我们的spring文件 , 这里我们命名为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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean就是java对象 , 由Spring创建和管理--> <bean id="zhao" class="com.zhao.pojo.User"> <property name="name" value="赵可明"/> </bean> </beans>
- 进行测试
@Test public void test(){ //解析beans.xml文件 , 生成管理相应的Bean对象 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");//可以有多个xml文件 //getBean : 参数即为spring配置文件中bean的id . User hello = (User) context.getBean("zhao"); hello.show(); }
思考:
- Hello 对象是谁创建的 ? hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的 ? hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
- 控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
- 反转 : 程序本身不创建对象 , 而变成被动的接收对象 .
依赖注入 : 就是利用set方法来进行注入的.
案例1
我们在案例一中, 新增一个Spring配置文件beans.xml
<bean id="MysqlImpl" class="com.zhao.dao.UserDaoMysqlImpl"/> <bean id="OracleImpl" class="com.zhao.dao.UserDaoOracleImpl"/> <bean id="ServiceImpl" class="com.zhao.service.UserServiceImpl"> <!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写--> <!--引用另外一个bean , 不是用value 而是用 ref--> <property name="userDao" ref="OracleImpl"/> </bean>
测试!
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl"); serviceImpl.getUser(); }
OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !
5.IOC创建对象方式
实体类
private String name; public String getName() { return name; } public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("你好"+name); }
在解析beans.xml文件的时候里面对应的bean就已经创建好了
@org.junit.Test public void test5(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); }
在只有解析beans.xml文件对应的无参构造已经出现在控制台里
所以这里不是网上有的人讲的在getBean的时候创建的
5.1.通过无参构造方法来创建
- 创建实体类
private String name; public String getName() { return name; } public User() { System.out.println("user无参构造方法"); } public void setName(String name) { this.name = name; } public void show(){ System.out.println("你好"+name); }
- 在xml配置文件中写对应的bean
<!--bean就是java对象 , 由Spring创建和管理--> <bean id="zhao" class="com.zhao.pojo.User"> <property name="name" value="赵可明"/> </bean>
-
测试 @org.junit.Testpublic void test4(){
//解析文件的时候已经创建好了对应的user ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //获取user User user = (User) context.getBean("zhao"); //调用对象的方法 . user.show(); } - 结果 可以发现,在调用show方法之前,User对象已经通过无参构造初始化了!
user无参构造方法 你好赵可明
5.2.通过有参构造方法来创建
- 实体类
public class UserT { private String name; public UserT(String name) { this.name = name; } public void setName(String name) { this.name = name; } public void show(){ System.out.println("name="+ name ); } }
- 编写xml文件中对应的bean 有三种方式编写
<!-- 第一种根据index参数下标设置--> <bean id="userT" class="com.zhao.pojo.UserT"> <!-- index指构造方法 , 下标从0开始 --> <constructor-arg index="0" value="赵可明2"/> </bean> <!-- 第二种根据参数名字设置 --> <bean id="userT" class="com.zhao.pojo.UserT"> <!-- name指参数名 --> <constructor-arg name="name" value="赵可明2"/> </bean> <!-- 第三种根据参数类型设置 不建议用会有多个相同参数 --> <bean id="userT" class="com.zhao.pojo.UserT"> <constructor-arg type="java.lang.String" value="赵可明2"/> </bean>
- 测试
@org.junit.Test public void test5(){ //在解析beans.xml文件的时候已经获取所有的对象了 ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); //在执行getBean的时候 获取user UserT userT = (UserT) context.getBean("userT"); //调用对象的方法 . userT.show(); }
- 结果
-
<!-- bean就是java对象 , 由Spring创建和管理--> <bean id="zhao" class="com.zhao.pojo.User"> <property name="name" value="赵可明"/> </bean> <bean id="zhao1" class="com.zhao.pojo.Zhao"> <property name="zhao" value="赵可明3"/> </bean>
- 上面可以看到在beans.xml文件中创建几个bean 解析对应xml文件的时候就会生成几个对象
- 再一次验证对象实在解析beans.xml文件就全部创建了
6.Spring配置
1.别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取--> <alias name="userT" alias="userNew"/>
2.Bean的配置
<!--bean就是java对象,由Spring创建和管理--> <!-- id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符 如果配置id,又配置了name,那么name是别名 name可以设置多个别名,可以用逗号,分号,空格隔开 如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象; class是bean的全限定名=包名+类名 --> <bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello"> <property name="name" value="Spring"/> </bean>
7.依赖注入
一般用于团队开发使用,他将多个配置文件,导入合并为一个
beans.xml是不同人开发的
applicationContext.xml为中配置文件
将其他人的导入总配置文件需要在中只需要在总配置文件中写入
概念
-
依赖注入(Dependency Injection,DI)。
-
依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
-
注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
1.构造器注入 (重点在每个bean)
我们在之前的案例已经讲过了 5.那个有参构造器无参构造器注入
2.set注入【重点】在每个bean里面 值注入和拓展注入
- 依赖注入 set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有属性,有容器来注入
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .
3.拓展注入 p命名和c命名注入
测试pojo类 :
package com.zhao.pojo; public class Address { private String address; @Override public String toString() { return "Address{" + "address='" + address + '\'' + '}'; } public void setAddress(String address) { this.address = address; } }
package com.zhao.pojo; import java.util.*; 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 void setName(String name) { this.name = name; } public void setAddress(Address address) { this.address = address; } public void setBooks(String[] books) { this.books = books; } public void setHobbys(List<String> hobbys) { this.hobbys = hobbys; } public void setCard(Map<String, String> card) { this.card = card; } public void setGames(Set<String> games) { this.games = games; } public void setWife(String wife) { this.wife = wife; } public void setInfo(Properties info) { this.info = info; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", address=" + address.toString() + ", books=" + Arrays.toString(books) + ", hobbys=" + hobbys + ", card=" + card + ", games=" + games + ", wife='" + wife + '\'' + ", info=" + info + '}'; } }
测试xml文件bean:
<bean id="address" class="com.zhao.pojo.Address"> <property name="address" value="西安"/> </bean> <bean id="student" class="com.zhao.pojo.Student"> <!--普通值注入--> <property name="name" value="赵可明"/> <!--bean注入--> <property name="address" ref="address"/> <!--数组注入--> <property name="books"> <array> <value>西游记</value> <value>西游记1</value> <value>西游记2</value> </array> </property> <!--list注入--> <property name="hobbys"> <list> <value>吃饭</value> <value>看电视</value> <value>敲代码</value> </list> </property> <!--map注入--> <property name="card"> <map> <entry key="身份证" value="111213212312313211"/> <entry key="银行卡" value="11564654651313132"/> </map> </property> <!--Set注入--> <property name="games"> <set> <value>lol</value> <value>吃鸡</value> <value>cs</value> </set> </property> <!--Null注入--> <property name="wife" > <null/> </property> <!--Properties注入--> <property name="info"> <props> <prop key="学号">2301004514</prop> <prop key="电话号">175849325</prop> </props> </property> </bean>
测试结果:
Student{name='赵可明', address=Address{address='西安'}, books=[西游记, 西游记1, 西游记2], hobbys=[吃饭, 看电视, 敲代码], card={身份证=111213212312313211, 银行卡=11564654651313132}, games=[lol, 吃鸡, cs], wife='null', info={电话号=175849325, 学号=2301004514}}
3.3.拓展注入 p命名和c命名注入
User.java :【注意:这里没有有参构造器!】
package com.zhao.pojo; public class User { private String name; private int age; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
- P命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p" <!--P(属性: properties)命名空间 , 直接注入属性--> <bean id="user" class="com.zhao.pojo.User" p:name="赵小帅" p:age="18"/>
- c 命名空间注入 : 需要在头文件中加入约束文件
User.java :需要添加构造器
public User() { } public User(String name, int age) { this.name = name; this.age = age; }
<!--C(构造: Constructor)命名空间 , 使用构造器注入--> <bean id="user1" class="com.zhao.pojo.User" c:name="赵小帅2" c:age="19"/>
测试代码:
@org.junit.Test public void test1(){ ApplicationContext Context = new ClassPathXmlApplicationContext("UserBeans.xml"); User user = (User) Context.getBean("user"); String string = user.toString(); System.out.println(string); }
4.bean的作用域(scope)
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
Singleton(单例模式)spring默认模式:
- 当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,
- 并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
- Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。
- 注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
测试:
@org.junit.Test public void test(){ ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) Context.getBean("student"); Student student1 = (Student) Context.getBean("student"); System.out.println(student==student1); }
结果为rtue
获取两个相同的bean但是用的同一个
Prototype(原型模式):
- 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。
- Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。
- Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
- 根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>
测试:
@org.junit.Test public void test(){ ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml"); Student student = (Student) Context.getBean("student"); Student student1 = (Student) Context.getBean("student"); System.out.println(student==student1); }
结果为false
其他的在web开发中用到
-
Request
- 当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;
- 即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。
- 该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
-
Session
- 当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。
- 该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
8.bean的自动装配
自动装配说明
- 自动装配是使用spring满足bean依赖的一种方法
- spring会在应用上下文中为某个bean寻找其依赖的bean。
Spring中bean有三种装配机制,分别是:
- 在xml中显式配置;
- 在java中显式配置;
- 隐式的bean发现机制和自动装配
这里我们主要讲第三种:自动化的装配bean。
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置 , 而使用注解 .
8.1测试环境搭建:
- 创建一个新项目
- 新建两个实体类,Cat Dog 都有一个叫的方法
public class Dog { public void shout() { System.out.println("wang~"); } } public class Cat { public void shout() { System.out.println("miao~"); } }
- 新建一个用户类 User
private Cat cat; private Dog dog; private String str; public void setCat(Cat cat) { this.cat = cat; } public void setDog(Dog dog) { this.dog = dog; } public void setStr(String str) { this.str = str; } public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getStr() { return str; } @Override public String toString() { return "User{" + "cat=" + cat + ", dog=" + dog + ", str='" + str + '\'' + '}'; }
- 编写Spring配置文件
<?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="dog" class="com.zhao.pojo.Dog"/> <bean id="cat" class="com.zhao.pojo.Cat"/> <bean id="user" class="com.zhao.pojo.User"> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> <property name="str" value="赵可明"/> </bean> </beans>
- 测试
public void test(){ ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml"); User user = (User) Context.getBean("user"); user.getCat().shout(); user.getDog().shout(); System.out.println(user); }
- 结果
miao~ wang~ User{cat=com.zhao.pojo.Cat@206a70ef, dog=com.zhao.pojo.Dog@292b08d6, str='赵可明'}
结果正常输出,环境OK
8.2.byName
- autowire byName (按名称自动装配)
-
- 由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
- 采用自动装配将避免这些错误,并且使配置简单化。
测试:
1、修改bean配置,增加一个属性 autowire=“byName”
<bean id="user" class="com.zhao.pojo.User" autowire="byName"> <!-- <property name="cat" ref="cat"/>--> <!-- <property name="dog" ref="dog"/>--> <property name="str" value="赵可明"/> </bean>
2、 我们把对应的cat 和dog bean注释了 再次测试,结果依旧成功输出!
3、我们将 cat 的bean id修改为 catXXX
4、再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。
-
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
-
去spring容器中寻找是否有此字符串名称id的对象。
-
如果有,就取出注入;如果没有,就报空指针异常。
8.3.byType
- autowire byType (按类型自动装配)
- 使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
-
NoUniqueBeanDefinitionException
测试:
- 将user的bean配置修改一下 : autowire=“byType”
- 测试,正常输出
- 在注册一个cat 的bean对象!
<bean id="dog" class="com.zhao.pojo.Dog"/> <bean id="cat" class="com.zhao.pojo.Cat"/> <bean id="cat2" class="com.zhao.pojo.Cat"/>
-
- 删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
<bean id="dog" class="com.zhao.pojo.Dog"/> <bean id="cat5454" class="com.zhao.pojo.Cat"/> <!-- <bean id="cat2" class="com.zhao.pojo.Cat"/>-->
- 结果
这就是按照类型自动装配!
8.4.使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作:利用注解的方式注入属性。
- 1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- 2、开启属性注解支持!
<!--开启属性注解支持--> <context:annotation-config/>
在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: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
- @Autowired是按类型自动转配的,不支持id匹配。
- 需要导入 spring-aop的包!
测试:
- 将User类中的set方法去掉,使用@Autowired注解
@Autowired private Cat cat; @Autowired private Dog dog; private String str; public Cat getCat() { return cat; } public Dog getDog() { return dog; } public String getStr() { return str; }
- 此时配置文件内容
<bean id="dog" class="com.zhao.pojo.Dog"/> <bean id="cat5454" class="com.zhao.pojo.Cat"/> <bean id="user" class="com.zhao.pojo.User"/>
- 测试,成功输出结果!
miao~ wang~ com.zhao.pojo.User@17a7f733
@Autowired(required=false) 说明:false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true @Autowired(required = false) private Cat cat;
@Qualifier
- @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
- @Qualifier不能单独使用。
测试实验步骤:
- 配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog2" class="com.zhao.pojo.Dog"/> <bean id="dog1" class="com.zhao.pojo.Dog"/> <bean id="cat2" class="com.zhao.pojo.Cat"/> <bean id="cat1" class="com.zhao.pojo.Cat"/> <bean id="user" class="com.zhao.pojo.User"/>
- 没有加Qualifier测试,直接报错
- 在属性上添加Qualifier注解
@Autowired @Qualifier(value = "cat2") private Cat cat; @Autowired @Qualifier(value = "dog2") private Dog dog;
测试,成功输出!
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
实体类:
public class User { //如果允许对象为null,设置required = false,默认为true @Resource(name = "cat2") private Cat cat; @Resource private Dog dog; private String str; }
beans.xml
<bean id="dog1" class="com.zhao.pojo.Dog"/> <bean id="cat2" class="com.zhao.pojo.Cat"/> <bean id="cat1" class="com.zhao.pojo.Cat"/> <bean id="user" class="com.zhao.pojo.User"/>
测试:结果OK
配置文件2:beans.xml , 删掉cat2
<bean id="dog" class="com.zhao.pojo.Dog"/> <bean id="cat1" class="com.zhao.pojo.Cat"/>
实体类上只保留注解
@Resource private Cat cat; @Resource private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
在JDK11版本以后,javax.annotation这个包被移除了,所以无法使用@resource注解。
解决办法在pom文件中引入以下依赖即可:
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.3.2</version> </dependency>
小结
@Autowired与@Resource异同:
1、@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
2、@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,
如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,
如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。
如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。
当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
9.使用注解开发
在spring4之后,想要使用注解形式,必须得要引入aop的包
在applicationContext.xml配置文件当中,还得要引入一个context约束
<?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"> </beans>
9.1.Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
- 配置扫描哪些包下的注解
<!--方法一指定注解扫描包--> <context:component-scan base-package="com.zhao.pojo"/>
- 在指定包下编写类,增加注解
// 相当于配置文件中 <bean id="user" class="当前注解的类"/> //Component=组件 @Component("user") public class User { public String name = "赵可明"; }
- 测试
@org.junit.Test public void test(){ ApplicationContext Context = new ClassPathXmlApplicationContext("applicationContext.xml"); User user = (User) Context.getBean("user"); System.out.println(user.name); }
9.8.属性注入
使用注解注入属性
1、可以不用提供set方法,直接在直接名上添加@value(“值”)
// 相当于配置文件中 <bean id="user" class="当前注解的类"/> //Component=组件 @Component("user") public class User { @Value("赵可明") // 相当于配置文件中 <property name="name" value="赵可明"/> public String name; }
2、如果提供了set方法,在set方法上添加@value(“值”);
public String name; @Value("赵可明") public void setName(String name) { this.name = name; }
9.3.衍生注解
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:controller层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
9.4.自动装配注解
在Bean的自动装配已经讲过了,可以回顾!
9.5.作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user") @Scope("prototype") public class User { @Value("赵可明") public String name; }
9.6.小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<!--开启自动装配-->
<context:annotation-config/>
作用:
-
进行注解驱动注册,从而使注解生效
-
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
-
如果不扫描包,就需要手动配置bean
-
如果不加注解驱动,则注入的值为null!
10.基于Java类进行配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
测试:
1、编写一个实体类,
@Component //将这个类标注为Spring的一个组件,放到容器中! 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 + '\'' + '}'; } }
2、新建一个config配置包,编写一个Config配置类
@Configuration //代表这是一个配置类 public class ZhaoConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public User user(){ return new User(); } }
3、测试
@org.junit.Test public void test2(){ ApplicationContext Context = new AnnotationConfigApplicationContext(ZhaoConfig.class); User user = (User) Context.getBean("user"); System.out.println(user.getName()); }
4、成功输出结果!
导入其他配置如何做呢?
1、我们再编写一个配置类!
public class Zhao2Config { }
2、在之前的配置类中我们来选择导入这个配置类
@Configuration //代表这是一个配置类 @Import(Zhao2Config.class) //D导入合并其他配置文件,类似于配置文件中的inculde标签 public class ZhaoConfig { @Bean //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id! public User user(){ return new User(); }
关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可!
11.代理模式
为什么要学习代理模式,因为AOP的底层机制就是动态代理!
代理模式:
- 静态代理
- 动态代理
学习aop之前 , 我们要先了解一下代理模式!
11.1静态代理
静态代理角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
-
客户 : 使用代理角色来进行一些操作 .
代码实现 demo1
Rent . java 即抽象角色
//抽象角色:租房 public interface Rent { public void rent(); }
Host . java 即真实角色
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
Proxy . java 即代理角色
//代理角色:中介 public class Proxy implements Rent { private Host host; public Proxy() { } public Proxy(Host host) { this.host = host; } //租房 public void rent(){ seeHouse(); host.rent(); fare(); } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }
Client . java 即客户
//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东要租房 Host host = new Host(); //中介帮助房东 Proxy proxy = new Proxy(host); //你去找中介! proxy.rent(); } }
代码实现 demo2
1、创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象对象 public interface UserService { public void add(); public void delete(); public void update(); public void select(); }
2、我们需要一个真实对象来完成这些增删改查操作
//真实对象 public class UserServiceImpl 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("查看了一个数据"); } }
3、需求来了,现在我们需要增加一个日志功能,怎么实现!
- 思路1 :在实现类上增加代码 【麻烦!一般公司项目很大不建议修改原有的代码】
- 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
- 设置一个代理类来处理日志!代理角色
//代理角色 在这里面实现日志功能
public class UserServiceProxy implements UserService{ private UserServiceImpl userService; public void setUserService(UserServiceImpl userService) { this.userService = userService; } @Override public void add() { log("add"); userService.add(); } @Override public void delete() { log("delete"); userService.delete(); } @Override public void update() { log("update"); userService.update(); } @Override public void select() { log("select"); userService.select(); } //日志 public void log(String msg){ System.out.println("使用了"+msg+"方法"); } }
4、测试访问类:
//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //房东 UserServiceImpl userService =new UserServiceImpl(); //中介 UserServiceProxy userServiceProxy =new UserServiceProxy(); userServiceProxy.setUserService(userService); userServiceProxy.add(); } }
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
静态代理的好处:
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
缺点 :
- 类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !
聊聊AOP:纵向开发,横向开发 AOP的实现机制
11.2动态代理
-
动态代理的角色和静态代理的一样 .
-
动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
-
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
- 基于接口的动态代理----JDK动态代理
- 基于类的动态代理–cglib
- 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
- 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
【InvocationHandler接口:调用处理程序】
里面有一个invoke方法
Object invoke(Object proxy, 方法 method, Object[] args); //参数 //proxy - 调用该方法的代理实例 //method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。 //args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
//生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this); }
代码实现
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色
public interface Rent { public void rent(); }
Host . java 即真实角色
//真实角色: 房东,房东要出租房子 public class Host implements Rent{ public void rent() { System.out.println("房屋出租"); } }
ProxyInvocationHandler. java 即代理角色
package com.zhao.demo03; public class ProxyInvocationHandler implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色 public Object getProxy(){ return Proxy.newProxyInstance( this.getClass().getClassLoader(),//当前线程的上下文ClassLoader rent.getClass().getInterfaces(), //代理需要实现的接口 this); // 处理器自身 } // proxy : 代理类 // method : 代理类的调用处理程序的方法对象. // 处理代理实例上的方法调用并返回结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { seeHouse(); //核心:本质利用反射实现! Object result = method.invoke(rent, args); fare(); return result; } //看房 public void seeHouse(){ System.out.println("带房客看房"); } //收中介费 public void fare(){ System.out.println("收中介费"); } }
Client . java
//客户类,一般客户都会去找代理! public class Client { public static void main(String[] args) { //真实代理角色 Host host =new Host(); //代理对象的调用处理程序(代理角色) ProxyInvocationHandler proxyInvocationHandler =new ProxyInvocationHandler(); //抽象角色需要的代理的真实角色 proxyInvocationHandler.setRent(host); //动态生成代理类! Rent rent = (Rent) proxyInvocationHandler.getProxy(); rent.rent(); } }动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!
- 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
- 公共的业务由代理来完成 . 实现了业务的分工 ,
- 公共业务发生扩展时变得更加集中和方便 .
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,代理的是接口!
12.AOP
12.1.什么是AOP
- AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
12.2.Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …(添加的需求)
- 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。(对应的具体实现类)
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。(类中具体的方法)
- 目标(Target):被通知对象。(原有的类)
- 代理(Proxy):向目标对象应用通知之后创建的对象。(加入新需求的类)
- 切入点(PointCut):切面通知 执行的 “地点”的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .
12.3.使用Spring实现Aop
【重点】使用AOP织入,需要导入一个依赖包!
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
12.3.1.通过 Spring API 实现
第一种方式
- 首先编写我们的业务接口和实现类
public interface UserService { public void add(); public void delete(); public void update(); public void search(); } public class UserServiceImpl 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 search() { System.out.println("查询用户"); } }
- 然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice { //method : 要执行的目标对象的方法 //objects : 被调用的方法的参数 //Object : 目标对象 @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了"); } } public class AfterLog implements AfterReturningAdvice { //returnValue 返回值 //method被调用的方法 //args 被调用的方法的对象的参数 //target 被调用的目标对象 @Override public void afterReturning(Object returnValue, Method method, Object[] args,Object target) throws Throwable { System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法," +"返回值:"+returnValue); } }
最后去spring的 applicationContext.xml文件中注册 , 并实现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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--注册bean--> <bean id="userServiceImpl" class="com.zhao.demo05.UserServiceImpl"/> <bean id="log" class="com.zhao.demo05.Log"/> <bean id="afterLog" class="com.zhao.demo05.AfterLog"/> <!--aop的配置--> <aop:config> <!--切入点:你添加需求的地方 expression:表达式匹配要执行的方法--> <aop:pointcut id="pointcut" expression="execution(* com.zhao.demo05.UserServiceImpl.*(..))"/> <!--执行环绕:意思是把log这个类切入到pointcut; advice-ref执行方法 . pointcut-ref切入点--> <aop:advisor advice-ref="log" pointcut-ref="pointcut"/> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/> </aop:config> </beans>
- 测试
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userServiceImpl", UserService.class); userService.search(); } }
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
12.3.2.自定义类来实现Aop第二种方式
- 目标业务类不变依旧是userServiceImpl
- 第一步 : 写我们自己的一个切入类
public class DiyPointcut { public void before(){ System.out.println("---------方法执行前---------"); } public void after(){ System.out.println("---------方法执行后---------"); } }
- 去spring中配置
<!--第二种方式自定义实现--> <!--注册bean--> <bean id="diy" class="com.zhao.demo05.DiyPointcut"/> <!--aop的配置--> <aop:config> <!--第二种方式:使用AOP的标签实现--> <aop:aspect ref="diy"> <aop:pointcut id="diyPonitcut" expression="execution(* com.zhao.demo05.UserServiceImpl.*(..))"/> <aop:before pointcut-ref="diyPonitcut" method="before"/> <aop:after pointcut-ref="diyPonitcut" method="after"/> </aop:aspect> </aop:config>
- 测试
public class MyTest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService) context.getBean("userService", UserService.class); userService.search(); } }
- 第一步 : 写我们自己的一个切入类
第三种方式
- 第一步:编写一个注解实现的增强类
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; @Aspect public class AnnotationPointcut { @Before("execution(* com.kuang.service.UserServiceImpl.*(..))") public void before(){ System.out.println("---------方法执行前---------"); } @After("execution(* com.kuang.service.UserServiceImpl.*(..))") public void after(){ System.out.println("---------方法执行后---------"); } @Around("execution(* com.kuang.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint jp) throws Throwable { System.out.println("环绕前"); System.out.println("签名:"+jp.getSignature()); //执行目标方法proceed Object proceed = jp.proceed(); System.out.println("环绕后"); System.out.println(proceed); } }
- 第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现--> <bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/> <aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了 <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
13.整合MyBatis
1、导入相关jar包
- junit
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
- mybatis
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency>
- mysql-connector-java
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
- spring相关
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.1.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency>
- aspectJ AOP 织入器
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
- mybatis-spring整合包 【重点】
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>
- 配置Maven静态资源过滤问题!
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
2、编写配置文件mybatis-config.xml
3、代码实现
13.1回忆MyBatis
- 编写pojo实体类
package com.zhao.pojo; public class User { private int id; //id private String name; //姓名 private String pwd; //密码 }
- 实现mybatis的核心配置文件
<?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.zhao.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://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="zhao"/> </dataSource> </environment> </environments> <mappers> <package name="com.zhao.mapper"/> </mappers> </configuration>
- UserDao接口编写
public interface UserMapper { public List<User> selectUser(); }
- 接口对应的Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhao.mapper.UserMapper"> <select id="selectUser" resultType="com.zhao.pojo.User"> select * from user </select> </mapper>
- 测试类
public class MyTest { @Test public void selectUser() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.selectUser(); for (User user: userList){ System.out.println(user); } sqlSession.close(); } }
13.2.MyBatis-Spring学习
http://www.mybatis.org/spring/zh/index.html
什么是 MyBatis-Spring?
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中
知识基础:
在开始使用 MyBatis-Spring 之前,你需要先熟悉 Spring 和 MyBatis 这两个框架和有关它们的术语。这很重要
MyBatis-Spring 需要以下版本:
MyBatis-Spring | MyBatis | Spring 框架 | Spring Batch | Java |
---|---|---|---|---|
2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
如果使用 Maven 作为构建工具,仅需要在 pom.xml 中加入以下代码即可:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>
- 要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。
- 在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。
- 在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
- 在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。
- SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
- 一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。
- 需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。
- SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。
- SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。
- 模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。
- 在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。
- 可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
- 现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:
public class UserDaoImpl implements UserDao { private SqlSession sqlSession; public void setSqlSession(SqlSession sqlSession) { this.sqlSession = sqlSession; } public User getUser(String userId) { return sqlSession.getMapper...; } }
- 按下面这样,注入 SqlSessionTemplate:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl"> <property name="sqlSession" ref="sqlSession" /> </bean>
13.3.整合实现一
- 1、引入Spring配置文件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 http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
- 2、配置数据源替换mybaits的数据源 要在mybatis的核心配置文件中删除对应的数据库源
<!--配置数据源:数据源有非常多,可以使用第三方的,也可使使用Spring的--> <!--配置数据源:把mybatis的连接数据库配置写在这 变成一个bean可以随时用--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="zhao"/> </bean>
- 3、配置SqlSessionFactory,关联MyBatis
<!--配置SqlSessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--获取数据库连接--> <property name="dataSource" ref="dataSource"/> <!--关联Mybatis:就是把mybatis的核心配置文件的里面的东西写在这--> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!--Mybatis中的mapper映射--> <property name="mapperLocations" value="classpath:com/zhao/mapper/UserMapper.xml"/> </bean>
- 4、注册sqlSessionTemplate,关联sqlSessionFactory;
<!--注册sqlSessionTemplate , 关联sqlSessionFactory--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--利用构造器注入--> <!--这能用构造器注入因为没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
- 5、增加Mapper接口的实现类;私有化sqlSessionTemplate
//完成对应创建sqlSession public class UserMapperImpl implements UserMapper{ //sqlSession不用我们自己创建了,Spring来管理 private SqlSessionTemplate sqlSession; @Override public List<User> selectUser() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } }
- 6、注册bean实现
<!--创建成userMapper bean 里面带有sqlSession --> <bean id="userMapper" class="com.zhao.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
- 上面在xml中的步骤意义
<!--上面这些把之前mybatis 获取sqlSession的代码都写成一个个bean放在ioc容器中 //使用mybatis第一步获取SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); //使用SqlSessionFactory创建sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); //在代码中只需要获取userMapper bean就可以 --> 就是一一对应mybatis中获取sqlSession的过程这个不过是写成bean
- 7、测试
@Test public void selectUser() { //扫描beans.xml创建bean ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml"); //获取对应的mapper //userMapper bean中就有sqlSession UserMapper userMapper = (UserMapper) Context.getBean("userMapper"); List<User> users = userMapper.selectUser(); for (User user:users){ System.out.println(user); } }
结果成功输出!现在我们的Mybatis配置文件的状态!发现都可以被Spring整合!
<?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.kuang.pojo"/> </typeAliases> </configuration>
13.4.整合实现二
mybatis-spring1.2.3版以上的才有这个 .
mapper继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看
测试:
1、创建UserMapperImpl2 继承SqlSessionDaoSupport就不用私有化sqlSessionTemplate 简化了
//直接获取了mapper public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper { public List<User> selectUser() { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.selectUser(); } }
2、创建userMapper2 bean的配置
<bean id="userMapper2" class="com.zhao.mapper.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
3、测试
@Test public void selectUser2() { //扫描beans.xml创建bean ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml"); //获取对应的mapper //userMapper bean中就有sqlSession UserMapper userMapper = (UserMapper) Context.getBean("userMapper2"); List<User> users = userMapper.selectUser(); for (User user:users){ System.out.println(user); } }
总结 : 整合到spring以后可以完全不要mybatis的配置文件,除了这些方式可以实现整合之外,我们还可以使用注解来实现,这个等我们后面学习SpringBoot的时候还会测试整合!
14.声明式事务
14.1.回顾事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。
事务四个属性ACID
- 原子性(atomicity)
-
- 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性(consistency)
-
- 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
-
隔离性(isolation)
- 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
-
持久性(durability)
- 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
例子
在之前的案例中,我们给userDao接口新增两个方法,删除和增加用户;
//添加一个用户 int addUser(User user); //根据id删除用户 int deleteUser(int id);
mapper文件,我们故意把 deletes 写错,测试!
<insert id="addUser" parameterType="com.kuang.pojo.User"> insert into user (id,name,pwd) values (#{id},#{name},#{pwd}) </insert> <delete id="deleteUser" parameterType="int"> deletes from user where id = #{id} </delete>
编写接口的实现类,在实现类中,我们去操作一波
public class UserDaoImpl extends SqlSessionDaoSupport implements UserMapper { //增加一些操作 public List<User> selectUser() { User user = new User(4,"小明","123456"); UserMapper mapper = getSqlSession().getMapper(UserMapper.class); mapper.addUser(user); mapper.deleteUser(4); return mapper.selectUser(); } //新增 public int addUser(User user) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.addUser(user); } //删除 public int deleteUser(int id) { UserMapper mapper = getSqlSession().getMapper(UserMapper.class); return mapper.deleteUser(id); } }
测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }
报错:sql异常,delete写错了
结果 :插入成功!
没有进行事务的管理;我们想让他们都成功才成功,有一个失败,就都失败,我们就应该需要事务!
以前我们都需要自己手动管理事务,十分麻烦!
但是Spring给我们提供了事务管理,我们只需要配置即可;
14.2.Spring中的事务管理
Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式的事务管理。
编程式事务管理
- 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
- 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
声明式事务管理
- 一般情况下比编程式事务好用。
- 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
- 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。
使用Spring管理事务,注意头文件的约束导入 : tx
xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
事务管理器
- 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。
- 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
JDBC事务
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
配置好事务管理器后我们需要去配置事务的通知
<!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <!--配置哪些方法使用什么样的事务,配置事务的传播特性--> <tx:method name="add" propagation="REQUIRED"/> <tx:method name="delete" propagation="REQUIRED"/> <tx:method name="update" propagation="REQUIRED"/> <tx:method name="search*" propagation="REQUIRED"/> <tx:method name="get" read-only="true"/> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
spring事务传播特性
事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:
- propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
- propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
- propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
- propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
- propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
- propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。
假设 ServiveX#methodX() 都工作在事务环境下(即都被 Spring 事务增强了),
假设程序中存在如下的调用链:Service1#method1()->Service2#method2()->Service3#method3(),
那么这 3 个服务类的 3 个方法通过 Spring 的事务传播机制都工作在同一个事务中。
就好比,我们刚才的几个方法存在调用,所以会被放在一组事务当中!
配置AOP
导入aop的头文件!
<!--配置aop织入事务--> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* com.kuang.dao.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config>
进行测试
@Test public void test2(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); UserMapper mapper = (UserMapper) context.getBean("userDao"); List<User> user = mapper.selectUser(); System.out.println(user); }
14.3.思考问题?
为什么需要配置事务?
- 如果不配置,就需要我们手动提交控制事务;
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
标签:xml,name,Spring,void,bean,public From: https://www.cnblogs.com/zhao-ke-ming/p/18357915