Spring5学习笔记
1、Spring
1.1、简介
- 2002年,首次退出Spring框架的雏形:interface21框架
- Spring框架即以interface21框架为基础,经过重新设计,并不断丰富其内涵。与2004年3月24日发布了1.0正式版
- Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架!
SSH:Struct2 + Spring + Hibernate
SSM:SpringMVC + Spring + Mybatis
1.2、优点
-
Spring是一个开源的免费的框架(容器)
-
Spring是一个轻量级的、非入侵式的框架!
-
控制反转(IOC)、面向切面编程(AOP)
-
支持事务的处理,可以整合其他框架
Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.3、组成
1.4、扩展
- Spring Boot
- 一个快速开发的脚手架
- 基于Spring Boot可以快速开发单个的微服务
- 约定大于配置
- Spring Cloud
- SpringCloud是基于SpringBoot实现的
学习SpringBoot的前提:Spring SpringMVC
1.5、功能
- 轻 量 级 - Spring 在 代 码 量 和 透 明 度 方 面 都 很 轻 便 。
- IOC - 控 制 反 转
- AOP - 面 向切 面 编 程 可 以 将 应 用 业 务 逻 辑 和 系 统 服 务 分 离 ,以 实 现 高 内 聚 。
- 容 器 - Spring 负责 创 建 和 管 理 对 象 ( Bean) 的 生 命 周 期 和 配 置 。
- MVC - 对 web 应 用 提 供 了 高度 可 配 置 性 ,其 他 框 架 的 集 成 也 十 分 方 便 。
- 事 务 管 理 - 提 供 了 用 于 事 务 管 理 的 通用 抽 象 层 。 Spring 的 事 务 支 持 也 可 用 于 容 器 较 少 的 环 境 。
- JDBC 异 常 - Spring的 JDBC 抽 象 层 提 供 了 一 个 异 常 层 次 结 构 , 简 化 了 错 误 处 理 策 略 。
2、IOC理论推导
2.1、IOC
- UserDao接口
public interface UserDao {
void getUser();
}
- UserDaoImpl实现类
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("默认获取用户的数据");
}
}
- UserService 业务接口
public interface UserService {
void getUser();
}
- UserServiceImpl 业务实现类
public class UserServiceImpl implements UserService {
private UserDao userDao ;
// 利用set进行动态实现值的注入
public void setUser(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
- 测试
public class Mytest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUser(new UserDaoImpl());
userService.getUser();
((UserServiceImpl) userService).setUser(new UserDaoMysqlImpl());
userService.getUser();
((UserServiceImpl) userService).setUser(new UserDaoOracleImpl());
userService.getUser();
}
}
输出:
默认获取用户的数据
MySQL获取用户数据
Oracle获取用户数据
- 使用set注入后,程序不在具有主动性,而是变成了被动的接受对象
- 这种思想,从本质上解决了问题,我们程序员不用再去管理对象的创建。系统的耦合性大大降低,可以更加专注的在业务的实现上。
- IOC的原型
2.2、IOC本质
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法。没有IoC的程序中,我们使用面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为所谓的控制反转就是:获得依赖对象的方式反转了。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的IoC容器,其实现方法是依赖注入(Dependency Injection ,DI)
2.3、HelloSpring
public class Hello {
private String str;
public String getStr() {
return str;
}
// 没有这个方法,无法在Bean中设置默认值
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
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">
<!--使用Spring来创建对象,在Spring这些都称为Bean
类型 变量名 = new 类型()
Hello hello = new Hello()
id = 变量名
class = new 的对象
property 相当于给对象中的属性设置一个值
-->
<bean id="hello" class="com.gmt.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
- Hello 对象是谁创建的?
hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的?
hello 对象的属性是由Spring容器设置的
这个过程就是控制反转:
-
控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的
-
反转:程序本身不创建对象,而变成被动的接受对象
-
依赖注入:就是利用set方法来进行注入
-
IoC是一种编程思想,由主动地编程变成被动的接受
小结:
要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IoC:对象由Spring创建、管理、装配
4、IoC创建对象的方式
-
使用有参构造创建对象,默认
-
假设我们要使用有参构造创建对象
- 下标赋值
<--第一种,通过下标来赋值--> <bean id="user" class="com.gmt.pojo.User"> <constructor-arg index="0" value="gmt"/> </bean>
- 类型(不建议使用)
<!--第二种,通过类型创建,不建议使用--> <bean id="user" class="com.gmt.pojo.User"> <constructor-arg type="java.lang.String" value="gmt"/> </bean>
因为如果有多个相同类型的值,那么constructor-arg的value值必须和构造函数的传参顺序一一对应。
比如构造函数
public User(String email , String name){}
那么bean传参的顺序就必须得是如下这样
<bean id="user" class="com.gmt.pojo.User"> <constructor-arg type="java.lang.String" value="213432@qq.com"/> <constructor-arg type="java.lang.String" value="gmt"/> </bean>
- 参数名
<!--第三种,通过参数名来设置--><bean id="user" class="com.gmt.pojo.User"> <constructor-arg name="name" value="gmt"/></bean>
总结
- 在配置文件加载的时候,Spring容器中管理的对象Bean就已经初始化了
5、Spring配置
5.1、别名
<bean id="user" class="com.gmt.pojo.User">
<constructor-arg name="name" value="gmt"/>
</bean>
<!--别名,如果添加了别名,我们也可以通过别名来获取这个Bean-->
<alias name="user" alias="testName"/>
5.2、Bean的配置
<!--
id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean 对象所对应的全限定名:包名+类型
name:也是别名,相当于alias,而且name可以取多个别名,可以使用逗号,空格,分号来分割
-->
<bean id="userT" class="com.gmt.pojo.UserT" name="user2,u2 u3;u4">
<property name="name" value="gmt"></property>
</bean>
5.3、import
一般用于团队开发,可以将多个配置文件,导入合并成为一个
假设现在项目中有多个人开发, 这三个人负责不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的。
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
5.4、ApplicationContext
作用:是一个在BeanFactory基础上封装了更多功能的、Spring中最为常用的IoC的容器。
ApplicationContext是由BeanFactory派生而来。
主要实现类:
ClassPathXmlApplicationContext
:从类路径加载配置文件FileSystemXmlApplicationContext
:从文件系统中的XML文件载入上下文定义信息XmlWebApplicationContext
:从Web系统中的XML文件载入上下文定义信息
6、DI依赖注入
6.1、构造器注入
详见4
6.2、Set方式注入【重点】
- 依赖注入:Set注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中所有的属性,由容器来注入
【环境搭建】
- 复杂类型
public class Address { private String address; public String getAddress() { return address; } public void setAddress(String address) { this.address = address; }}
- 真实测试对象
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;
}
- 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="student" class="com.gmt.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="张三"/>
</bean>
</beans>
- 测试类
public class Mytest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getName());
}
}
- 完善注入信息
<?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.gmt.pojo.Address">
<property name="address" value="南京"></property>
</bean>
<bean id="student" class="com.gmt.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="张三"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--String[] 数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>三体</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--List<String> -->
<property name="hobbys">
<list>
<value>听歌</value>
<value>看电影</value>
<value>滑冰</value>
<value>篮球</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="123153545"/>
<entry key="银行卡" value="12458765"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!--null-->
<property name="wife">
<null></null>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="学号">2021201420</prop>
<prop key="性别">男</prop>
<prop key="姓名">李四</prop>
</props>
</property>
</bean>
</beans>
6.3、拓展方式注入
1、p命名空间
- 导入p-namespace头文件
xmlns:p="http://www.springframework.org/schema/p"
- 构建实体类
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);
}
}
- 编写xml文件
<-- p命名空间注入,可以直接注入属性的值:property -->
<bean id="user" class="com.gmt.pojo.User" p:name="张三"/>
- 测试
@Testpublic void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user", User.class);
user.show();
}
2、c命名空间
- 导入c-namespace头文件
xmlns:c="http://www.springframework.org/schema/c"
- 构建实体类
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show (){
System.out.println("姓名:"+name);
System.out.println("年龄:"+age);
}
}
- 编写xml文件
<--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.gmt.pojo.User" c:name="李四" c:age="12"/>
- 测试
@Testpublic void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user2",User.class);
user.show();
}
3、注意点
p命名空间和c命名空间不能直接使用,需要导入xml约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
6.4、Bean的作用域
1. 单例模式(Spring默认机制)
<--singleton:单例模式 scope:作用域 -->
<bean id="user2" class="com.gmt.pojo.User" c:name="李四" c:age="12" scope="singleton"/>
@Testpublic void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user2",User.class);
User user1 = context.getBean("user2",User.class);
System.out.println(user==user1); // true
}
2. 原型模式
每次从容器中getBean的时候,都会创建一个新对象
<bean id="user2" class="com.gmt.pojo.User" c:name="李四" c:age="12" scope="prototype"/>
@Testpublic void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = context.getBean("user2",User.class);
User user1 = context.getBean("user2",User.class);
System.out.println(user==user1); // false
}
7、Bean的自动装配
- 自动装配就是让应用程序上下文为你找出依赖项的过程。
- 说的通俗一点,就是Spring会在上下文中自动查找,并自动给bean装配与其关联的属性!
在Spring中有 三种装配的方式:
- 在xml中显式的配置【手动装配】
- 在java中显式的配置
- 隐式的自动装配bean【重要】
7.1、测试环境搭建
public class Cat {
public void shout(){
System.out.println("miao");
}
}
public class Dog {
public void shout(){
System.out.println("wang");
}
}
public class People {
private Cat cat;
private Dog dog;
private String name;
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "People{" +
"cat=" + cat +
", dog=" + dog +
", name='" + name + '\'' +
'}';
}
}
手动装配
<bean id="cat" class="com.gmt.pojo.Cat"></bean><bean id="dog" class="com.gmt.pojo.Dog"></bean><bean id="people" class="com.gmt.pojo.People"> <property name="name" value="张三"></property> <property name="dog" ref="dog"></property> <property name="cat" ref="cat"></property></bean>
7.2、ByName自动装配
<bean id="cat" class="com.gmt.pojo.Cat"></bean><bean id="dog" class="com.gmt.pojo.Dog"></bean><!--byName:会在容器上下文中查找,和自己对象set方法后面的值对应的beanid,且第一个字母必须小写--><bean id="people" class="com.gmt.pojo.People" autowire="byName"> <property name="name" value="张三"></property></bean>
7.3、ByType自动装配
<bean class="com.gmt.pojo.Cat"></bean><bean class="com.gmt.pojo.Dog"></bean><!--byType:会在容器上下文中查找,和自己对象属性类型相同的bean,可以不写id,但是只能有同类型中只能有一个bean--><bean id="people" class="com.gmt.pojo.People" autowire="byType"> <property name="name" value="张三"></property></bean>
小结:
- byName必须保证bean的id和set方法后边的值一致,并且第一个字母为小写
- byType必须保证bean的类型唯一,不能同时存在两个相同类型的bean,这个是通过class类型来查找的,所以id可以不写
7.4、使用注解实现自动装配
注意点:
- 引入约束: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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解--> <context:annotation-config/></beans>
1. @Autowired
直接在属性上使用即可,也可以在set方法上使用。
在属性上使用@Autowired时,可以不用写set方法。前提是自动装配的属性在IoC容器中存在,且符合类型byType。
装配策略
- 默认按照类型装配byType
- 类型相同按照名称装配byName
- 通过byName和byType还是无法确定的话,还有两种方式
- Primary:它的作用是看Bean上是否包含@Primary注解,如果包含就返回。不同把多个Bean都设置为@Primary,不然你会得到
NoUniqueBeanDefinitionException
这个异常。 - Priority:可以再Bean上配置@Priority注解,他有个int类型的属性值value,可以配置优先级大小。数字
- Primary:它的作用是看Bean上是否包含@Primary注解,如果包含就返回。不同把多个Bean都设置为@Primary,不然你会得到
@Nullable // 字段标记了这个注解,说明这个字段可以为null
// 如果显式定义了Autowired的required属性为false,说明这个对象可以为null,否则不允许为空@Autowired(required = false)
// 通过指定Bean的id,来指定唯一一个Bean对象的注入@Qualifier("xxx")
public class People { @Qualifier("cat123") @Autowired(required = false) private Cat cat; @Qualifier("dog123") @Autowired private Dog dog; private String name;}
2. @Resource
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入罢了。
@Resource装配策略
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常 2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常 3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常 4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
3. 区别
@Autowired 与@Resource的区别:
1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2、 @Autowired默认按类型装配(这个注解是属于spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
@Autowired()@Qualifier("baseDao")private BaseDao baseDao;
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource(name="baseDao")private BaseDao baseDao;
8、使用注解开发
1、MVC和三层架构的区别
三层架构是软件体系架构设计中的一种架构模式,它可适用于任何一个项目。MVC是子系统或软件系统框架提炼抽象中的一种设计模式,它是根据项目的具体需求来决定是否适用于该项目。
一、概念的不同
- 三层架构
三层架构(3-tier application) ,通常意义上的三层架构就是将整个业务应用划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。
(1)表现层(UI,User Interface Layer):通俗讲就是展现给用户的界面,即用户在使用一个系统的时候他的所见所得。
(2)业务逻辑层(BLL,Business Logic Layer):针对具体问题的操作,也可以说是对数据层的操作,对数据业务逻辑处理。
(3)数据访问层(DAL,Data Access Layer):该层所做事务直接操作数据库,针对数据的增添、删除、修改、更新、查找等。
在软件体系架构设计中,分层的目的即为了“高内聚,低耦合”的思想,分层式结构是最常见,也是最重要的一种结构,微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或称为领域层)、表示层。
- MVC
MVC(Model View Controller),通常意义上的MVC框架就是将整个应用架构的表现层划分为:模型(Model)、视图(View)、控制器(Controller)。
(1)模型(Model):模型表示企业数据和业务规则,主要负责处理业务逻辑,用来储存数据的组件(与领域模型概念不同,两者会相互交叉),提供数据,数据之间的关系、转化等,并可以通知视图和控制器自己哪些地方发生了变化。
(2)视图(View):视图是用户看到并与之交互的界面,主要负责显示数据和提交数据,能根据m的改变来更新自己。
(3)控制器(Controller):控制器接受用户的输入并调用模型和视图去完成用户的需求,主要是用作辅助捕获请求并控制请求转发,比如视图做了点击一个按钮,会先发给这个视图的控制器,然后这个控制器来决定做什么操作(让模型更新数据,控制视图改变)。
MVC是一种设计模式即GUI界面开发的指导模式,MVC可以与三层架构共存,把三层架构中的表现层再度进行了分化,分成了控制器、视图、实体三个部分,呈三角形结构。
二、具体应用的不同
三层架构和MVC同属于架构级别的,相同的地方在于他们都有一个表现层,但是他们不同的地方在于其他的两个层。三层是基于业务逻辑来分的,主要用于体系架构;而MVC是基于页面来分的,主要用于表现层。
1、控制器,在三层架构中没有定义Controller这个概念,这也是最不同的地方,而MVC也没有把业务的逻辑访问看成两个层,这是采用三层架构和MVC搭建框架最主要的区别。控制器完成页面逻辑,确保M和V的同步,通过实体来与用户界面完成通话,一旦M改变,V应该同步更新;而控制器直接与三层中的BLL进行对话。
2、Model,在三层中同样也提到了Model,但是三层架构中Model的概念与MVC中Model的概念是不一样的,“三层”中典型的Model层是以实体类构成的,而MVC里则是由业务逻辑与访问数据组成的。
3、分离,存在单向引用,例如Model不知道View和Controller的存在,View不知道Controller的存在,这就隔离了表现和数据。View和controller是单向引用,而实际中View和Controller也是有数据交互的。
(1)模型与控制器和视图相分离
模型是自包含的,所以很容易改变应用程序的数据和业务规则。
(2)View和数据(Model)的分离
使用不同的View对相同的数据进行展示;分离可视和不可视的组件,能够对Model进行独立测试,因为分离了可视组件减少了外部依赖利于测试,实际上数据库也是一种外部组件。
(3)View和表现逻辑(Controller)的分离
Controller是一个表现逻辑的组件,并非一个业务逻辑组件,分离逻辑和具体展示,能够对逻辑进行独立测试。
4、模式,三层架构是分层架构模式,是典型的上下关系,上层依赖于下层;但MVC作为表现设计模式是不存在上下关系的,而是相互协作关系,MVC是一个复合模式,一种解决方案,MV和MC都是观察者模式、M内部是组合模式、VC之间是策略模式(可以随时更换不同的控制器)。本质上,MVC和三层架构没有可比性,是应用于不同领域的一种模式,三层架构和MVC设计模式侧重点不一样,三层是一种笼统的架构思想,没有限制具体的设计;而MVC就比较具体的说明它的设计方法 。
2、注解开发
在spring4之后,使用注解开发必须保证aop的包导入了。
- applicationContext.xml配置导入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解-->
<context:annotation-config/>
<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.gmt"/>
</beans>
- 属性注入
// 等价于 <bean id="user" class="com.gmt.pojo.User"/>
// @Component 组件
@Component
public class User {
// 等价于 <property name="name" value="张三"/>
@Value("张三")
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- 衍生的注解
@Component有几个衍生的注解,我们在web开发中,会按照三层架构分层。
- dao【@Repository】
- service【@Service】
- controller【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean。
- 自动装配
@Autowired
@Resource
@Nullable
- 作用域
@Scope("singleton") // singleton 单例模式,prototype 原型模式
9、代理模式
9.1、什么是代理模式
代理模式的定义:代理模式给某一个对象提供一个代理对象,并有代理对象控制对原对象的引用。通俗的来讲代理模式就是中介。
9.2、为什么要用代理模式
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能:
- 代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
- 代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
开闭原则:对于扩展是开放的,对于修改是封闭的。
9.3、有哪几种代理模式
- 静态代理:是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class就已经被创建了。
- 动态代理:是在程序运行时通过反射机制动态创建的。
9.3.1、静态代理
第一步:创建服务类接口
public interface BuyHouse {
void buyHouse();
}
第二步:实现服务类接口
// 委托类,委托给中介,我要买房子
public class BuyHouseImpl implements BuyHouse{
@Override
public void buyHouse() {
System.out.println("我要买房");
}
}
第三步:创建代理类
// 代理类,别人要买房子,我起到中介的作用
public class BuyHouseProxy implements BuyHouse {
private BuyHouse buyHouse;
public BuyHouseProxy(BuyHouse buyHouse) {
this.buyHouse = buyHouse;
}
@Override
public void buyHouse() {
System.out.println("买房前准备");
buyHouse.buyHouse();
System.out.println("买房后装修");
}
}
第四步:编写测试类
public class Mytest {
@Test
public void test(){
BuyHouse buyHouse = new BuyHouseImpl();
buyHouse.buyHouse();
BuyHouseProxy buyHouseProxy = new BuyHouseProxy(buyHouse);
buyHouseProxy.buyHouse();
}
}
总结:
- 优点:可以在符合开闭原则的情况先对目标对象进行功能扩展。
- 缺点:我们得为每一个服务创建代理类,工作量大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
9.3.2、动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
第一步:编写动态处理器
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(Object object){
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买房前准备");
Object result = method.invoke(object,args);
System.out.println("买房后装修");
return result;
}
}
第二步:编写测试类
public class Mytest {
@Test
public void test(){
BuyHouse buyHouse = new BuyHouseImpl();
BuyHouse buyHouseProxy = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(),
new Class[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));
buyHouseProxy.buyHouse();
}
}
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader
:指定当前目标对象使用的类加载器,获取加载器的方法是固定的Class<?>[] interfaces
:指定目标对象实现的接口的类型,使用泛型方式确认类型InvocationHandler:
指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
10、AOP
10.1、什么是AOP
【百度百科】AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
10.2、AOP相关概念
Aspect
(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 AdviceJoint point
(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。Pointcut
(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。Advice
(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。Target
(目标对象):织入 Advice 的目标对象.。Weaving
(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
10.3、例子
AOP 中 Aspect
, Joint point
, Pointcut
与 Advice
之间的关系.
让我们来假设一下, 从前有一个叫爪哇的小县城, 在一个月黑风高的晚上, 这个县城中发生了命案. 作案的凶手十分狡猾, 现场没有留下什么有价值的线索. 不过万幸的是, 刚从隔壁回来的老王恰好在这时候无意中发现了凶手行凶的过程, 但是由于天色已晚, 加上凶手蒙着面, 老王并没有看清凶手的面目, 只知道凶手是个男性, 身高约七尺五寸. 爪哇县的县令根据老王的描述, 对守门的士兵下命令说: 凡是发现有身高七尺五寸的男性, 都要抓过来审问. 士兵当然不敢违背县令的命令, 只好把进出城的所有符合条件的人都抓了起来.
在 Spring AOP 中 Joint point
指代的是所有方法的执行点, 而 Pointcut
是一个描述信息, 它修饰的是 Joint point
, 通过 Pointcut
, 我们就可以确定哪些 Joint point
可以被织入 Advice
. 对应到我们在上面举的例子, 我们可以做一个简单的类比, Joint point
就相当于 爪哇的小县城里的百姓, Pointcut
就相当于 老王所做的指控, 即凶手是个男性, 身高约七尺五寸, 而 Advice
则是施加在符合老王所描述的嫌疑人的动作: 抓过来审问。
-
Joint point
: 爪哇的小县城里的百姓: 因为根据定义,Joint point
是所有可能被织入Advice
的候选的点, 在 Spring AOP中, 则可以认为所有方法执行点都是Joint point
. 而在我们上面的例子中, 命案发生在小县城中, 按理说在此县城中的所有人都有可能是嫌疑人. -
Pointcut
:男性, 身高约七尺五寸: 我们知道, 所有的方法(Joint point
) 都可以织入Advice
, 但是我们并不希望在所有方法上都织入Advice
, 而Pointcut
的作用就是提供一组规则来匹配Joint point
, 给满足规则的Joint point
添加Advice
. 同理, 对于县令来说, 他再昏庸, 也知道不能把县城中的所有百姓都抓起来审问, 而是根据凶手是个男性, 身高约七尺五寸, 把符合条件的人抓起来. 在这里 凶手是个男性, 身高约七尺五寸 就是一个修饰谓语, 它限定了凶手的范围, 满足此修饰规则的百姓都是嫌疑人, 都需要抓起来审问. -
Advice
:抓过来审问,Advice
是一个动作, 即一段 Java 代码, 这段 Java 代码是作用于Pointcut
所限定的那些Joint point
上的. 同理, 对比到我们的例子中, 抓过来审问 这个动作就是对作用于那些满足 男性, 身高约七尺五寸 的爪哇的小县城里的百姓。 -
Aspect
:Aspect
是Pointcut
与Advice
的组合, 因此在这里我们就可以类比: “根据老王的线索, 凡是发现有身高七尺五寸的男性, 都要抓过来审问” 这一整个动作可以被认为是一个Aspect
。
10.4、测试
public interface UserService { void add(); void update(); void delete(); void query();}
@Servicepublic class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void query() { System.out.println("查询了一个用户"); }}
public class Mytest { @Test public void test(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = context.getBean("userServiceImpl", UserService.class); userService.add(); }}
10.4.1、方式一:使用原生Spring API接口
@Componentpublic class AfterLog implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了"+method.getName()+"方法,返回结果为"+returnValue); }}
@Componentpublic class BeforeLog implements MethodBeforeAdvice { @Override public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(method.getClass().getName()+"的"+method.getName()+"方法被执行了"); }}
<?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" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--将包下的所有类装配到spring中--> <context:annotation-config/> <context:component-scan base-package="com.gmt"/> <aop:config> <!-- 切入点:expression:表达式 .*(..)表示该类下的任意方法任意参数 execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?) execution(修饰符匹配式? 返回类型匹配式 类名匹配式? 方法名匹配式(参数匹配式) 异常匹配式?) --> <aop:pointcut id="pointcut" expression="execution(* com.gmt.service.UserServiceImpl.*(..))"/> <aop:advisor pointcut-ref="pointcut" advice-ref="beforeLog"/> <aop:advisor pointcut-ref="pointcut" advice-ref="afterLog"/> </aop:config> </beans>
输出:
java.lang.reflect.Method的add方法被执行了
增加了一个用户
执行了add方法,返回结果为null
10.4.2、方式二:自定义类
@Componentpublic class DiyPointCut { public void before(){ System.out.println("执行方法前"); } public void after(){ System.out.println("执行方法后"); }}
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--将包下的所有类装配到spring中--> <context:annotation-config/> <context:component-scan base-package="com.gmt"/> <aop:config> <aop:aspect ref="diyPointCut"> <!--切入点--> <aop:pointcut id="point" expression="execution(* com.gmt.service.UserServiceImpl.*(..))"/> <aop:before method="before" pointcut-ref="point"/> <aop:after method="after" pointcut-ref="point"/> </aop:aspect> </aop:config></beans>
输出:
执行方法前
增加了一个用户
执行方法后
10.4.3、方式三:使用注解实现AOP
//@Aspect 标注这个类是一个切面@Aspect@Componentpublic class AnnotationPointCut { @Before("execution(* com.gmt.service.UserServiceImpl.*(..))") public void before(){ System.out.println("方法执行前"); } @After("execution(* com.gmt.service.UserServiceImpl.*(..))") public void after(){ System.out.println("方法执行后"); } @Around("execution(* com.gmt.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint pj)throws Throwable{ System.out.println("环绕前"); Object proceed = pj.proceed(); System.out.println(pj.getSignature()); System.out.println("环绕后"); }}
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--将包下的所有类装配到spring中--> <context:annotation-config/> <context:component-scan base-package="com.gmt"/> <!--开启注解支持--> <aop:aspectj-autoproxy/></beans>
输出
环绕前
方法执行前
增加了一个用户方法执行后
void com.gmt.service.UserService.add()
环绕后
10.5、execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)execution(修饰符匹配式? 返回类型匹配式 类名匹配式? 方法名匹配式(参数匹配式) 异常匹配式?)
代码块中带?
符号的匹配式都是可选的,对于execution PCD
必不可少的只有三个:
- 返回值匹配值
- 方法名匹配式
- 参数匹配式
11、整合Mybatis
- UserMapper
public interface UserMapper { List<User> selectAll();}
- 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"><!--configuration核心配置文件--><mapper namespace="com.gmt.mapper.UserMapper"> <select id="selectAll" resultType="user"> select * from mybatis.user </select></mapper>
- User
public class User { private String name; private String pwd; private int id; @Override public String toString() { return "User{" + "name='" + name + '\'' + ", pwd='" + pwd + '\'' + ", id=" + id + '}'; }}
- UserMapperImpl
public class UserMapperImpl implements UserMapper{ private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } @Override public List<User> selectAll() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectAll(); }}
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--DataSource:使用Spring的数据源替换Mybatis的配置--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://121.196.100.240:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="mybatis"/> <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-conf.xml"/> <property name="mapperLocations" value="classpath:com/gmt/mapper/*.xml"/> </bean> <!--SqlSessionTemplate:就是sqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--只能使用构造器注入,因为没有set方法--> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean> <bean id="userMapper" class="com.gmt.mapper.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean></beans>
- mybatis-conf.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.gmt"/> </typeAliases></configuration>