简介
Spring:春天—->给软件行业带来了春天!
2002,首次推出了 Spring 框架的雄性:interface21 框架!
Spring 框架即以 interface21 框架为基础,经过重新设计,并不断丰富其内涵,于 2004 年 3 月 24 日发布了 1.0 正式版
Rod Johnson Spring Framework 创始人,著名作者,很难想象 Rod Johnson 的学历,真的让好多人大吃一惊,他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学
Spring 理念:使现有的技术更加容易使用,本事是一个大杂烩,整合了现有的技术框架
SSH:Struct2 + Spring + Hibernate
SSM:SpringMVC + Spring + Mybatis
官网:https://spring.io/
Spring Framework 5.3.12 API:https://docs.spring.io/spring-framework/docs/current/javadoc-api/
Version 5.3.12:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#spring-core
官方下载地址:https://repo.spring.io/ui/native/release/org/springframework/spring/
1 2 3 4 5 6 7 8 9 10 11 12 13 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.3.12</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbk</artifactId > <version > 5.3.12</version > </dependency >
优点
Spring 是一个开源的免费的框架(容器)!
Spring 是一个轻量级的、非入侵式的框架!
控制反转(IOC),面向切面编程(AOP)
支持事务的处理,对框架的整合的支持 r
总结:开源免费容器,轻量级非侵入式,控制反转,面向切面,支持事务,支持框架整合
Spring 就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
组成
拓展 在 Spring 的官网有这个介绍:现代化的 Java 开发!也就是基于 Spring 的开发
Spring Boot
一个快速开发的脚手架
基于 SpringBoot 可以快速的开发单个微服务
约定大于配置
Spring Cloud
SpringCloud 是基于 SpringBoot 实现的
因为现在大多数公司都在使用 SpringBoot 进行快速开发,学习 SpringBoot 的前提,需要完全掌握 Spring 及 SpringMVC!承上启下的作用!
弊端:发展了太久之后,违背了原来的理念,配置十分繁琐,人称:“配置地狱”
IOC 理论推导 传统的调用
UserDao 接口 1 2 3 4 package dao;public interface UserDao { void getUser () ; }
UserDaoImpl 实现类 1 2 3 4 5 6 package dao;public class UserDaoImpl implements UserDao { public void getUser () { System.out.println("默认获取用户数据" ); } }
UserService 业务接口 1 2 3 4 5 package Service;public interface UserService { void getUser () ; }
UserServiceImpl 业务实现类 1 2 3 4 5 6 7 8 9 10 package Service;import dao.UserDao;import dao.UserDaoImpl;public class UserServiceImpl implements UserService { UserDao userDao = new UserDaoImpl (); public void getUser () { userDao.getUser(); } }
测试
1 2 3 4 5 6 7 8 9 10 11 package holle0;import Service.UserService;import Service.UserServiceImpl;public class MyTest0 { public static void main (String[] args) { UserService userService = new UserServiceImpl (); userService.getUser(); } }
在我们之前的业务中,用户的需求可能会影响我们原来的代码,我们需要根据用户的需求去修改原代码!如果程序代码量十分大,修改一次的成本代价十分昂贵!
改良:我们使用一个 Set 接口实现。已经发生了革命性的变化!
1 2 3 4 5 6 7 private UserDao userDao;public void setUserDao (UserDao userDao) { this .userDao = userDao; }
set() 方法实际上是动态改变了 UserDao userDao 的 初始化部分(new UserDaoImpl() )
测试中加上
1 ((UserServiceImpl)userService).setUserDao(new UserDaoImpl ());
之前,程序是主动创建对象!控制权在程序猿手上!
使用了 Set 注入后,程序不再具有主动性,而是变成了被动的接受对象!
本质上解决了问题,程序员不用再去管理对象的创建
系统的耦合性大大降低,可以更专注在业务的实现上
这是 IOC(控制反转)的原型,反转(理解):主动权交给了用户
IOC 本质 控制反转 IOC(Inversion of Control),是一种设计思想,ID(依赖注入)是实现 IOC 的一种方法
控制反转是一种通过描述(XML 或注解)并通过第三方去生产获取特定对象的方式,在 Spring 中实现控制反转的是 IOC 容器,其实方法是依赖注入
HelloSpring 在父模块中导入 jar 包
1 2 3 4 5 6 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.2.7.RELEASE</version > </dependency >
pojo 的 Hello.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package pojo;public class Hello { private String str; public String getStr () { return str; } public void setStr (String str) { this .str = str; } @Override public String toString () { return "Holle [str=" + str + "]" ; } }
在 resource 里面的 xml 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?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 ="hello" class ="pojo.Hello" > <property name ="str" value ="Spring" /> </bean > </beans >
测试类 MyTest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package holle1;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import pojo.Hello;public class MyTest { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); Hello holle = (Hello) context.getBean("hello" ); System.out.println(holle.toString()); } }
核心用 set 注入,所以必须要有下面的 se()方法
1 2 3 4 public void setStr (String str) { this .str = str; }
思考:
IOC:对象由 Spring 来创建,管理,装配!
现实中的理解: 原来这套程序是:你写好菜单买好菜,客人来了自己把菜炒好招待,就相当于你请人吃饭 现在这套程序是:你告诉楼下餐厅,你要哪些菜,客人来的时候,餐厅把做好的你需要的菜送上来 IoC:炒菜这件事,不再由你自己来做,而是委托给了第三方__餐厅来做
此时的区别就是,如果我还需要做其他的菜,我不需要自己搞菜谱买材料再做好,而是告诉餐厅,我要什么菜,什么时候要,你做好送来
在前面第一个 module 试试引入 Spring
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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 ="userDaomSql" class ="dao.UserDaoMysqlImpl" > </bean > <bean id ="userServiceImpl" class ="service.UserServiceImp" > <property name ="userDao" ref ="userDaomSql" /> </bean > </beans >
第一个 module 改良后测试
1 2 3 4 5 6 7 8 9 10 11 12 13 package holle0;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import service.UserServiceImpl;public class MyTest0 { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userServiceImpl" ); userServiceImpl.getUser(); } }
总结:
所有的类都要装配的 beans.xml 里面;
所有的 bean 都要通过容器去取;
容器里面取得的 bean,拿出来就是一个对象,用对象调用方法即可;
控制:传统应用程序的对象是有程序本身控制创建的,使用 Spring 后,对象是由 Spring 来创建的
反转:程序本身不创建对象,而变成被动的接受对手
依赖注入:就是利 Set 方法来实现注入的
IOC 是一中编程思想,由主动的编程变成被动的接受
IOC 创建对象的方式
下标赋值 index 指的是有参构造中参数的下标,下标从 0 开始;
1 2 3 4 5 6 7 8 9 10 11 <?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 ="user" class ="pojo.User" > <constructor-arg index ="0" value ="chen" /> </bean > </beans >
类型赋值 (不建议使用)
1 2 3 4 <bean id ="user" class ="com.hgm.pojo.User" > <constructor-arg type ="java.lang.String" value ="黄帅哥" /> </bean >
直接通过参数名(掌握)
1 2 3 4 5 <bean id ="user" class ="com.hgm.pojo.User" > <constructor-arg name ="name" value ="黄帅哥" /> </bean >
注册 bean 之后就对象的初始化了(类似 new 类名() )
name 方式还需要无参构造和 set 方法,index 和 type 只需要有参构造
就算是 new 两个对象,也是只有一个实例(单例模式:全局唯一 )
1 2 3 User user = (User) context.getBean("user" );User user2 = (User) context.getBean("user" );system.out.println(user == user2)
总结:在配置文件加载的过程中,容器(< bean>)中管理的对象就已经初始化了
Spring 配置 别名 1 2 3 4 5 6 7 8 9 <bean id ="user" class ="pojo.User" > <constructor-arg name ="name" value ="chen" > </constructor-arg > </bean > <alias name ="user" alias ="userLove" />
Bean 配置 1 2 3 4 5 6 7 8 9 10 <bean id ="user" class ="pojo.User" name ="u1 u2,u3;u4" > <property name ="name" value ="chen" /> </bean >
import 这个 import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个
假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的 bean 中,我们可以利 用 import 将所有人的 beans.xml 合并为一个总的!
张三(beans.xm1)
李四(beans2.xm1)
王五(beans3.xm1)
applicationContext.xml
1 2 3 <import resource ="beans.xm1" /> <import resource ="beans2.xml" /> <import resource ="beans3.xm1" />
使用的时候,直接使用总的配置就可以了
弹幕评论:
按照在总的 xml 中的导入顺序来进行创建,后导入的会重写先导入的,最终实例化的对象会是后导入 xml 中的那个
依赖注入 构造器注入 第四点有提到
set 方式注入【重点】 依赖注入:set 注入!
依赖:bean 对象的创建依赖于容器
注入:bean 对象中的所有属性,由容器来注入
【环境搭建】
复杂类型
Address 类
真实测试对象
Student 类
beans.xml
测试
MyTest3
Student 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package pojo;import java.util.*;@Get @Set public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String, String> card; private Set<String> game; private Properties infor; private String wife; @Override public String toString () { return "Student{" +"\n" + "name='" + name + '\'' +"\n" + ", address=" + address.toString() +"\n" + ", books=" + Arrays.toString(books) +"\n" + ", hobbies=" + hobbies +"\n" + ", card=" + card +"\n" + ", game=" + game +"\n" + ", infor=" + infor +"\n" + ", wife='" + wife + '\'' +"\n" + '}' ; } }
Address 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package pojo;public class Address { private String address; public String getAddress () { return address; } public void setAddress (String address) { this .address = address; } @Override public String toString () { return "Address{" + "address='" + address + '\'' + '}' ; } }
beans.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <?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 ="pojo.Address" > <property name ="address" value ="address你好" /> </bean > <bean id ="student" class ="pojo.Student" > <property name ="name" value ="name你好" /> <property name ="address" ref ="address" /> <property name ="books" > <array > <value > 三国</value > <value > 西游</value > <value > 水浒</value > </array > </property > <property name ="hobbies" > <list > <value > 唱</value > <value > 跳</value > <value > rap</value > <value > 篮球</value > </list > </property > <property name ="card" > <map > <entry key ="username" value ="root" /> <entry key ="password" value ="root" /> </map > </property > <property name ="game" > <set > <value > wangzhe</value > <value > lol</value > <value > galname</value > </set > </property > <property name ="wife" > <null > </null > </property > <property name ="infor" > <props > <prop key ="id" > 20200802</prop > <prop key ="name" > cbh</prop > </props > </property > </bean > </beans >
MyTest3
1 2 3 4 5 6 7 8 9 10 11 import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import pojo.Student;public class MyTest3 { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" ); Student stu = (Student) context.getBean("student" ); System.out.println(stu.toString()); } }
拓展注入
pojo 增加 User 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package pojo;public class User { private String name; private int id; public User () { } public User (String name, int id) { super (); this .name = name; this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getId () { return id; } public void setId (int id) { this .id = id; } @Override public String toString () { return "User [name=" + name + ", id=" + id + "]" ; } }
注意: beans 里面加上这下面两行
使用 p 和 c 命名空间需要导入 xml 约束
xmlns:p=“http://www.springframework.org/schema/p” xmlns:c=“http://www.springframework.org/schema/c”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ?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" > <bean id ="user" class ="pojo.User" p:name ="cxk" p:id ="20" > </bean > <bean id ="user2" class ="pojo.User" c:name ="cbh" c:id ="22" > </bean > </beans >
测试
1 2 3 4 ApplicationContext context = new ClassPathXmlApplicationContext ("beans.xml" );User user = context.getBean("user" ,User.class);System.out.println(user.toString());
Bean 作用域
单例模式(默认)
1 <bean id ="user2" class ="pojo.User" c:name ="cxk" c:age ="19" scope ="singleton" > </bean >
弹幕评论:单例模式是把对象放在 pool 中,需要再取出来,使用的都是同一个对象实例
原型模式: 每次从容器中 get 的时候,都产生一个新对象!
1 <bean id ="user2" class ="pojo.User" c:name ="cxk" c:age ="19" scope ="prototype" > </bean >
其余的 request、session、application 这些只能在 web 开放中使用!
Bean 的自动装配
自动装配是 Spring 满足 bean 依赖的一种方式
Spring 会在上下文自动寻找,并自动给 bean 装配属性
在 Spring 中有三种装配的方式
在 xml 中显示配置
在 java 中显示配置
隐式的自动装配 bean 【重要】
环境搭建:一个人有两个宠物
byType 自动装配:byType 会自动查找,和自己对象 set 方法参数的类型相同的 bean
保证所有的 class 唯一(类为全局唯一)
byName 自动装配:byName 会自动查找,和自己对象 set 对应的值对应的 id
保证所有 id 唯一,并且和 set 注入的值一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <context:annotation-config /> <bean id ="cat" class ="com.hgm.pojo.Cat" /> <bean id ="dog" class ="com.hgm.pojo.Dog" /> <bean id ="people" class ="com.hgm.pojo.People" > <property name ="name" value ="黄帅哥" /> </bean >
测试:自动装配 pojo 的 Cat 类
1 2 3 4 5 public class Cat { public void shut () { System.out.println("miao" ); } }
pojo 的 Dog 类
1 2 3 4 5 6 7 public class Dog { public void shut () { System.out.println("wow" ); } }
pojo 的 People 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package pojo;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 + '\'' + '}' ; } }
xml 配置 -> byType 自动装配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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 ="cat" class ="pojo.Cat" /> <bean id ="dog" class ="pojo.Dog" /> <bean id ="people" class ="pojo.People" autowire ="byType" > <property name ="name" value ="cbh" > </property > </bean > </beans >
xml 配置 -> byName 自动装配
1 2 3 4 5 6 7 8 9 10 <bean id ="cat" class ="pojo.Cat" /> <bean id ="dog" class ="pojo.Dog" /> <bean id ="people" class ="pojo.People" autowire ="byName" > <property name ="name" value ="cbh" > </property > </bean >
弹幕评论:byName 只能取到小写,大写取不到
小结:
byname 的时候,需要保证所有 bean 的 id 唯一,并且这个 bean 需要和自动注入的属性的 set 方法的值一致
ByType 的时候,需要保证所有 bean 的 class 唯一,并且这个 bean 需要和自动注入的属性的类型值一致
使用注解实现自动装配 jdk1.5 支持的注解,Spring2.5 就支持注解了!
The introduction of annotation-based configuration raised the question of whether this approach is “better” than XML.(翻译:基于注释的配置的引入提出了一个问题,即这种方法是否比 XML“更好”)
导入 context 约束
1 1. xmlns:context="http://www.springframework.org/schema/context"
配置注解的支持:< context:annotation-config/>
1 2 3 4 5 6 7 8 9 10 11 <?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 >
@Auotwired 默认是 byType 方式,如果匹配不上,就会 byName
直接在属性上使用即可! 也可以在 set 方式上使用
使用 Auotwired 我们可以不用编写 Set 方法了,前提是你这个自动导入装配的属性在 IOC (Spring) 容器中存在,且符合名字 Byname
科普: @Nullable 字段标记了这个注解,说明这个字段可以为 null
1 2 3 4 5 6 7 8 public class People { @Autowired private Cat cat; @Autowired private Dog dog; private String name; }
@Nullable 字段标记了这个注解,说明该字段可以为空
public name(@Nullable String name){
}
1 2 3 4 5 public @interface Autowired { boolean required () default true ; }
如果定义了 Autowire 的 require 属性为 false,说明这个对象可以为 null,否则不允许为空(false 表示找不到装配,不抛出异常)
@Autowired+@Qualifier @Autowired 不能唯一装配时,需要@Autowired+@Qualifier
如果@Autowired 自动装配的环境比较复杂,自动装配无法通过一个注解[@Autowired]完成的时候,我们可以使用@Qualifier(value = “xx”)去配置@Autowired 的使用,指定一个唯一的 bean 对象注入!
1 2 3 4 5 6 7 8 9 public class People { @Autowired private Cat cat; @Autowired @Qualifier(value = "dog") private Dog dog; private String name; }
弹幕评论:
如果 xml 文件中同一个对象被多个 bean 使用,Autowired 无法按类型找到,可以用@Qualifier 指定 id 查找
@Resource 默认是 byName 方式,如果匹配不上,就会 byType
1 2 3 4 5 6 7 8 public class People { Resource(name="cat" ) private Cat cat; Resource(name="dog" ) private Dog dog; private String name; }
弹幕评论:
Autowired 是 byType,@Autowired+@Qualifier = byType || byName
Autowired 是先 byteType,如果唯一則注入,否则 byName 查找。resource 是先 byname,不符合再继续 byType
@Resource 和@Auotwired 的区别:
都是用来自动装配的,都可以放在属性字段上
@Auotwired 是通过 Byname 的方式实现,而且必须要求这个对象存在[常用]
@Resource 默认是通过 byname 的方式实现,如果找不到名字,则通过 byType 实现,如果两个都找不到的情况下就报错!
执行顺序不同:@Auotwired 通过 byType 的方式实现.@Resource 默认是通过 byname 的方式实现,
使用注解开发 在 spring4 之后,要使用注解开发,必须保证 aop 的包导入了
使用注解需要导入 context 约束,增加注解的支持
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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 >
bean 弹幕评论: 有了< context:component-scan>,另一个< context:annotation-config/>标签可以移除掉,因为已经被包含进去了。
所以:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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 https://www.springframework.org/schema/context/spring-context.xsd" > <context:component-scan base-package ="com.ms" /> 这里就可以注释掉了 </beans >
属性如何注入 @Value 1 2 3 4 5 6 7 8 9 @Component public class User { public String name; @Value("SerMs") public void setName (String name) { this .name = name; } }
衍生的注解 在类里面使用了注解就说明这个类被 Spring 托管了,也就是成为 Spring 的组件了
@Component 有几个衍生注解,会按照 web 开发中,mvc 架构中分层。
@Component 有几个衍生注解,我们在 web 开发中,会按照 MVC 三层架构分层!
dao 层【@Repository】 (dao 层我们都会用 Repository 来注解)
service 层【@Service】(service 层我们都会用 Serivce 来注解)
Controller 层【@Controller】
这四个功能都是一样的,都是代表将某个类注册到 Spring 中,装配 Bean
自动装配
@Nullable:字段标记了这个注解,说明这个字段可以为 null
@Resource:自动装配通过名字,类型
作用域@scope 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component @scope("prototype") public class User { @value("kuangshen") public String name; @value("kuangshen") public void setName (String name) { this .name = name; } }
小结 xml 与注解:
xml 更加万能,使用宇任何场合,维护更加方便
注解 不是自己类使用不了,维护相对复杂!
xml 与注解最佳实践:
弹幕评论:要么使用@Bean,要么使用@Component 和 ComponentScan,两种效果一样
配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.kuang.config;import com.kuang.pojo.User;import org.springframework.context.annotation.*;@Configuration @ComponentScan("com.kuang.pojo") @Import(MyConfig1.class) public class MyConfig { @Bean public User getUser () { return new User (); } }
弹幕评论:ComponentScan、@Component(“pojo”) 这两个注解配合使用
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import com.kuang.config.MyConfig;import com.kuang.pojo.User;import org.junit.Test;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class MyTest { @Test public void test () { ApplicationContext context = new AnnotationConfigApplicationContext (MyConfig.class); User user = (User) context.getBean("getUser" ); System.out.println(user.toString()); } }
这种纯 Java 的配置方式,在 SpringBoot 中随处可见
这里这个注解的意思就是说明这个类被 Spring 接管了,注册到了容器中
@Configuration 本质上还是一个@Component,代表这个一个配置类和 bean.xml 一样
@ComponentScan(“com.kuang.pojo”) // @ComponentScan 表示组件的扫描 @Import(MyConfig1.class) // 通过 import 导入其他的配置类
会创建两个相同对象问题的说明:
弹幕总结 - -> @Bean 是相当于< bean>标签创建的对象,而我们之前学的@Component 是通过 spring 自动创建的这个被注解声明的对象,所以这里相当于有两个 User 对象被创建了。一个是 bean 标签创建的(@Bean),一个是通过扫描然后使用@Component,spring 自动创建的 User 对象,所以这里去掉@Bean 这些东西,然后开启扫描。之后在 User 头上用@Component 即可达到 spring 自动创建 User 对象了
代理模式(AOP) 为什么要学代理模式?
因为这个就是 SpringAOP 的底层!【SpringAOP 和 SpringMVC】
代理模式的分类:
静态代理 角色分析 :
抽象角色:一般会使用接口或者抽象类来解决
真实角色:被代理角色
代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
客户:使用代理角色来进行一些操作 .
代码步骤:
rent 接口
1 2 3 4 5 package com.kuang.Daili;public interface Rent { public void rent () ; }
真实角色(Client 租户)
1 2 3 4 5 6 7 8 9 10 11 package com.kuang.Daili;public class Client { public static void main (String[] args) { Host host=new Host (); Proxy proxy=new Proxy (host); proxy.rent(); } }
proxy 代理角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package com.kuang.Daili;public class Proxy { private Host host; public Proxy () { } public Proxy (Host host) { this .host=host; } public void rent () { seeHouse(); host.rent(); hetong(); fare(); } public void seeHouse () { System.out.println("中介带你看房" ); } public void fare () { System.out.println("收中介费!" ); } public void hetong () { System.out.println("签租领合同" ); } }
客户端访问代理角色
1 2 3 4 5 6 7 public class Client { public static void main (String[] args) { Host host = new Host (); Proxy proxy = new Proxy (host); proxy.rent(); } }
分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式,程序源自于生活,所以学编程的人,一般能够更加抽象的看待生活中发生的事情。
代理模式的好处:
可以使真实角色的操作更加纯粹,不用去关注一些公共业务
公共业务就交给了代理角色,实现了业务的分工
公共业务发生扩展的时候,方便集中管理
缺点:
一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
静态代理的再理解
创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
1 2 3 4 5 6 7 public interface UserService { void add () ; void delete () ; void update () ; void query () ; }
我们需要一个真实对象来完成这些增删改查操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class UserServiceImpl implements UserService { public void add () { System.out.println("增加了一个用户" ); } public void delete () { System.out.println("删除了一个用户" ); } public void update () { System.out.println("更新了一个用户" ); } public void query () { System.out.println("查询了一个用户" ); } }
需求来了,现在我们需要增加一个日志功能,怎么实现!
设置一个代理类来处理日志!代理角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class UserServiceProxy implements UserService { private UserServiceImpl userService; public void setUserService (UserServiceImpl userService) { this .userService = userService; } public void add () { log("add" ); userService.add(); } public void delete () { log("delete" ); userService.delete(); } public void update () { log("update" ); userService.update(); } public void query () { log("query" ); userService.query(); } public void log (String msg) { System.out.println("执行了" +msg+"方法" ); } }
OK,到了现在代理模式大家应该都没有什么问题了,重点大家需要理解其中的思想;我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是 AOP 中最核心的思想
AOP 横向开发
我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理
动态代理
动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们直接写好的
动态代理分为两大类:基于接口的动态代理,基于类的动态代理
基于接口—-JDK 动态代理
基于类:cglib
java 字节码实现 : Javassist
需要了解两个类:Proxy,InvocationHandler
【InvocationHandler:调用处理程序】
1 2 3 4 5 Object invoke (Object proxy, 方法 method, Object[] args) ;
实例:
接口 Host.java
1 2 3 4 5 6 package pojo2;public interface Host { public void rent () ; }
接口 Host 实现类 HostMaster.java
1 2 3 4 5 6 7 package pojo2;public class HostMaster implements Host { public void rent () { System.out.println("房东要租房子" ); } }
代理角色的处理程序类 ProxyInvocationHandler.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package pojo2;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class ProxyInvocationHandler implements InvocationHandler { public HostMaster hostMaster ; public void setHostMaster (HostMaster hostMaster) { this .hostMaster = hostMaster; } public Object getProxy () { return Proxy.newProxyInstance(this .getClass().getClassLoader(), hostMaster.getClass().getInterfaces(), this ); public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { seeHouse(); Object result = method.invoke(hostMaster, args); fee(); return result; } public void seeHouse () { System.out.println("看房子" ); } public void fee () { System.out.println("收中介费" ); } }
用户类 My2.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package holle4_proxy;import pojo2.Host;import pojo2.Host2;import pojo2.HostMaster;import pojo2.ProxyInvocationHandler;public class My2 { public static void main (String[] args) { HostMaster hostMaster = new HostMaster (); ProxyInvocationHandler pih = new ProxyInvocationHandler (); pih.setHostMaster(hostMaster); Host proxy = (Host) pih.getProxy(); proxy.rent(); } }
弹幕评论: 什么时候调用 invoke 方法的? 代理实例调用方法时 invoke 方法就会被调用,可以 debug 试试
改为万能代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class ProxyInvocationHandler implements InvocationHandler { public Object target; public void setTarget (Object target) { this .target = target; } public Object getProxy () { return Proxy.newProxyInstance(this .getClass().getClassLoader(), target.getClass().getInterfaces(), this ); } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { pre(); log(method.getName()); Object result = method.invoke(target, args); post(); return result; } public void pre () { System.out.println("[前置通知]" ); } public void post () { System.out.println("[后置通知]" ); } public void log (String msg) { System.out.println("[Debug]执行了" + msg + "方法" ); } }
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Client { public static void main (String[] args) { UserServiceImpl userService = new UserServiceImpl (); ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler (); proxyInvocationHandler.setTarget(userService); UserService proxy = (UserService) proxyInvocationHandler.getProxy(); proxy.add(); } }
动态代理的好处:
可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
公共也就交给代理角色!实现了业务的分工
公共业务发生扩展的时候,方便集中管理
一个动态代理类代理的是一个接口,一般就是对应的一类业务
一个动态代理类可以代理多个类,只要是实现了同一个接口即可
AOP
什么是 AOP AOP 意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,AOP 是 OOP 的延续,是软件开发中的一个热点,也是 String 框架中的一个重要内容,是函数式编程的一种衍生范型,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发效率。
术语:
Aspect:切面,表示增强的功能,就是一推代码,完成某一个功能,非业务功能,常见的切面功能有日志,事务,统计信息,参数检查,权限验证
JoinPoint:链接点,连接业务方法和切面的位置,就某类中的业务的方法
目标对象:给哪个类的方法增加功能,这个类就是目标对象
切面有关的三要素:
切面的功能代码,切面干什么
切面的执行位置,使用 Pointcut 表示切面执行的位置
切面的执行时间,使用 Advice 表示时间,在目标方法之前,还是目标方法之后
AOP 在 Spring 中的使用 提供声明式事务,允许用户自定义切面
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
切面(Aspect):横切关注点 被模块化的特殊对象。即,它是一个类。(Log 类)
通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。(Log 类中的方法)
目标(Target):被通知对象。(生成的代理类)
代理(Proxy):向目标对象应用通知之后创建的对象。(生成的代理类)
切入点(PointCut):切面通知执行的”地点”的定义。(最后两点:在哪个地方执行,比如:method.invoke())
连接点(JointPoint):与切入点匹配的执行点。
SpringAOP 中,通过 Advice 定义横切逻辑,Spring 中支持 5 种类型的 Advice:
即 AOP 在不改变原有代码的情况下,去增加新的功能。 (代理)
使用 Spring 实现 AOP【重点】
1 2 3 4 5 6 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.9.4</version > </dependency >
方法一:使用原生 spring 接口 springAPI 接口实现
需要先导入 aop 约束
1 2 3 4 5 6 7 8 9 10 <?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" ></beans >
创建 applicationContext.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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" > <bean id ="userservice" class ="service.UserServiceImpl" /> <bean id ="log" class ="log.Log" /> <bean id ="afterLog" class ="log.AfterLog" /> <aop:config > <aop:pointcut id ="pointcut" expression ="execution(* service.UserServiceImpl.*(..))" /> <aop:advisor advice-ref ="log" pointcut-ref ="pointcut" /> <aop:advisor advice-ref ="afterLog" pointcut-ref ="pointcut" /> </aop:config > </beans >
execution(返回类型,类名,方法名(参数))->
execution( com.service. ,*(…))
UserService.java
1 2 3 4 5 6 7 package service;public interface UserService { public void add () ; public void delete () ; public void query () ; public void update () ; }
UserService 的实现类 UserServiceImp.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package service;public class UserServiceImpl implements UserService { public void add () { System.out.println("add增" ); } public void delete () { System.out.println("delete删" ); } public void update () { System.out.println("update改" ); } public void query () { System.out.println("query查" ); } }
前置 Log.java
1 2 3 4 5 6 7 8 9 10 11 12 13 package log;import org.springframework.aop.MethodBeforeAdvice;import java.lang.reflect.Method;public class Log implements MethodBeforeAdvice { public void before (Method method, Object[] args, Object target) throws Throwable { System.out.println(target.getClass().getName()+"的" +method.getName()+"被执行了" ); } }
后置 AfterLog.java
1 2 3 4 5 6 7 8 9 10 11 package log;import java.lang.reflect.Method;import org.springframework.aop.AfterReturningAdvice;public class AfterLog implements AfterReturningAdvice { public void afterReturning (Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("执行了" +method.getName()+"方法,返回值是" +returnValue); } }
测试类 MyTest5
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import service.UserService;public class MyTest5 { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("applicationContext.xml" ); UserService userService = (UserService) context.getBean("userservice" ); userService.add(); } }
方法二:自定义类实现 AOP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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" > <bean id ="userservice" class ="service.UserServiceImpl" /> <bean id ="log" class ="log.Log" /> <bean id ="afterLog" class ="log.AfterLog" /> <bean id ="diy" class ="diy.DiyPointcut" /> <aop:config > <aop:aspect ref ="diy" > <aop:pointcut id ="point" expression ="execution(* service.UserServiceImpl.*(..))" /> <aop:before method ="before" pointcut-ref ="point" /> <aop:after method ="after" pointcut-ref ="point" /> </aop:aspect > </aop:config > </beans >
1 2 3 4 5 6 7 8 9 10 11 12 package diy;public class DiyPointcut { public void before () { System.out.println("插入到前面" ); } public void after () { System.out.println("插入到后面" ); } }
1 2 3 4 5 6 7 8 9 public class MyTest5 { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("applicationContext.xml" ); UserService userService = (UserService) context.getBean("userservice" ); userService.add(); } }
方法三:使用注解实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.kuang.config;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()); Object proceed = jp.proceed(); System.out.println("环绕后" ); System.out.println(proceed); } }
第二步:在 Spring 配置文件中,注册 bean,并增加支持注解的配置
1 2 3 <bean id ="annotationPointcut" class ="com.kuang.config.AnnotationPointcut" /> <aop:aspectj-autoproxy />
测试
1 2 3 4 5 6 7 8 public class MyTest5 { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext ("applicationContext.xml" ); UserService userService = (UserService) context.getBean("userservice" ); userService.add(); } }
输出结果:
aop:aspectj-autoproxy:说明
通过 aop 命名空间的 声明自动为 spring 容器中那些配置@aspectJ 切面的 bean 创建代理,织入切面。当然,spring 在内部依旧采用 AnnotationAwareAspectJAutoProxyCreator 进行自动代理的创建工作,但具体实现的细节已经被 隐藏起来了
有一个 proxy-target-class 属性,默认为 false,表示使用 jdk 动态代理织入增强,当配为 时,表示使用 CGLib 动态代理技术织入增强。不过即使 proxy-target-class 设置为 false,如果目标类没有声明接口,则 spring 将自动使用 CGLib 动态代理。
aop 的技术实现框架:
spring:Spring 在内部实现了 aop 的规范,能做了 aop 的工作
我们项目开发中很少使用 Spring 的 aop 实现,因为 spring 的 aop 比较笨重
AspectJ:一个开源的专门处理 Aop 的框架,Spring 框架中集成了 aspectj 框架,通过 spring 就能使用 aspectj 的功能
使用 xml 的配置文件:配置全局事务
使用注解,我们在项目中要做 aop 功能,一般都用注解,aspectj 有五个注解