1、Spring
1.1、简介
Spring框架是由于软件开发的复杂性而创建的。Spring使用的是基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅仅限于服务器端的开发。从简单性、可测试性和松耦合性角度而言,绝大部分Java应用都可以从Spring中受益。
maven依赖
<!-- 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>
1.2、优点
- Spring是一个开源免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 控制反转(IOC)、面向切面编程(AOP) (都是重点)
- 支持事务的处理,对框架整合的支持
总结:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.3、组成
先大概看看总体模块有哪些,具体细节可以参照这里
2、IOC理论推导
我们先用原来的方式写一段代码:
- UserDao接口
package com.lut.dao;
public interface UserDao {
void getUser();
}
- UserDao的实现类
public class UserDaoImpl implements UserDao{
public void getUser() {
System.out.println("默认获取用户数据");
}
}
- UserService接口
public interface UserService {
void getUser();
}
- UserService的实现类
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
public void getUser() {
userDao.getUser();
}
}
- 测试
public class MyTest {
public static void main(String[] args) {
// 用户实际调用的是业务层,dao层用户不需要接触
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
在之前的业务中,用户的需求可能会影响我们原来的代码,因此我们需要根据用户的需求去改代码。类似上面的代码,如果修改的代码量大,修改一次的成本会十分昂贵。
使用一个set接实现,已经发生了革命性的变化!
private UserDao userDao;
// 利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
service层直接调用dao层方法,由于dao层有多个实现,如果在service实现类中直接将dao实例化,则后期更换数据库则需要修改代码,而使用set方法,用户可以自己设置需要使用的dao层实现类,代码可扩展性增强,控制权交由用户来处理。
- 之前,需要程序员去创建对象,创建何种对象的控制权在开发人员身上
- 使用了set注入后,程序不再有主动性,而是被动接受对象
这种思想从本质上解决了问题,程序员不用再去管理、考虑对象的创建。系统耦合性大大降低,可以更加专注于业务的实现。客户需要用什么对象,自己去调用就ok。这就是IOC的原型。
IOC本质:所谓控制反转,本质就是获得依赖对象的方式反转了。
控制反转是一种通过描述并通过第三方去上产或获取特定对象的方式。在spring中实现控制反转的是IOC容器,其实现方式是依赖注入。
3、Hellospring
- 编写实体类
public class Hello {
private String str;
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
}
- 配置文件
<?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">
<!--使用spring创建对象,在spring中这些都称为Bean-->
<bean id="hello" class="com.lut.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
- 测试
public class MyTest {
public static void main(String[] args) {
// 获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 现在所有对象都在spring中管理,要使用直接从里面取出来就可以
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
小结
- Hello 对象是谁创建的? hello 对象是由Spring创建的
- Hello 对象的属性是怎么设置的? hello 对象的属性是由Spring容器设置的
这个过程就叫控制反转 :
控制 : 谁来控制对象的创建 , 传统应用程序的对象是由程序本身控制创建的 , 使用Spring后 , 对象是由Spring来创建的
反转 : 程序本身不创建对象 , 而变成被动的接收对象。
依赖注入 : 就是利用set方法来进行注入的。
IOC是一种编程思想,由主动的编程变成被动的接收
4、IOC创建对象的方式
- 默认使用无参构造创建对象
- 如何使用有参构造创建对象?
个人更偏向第三种。。。
<!--第一种,下标赋值-->
<bean id="user" class="com.lut.pojo.User">
<constructor-arg index="0" value="楠怪"/>
</bean>
<!--第二种,通过类型创建-->
<bean id="user" class="com.lut.pojo.User">
<constructor-arg type="java.lang.String" value="nanguai"/>
</bean>
<!--第三种,直接通过参数名来设置-->
<bean id="user" class="com.lut.pojo.User">
<constructor-arg name="name" value="NANGUAI"/>
</bean>
总结:在配置文件加载的时候,容器中管理的所有对象就已经实例化了!
5、Spring配置
5.1、别名
<!--别名设置-->
<alias name="user" alias="user2"/>
5.2、bean的配置
- id:容器中Bean的唯一标识符,Spring容器对Bean的配置、管理通过该属性完成,装配Bean时根据id值获取对象。
- name:Spring容器可以通过该属性对容器中的Bean进行配置和管理,name属性可以为Bean指定多个名称,每个名称之间用逗号或分号隔开。
- class:指定Bean的具体实现类,使用对象所在类的全路径。
5.3、import
import一般用于团队开发,它可以将多个配置文件,导入合并为一个。最后使用的时候,只用最后合并的那一个就可以了。
6、依赖注入
6.1、构造器注入
这个前面已经讲过,这里就不再赘述。
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;
...
// 以下是get、set、toString方法
}
- beans.xml
<bean id="student" class="com.lut.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="楠怪"/>
</bean>
- 测试类
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getAddress());
}
}
- 完善注入
<!--第二种,bean注入-->
<property name="address" ref="address"/>
<!--数组-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--list-->
<property name="hobbys">
<list>
<value>唱</value>
<value>跳</value>
<value>rap</value>
<value>篮球</value>
</list>
</property>
<!--Map-->
<property name="card">
<map>
<entry key="身份证" value="1243234"/>
<entry key="银行卡" value="3435343"/>
</map>
</property>
<!--Set-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
</set>
</property>
<!--null-->
<property name="wife">
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="学号">17424334</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
6.3、其他方式
采用p命名和c命名,简化了property和constructor-arg的赋值。在使用之前需要进行配置:
p命名
xmlns:p="http://www.springframework.org/schema/p
c命名
xmlns:c="http://www.springframework.org/schema/c
使用:
<!--p命名空间注入,可以直接注入属性的值:propertiy-->
<bean id="user" class="com.lut.pojo.User" p:age="24" p:name="楠怪"/>
<!--c命名空间注入,通过构造器注入:construct-args-->
<bean id="user2" class="com.lut.pojo.User" c:age="18" c:name="nanguai"/>
6.4、bean的作用域
作用域 | 描述 |
---|---|
singleton | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。 |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。 |
7、Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean配置属性
在spring种有三种装配方式:
- 在xml中显示装配
这里有篇博客举了很容易理解的例子,可以来看一下,传送门
- 在Java中显示装配
- 隐式自动装配bean
7.1、注解实现自动装配
自动装配所用注解
使用注解须知:
- 导入约束,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
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>
- @Autowired直接在属性上使用即可。(也可以在set方法上使用)
- 使用Autowired可以不用编写set方法,前提是自动装配的属性在IOC容中存在,且符合名字byName
- 如果@Autowired自动装配的环境比较复杂,自动装配无法通过一个注解@Autowired完成的时候,我们可以使用@Qualifier(value = “xxx”)去配置自动装配,指定一个唯一的bean对象注入。
@Autowired
@Qualifier(value = "dog222")
private Dog dog;
- @Resource注解
@Resource(name = "dog222")
private Dog dog;
@Autowired 与@Resource的区别:
- @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配,默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。
- @Resource默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
8、使用注解开发
学习之后发现使用配置文件和注解开发都各有好处,但是如果项目比较大比较复杂,还是建议去使用配置文件开发。这里把spring的一部分注解贴出来,需要的时候再去看,传送门
9、代理模式
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
优点:
(1)职责清晰
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
(2)代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
(3)高扩展性
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率变低。
9.1、静态代理
角色分析:
- 抽象角色:一般使用抽象类或者接口
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理后我们一般还会做些附属操作
- 客户:访问代理角色的人
代码步骤:
- 接口
public interface Rent {
public void rent();
}
- 真实角色
// 房东
public class Host {
public void rent(){
System.out.println("房东租房");
}
}
- 代理角色
public class Proxy {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent(){
seeHouse();
host.rent();
fare();
hetong();
}
// 看房
public void seeHouse(){
System.out.println("看房子");
}
// 收中介费
public void fare(){
System.out.println("收中介费");
}
// 签合同
public void hetong(){
System.out.println("签合同");
}
}
- 客户端访问代理角色
public class Client {
public static void main(String[] args) {
Host host = new Host();
// 代理
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
9.2、动态代理
下面链接当中的图示感觉非常的明确,自我理解动态代理就是代理类和实现类都会去实现接口,而程序的运行则是通过代理类调用实现类进行。第一眼看上去好像是写了很多重复的代码、做了许多无用的工作。代理类好像和实现类并没有什么区别,但是其实如果秉承着不要改动源码的精神,后续的改动可以在代理类中实现,而不用去修改实现类,很好的隐藏和保护了实现类,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的
- 动态代理分为两大类
- 基于接口:JDK动态代理
- 基于类:cglib
- Java字节码实现:javasist
详细资料:Java JDK 动态代理(AOP)使用及实现原理分析
10、AOP
10.1、什么是AOP
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
10.2、Spring在AOP中的作用
提供声明事务,允许用户自定义切面
- 横切关注点:跨越引用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等。
- 切面:横切:关注点被关注点模块化的特殊对象,即,它是一个类。
- 通知:切面必须要完成的工作,即,它是类中的一个方法。
- 目标:被通知对象。
- 代理:向目标对象对应通知之后创建的对象。
- 切入点:切面通知执行的“地点”的定义。
- 连接点:与切入点匹配的执行点。
10.3、使用Sprig实现AOP
【依赖包】
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
<scope>runtime</scope>
</dependency>
方式一:使用Spring的API接口
方式二:自定义类实现
方式三:使用注解实现
11、声明式事务
11.1、事务
- 把一组业务当作一个业务来做:要么都成功,要么都失败
- 确保完整性和一致性
事务ACID原则:
- 原子性
- 一致性
- 隔离性
- 多个业务可能操作同一个资源,防止数据损坏
- 持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中
11.2、Spring
- 声明式事务:AOP
- 编程式事务:在代码中进行事务的管理
思考:
为什么需要事务?
如果不配置事务,可能会存在数据提交不一致