Spring
1. 概述
学习笔记,如有错误,请留言指正
1.1 简介
-
spring : 给java开发带来了春天;
-
2002年,首次推出了Spring的雏形,interface21;
-
2003年,以interface21框架为基础,经过重新设计,不断丰富,发布了Spring 1.0正式版;
-
作者: Rod Johnson ,音乐学博士,计算机学士,spring framework创始人
-
SSH: Struct2 + Spring + Hibernate
-
SSM: SpringMVC + Spring + MyBatis
官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/
GitHub: https://github.com/spring-projects/spring-framework/releases
Maven:
<!-- spring-webmvc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<!-- spring-jdbc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
1.2 优点:
- 开源免费框架(容器)
- 轻量级,非入侵式框架
- 控制反转(IOC), 面向切面(AOP)
- 支持事务处理,对其他框架的整合支持
1.3 组成:
1.4 扩展
- spring发展太久之后,配置十分繁琐,简直是“配置地狱”!
- 所以有了Spring Boot 和 Spring Cloud;
Spring Boot:
- 一个快速开发的脚手架;
- 基于Spring Boot 可以快速开发单个微服务;
- 约定大于配置;
Spring Cloud:
- Spring Cloud是基于Spring Boot实现的;
所以,学习Spring Boot的前提,需要掌握Spring及SpringMVC;
2. IOC 思想
控制反转IOC(Inversion of Control),是一种设计思想, DI (依赖注入)是实现IOC的一种方式,没有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 = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }
-
测试
public class MyTest { public static void main(String[] args) { //用户实际调用的是业务层,dao层不需要接触 UserService userService = new UserServiceImpl(); userService.getUser(); } }
-
结果
如果需求发生了变化,需要扩展其他功能, 那么就需要每次修改源代码
-
UserDaoOrecleImpl(新增功能)
public class UserDaoOracleImpl implements UserDao{ @Override public void getUser() { System.out.println("Oracle获取用户数据"); } }
-
UserDaoSqlServerImpl(新增功能)
public class UserDaoSqlServerImpl implements UserDao{ @Override public void getUser() { System.out.println("SqlServer获取用户数据"); } }
-
每次修改需求就需要修改Service层源代码:
public class UserServiceImpl implements UserService{ //修改 private UserDao userDao = new UserDaoSqlServerImpl(); @Override public void getUser() { userDao.getUser(); } }
那么有什么方法可以解决呢?
Ioc思想:
-
给service层添加set功能,将控制权交给调用者
public class UserServiceImpl implements UserService{ private UserDao userDao = new UserDaoImpl(); //利用set动态的实现值的注入 public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); } }
-
那么即可在调用层面上解决频繁修改源代码的问题
public class MyTest { public static void main(String[] args) { UserService userService = new UserServiceImpl(); // 传入要使用的新功能类就可以了 userService.setUserDao(new UserDaoSqlServerImpl()); userService.getUser(); } }
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
// 传入要使用的新功能类就可以了
userService.setUserDao(new UserDaoOracleImpl());
userService.getUser();
}
}
-
结果
以上即使IOC的原型
- 之前,程序是主动创建对象,控制权在程序员手上
- 使用set注入后,程序不再具有主动性,而变成了被动的接受对象
这是一种思想,这种思想,从本质上解决了问题,我们程序员不用再取管理对象的创建了,系统的耦合性大大降低,可以更加专注的在业务的实现上。
控制反转是一种通过描述(xml或注解)并通过第三方去生产或获取特定对象的方式。 在Spring中实现控制反转的是Ioc容器,容器实现方法是依赖注入(Dependency Injection, DI)。
3. HelloSpring:
创建实体类:
package com.nych.entity;
public class Hello {
private String name;
public Hello() {
}
public Hello(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
写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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 使用Spring来创建对象,在spring中这些都称为bean
bean = 对象
id = 对象名
class = 要new的类
property 相当于给对象中的属性设置一个值
-->
<bean id="hello" class="com.nych.entity.Hello">
<property name="name" 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.getName());
}
}
控制反转:
- 控制: 谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是有Spring来创建的。
- 反转: 程序本身不创建对象,而变成了被动的接收对象。
- 依赖注入: 就是利用set方法来进行注入的。
- ioc(控制反转)是一种编程思想,由主动的编程变成被动的接收,对象由spring来创建、管理、装配!
将原来写代码的方式修改为spring实现:
dao层和service层不变,添加一个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 id="userDaoImpl" class="com.nych.dao.UserDaoImpl"/>
<bean id="mySqlImpl" class="com.nych.dao.UserDaoMySqlImpl"/>
<bean id="oracleImpl" class="com.nych.dao.UserDaoOracleImpl"/>
<bean id="userService" class="com.nych.service.UserServiceImpl">
<!-- ref: 引用spring容器中创建好的对象
value: 基本数据类型,具体的值
-->
<property name="userDao" ref="userDaoImpl"/>
</bean>
</beans>
- 测试类:
public class MyTest {
public static void main(String[] args) {
//获取applicationContext, 拿到spring的容器
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl userService = (UserServiceImpl) context.getBean("userService");
userService.getUser();
}
}
现在修改需求,就不用修改任何源代码,只需要修改xml配置文件即可, 所谓的IoC就是一句话:对象由spring来创建、管理、装配!
4. IOC创建对象的方式
新建实体类:
package com.nych.entity;
public class User {
private String name;
public User(){
System.out.println("User无参构造");
}
public User(String name) {
this.name = name;
System.out.println("User有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用spring创建对象的方式:
-
使用无参构造创建对象(默认)
<bean id="user" class="com.nych.entity.User"> <property name="name" value="张三"/> </bean>
-
使用有参构造创建对象
-
方法一:通过下标赋值
<bean id="user" class="com.nych.entity.User"> <constructor-arg index="0" value="张三"/> </bean>
-
方法二:通过类型赋值
<bean id="user" class="com.nych.entity.User"> <constructor-arg type="java.lang.String" value="张三"/> </bean>
-
方法三: 通过参数名赋值
<!-- 通过参数名赋值--> <bean id="user" class="com.nych.entity.User"> <constructor-arg name="name" value="张三"/> </bean>
-
测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user.getName());
System.out.println("user == user2: " + (user == user2));
}
}
结果:
- spring默认单例模式
再次新建实体类UserTwo
public class UserTwo {
private String name;
public UserTwo() {
System.out.println("UserTwo被创建了!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
在spring容器中注册
<bean id="userTwo" class="com.nych.entity.UserTwo"/>
再次运行测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user.getName());
System.out.println("user == user2: " + (user == user2));
}
}
结果:
**分析结果:**即使没有调用UserTwo,UserTwo的对象也会被创建。
- 小节总结
- spring默认单例模式
- 在配置文件被加载时,容器中管理的类对象就被创建了(初始化了)
5. Spring配置
5.1 别名
<!-- name对应bean的id, alias:别名 -->
<alias name="user" alias="newUser"/>
使用别名获取bean:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//可以使用别名获取对象,也可以使用原名获取对象
User user = (User) context.getBean("newUser");
}
}
5.2 Bean配置
-
id: bean的唯一标识,相当于对象名
-
class: bean所对应的类的全限定名(包名+类名)
-
name: 也是别名, 而且name可以同时起多个别名, 用(逗号/空格/分号..)分隔
-
其他配置会在后续学习中使用时讲解
<bean id="userTwo" class="com.nych.entity.UserTwo" name="two, 2">
<property name="name" value="李四"/>
</bean>
5.3 import
-
一般用于团队开发,可以引入别的配置文件
- 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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 引入其他的配置文件 --> <import resource="beans1.xml"/> <import resource="beans2.xml"/> </beans>
6. 依赖注入 DI
6.1 构造器注入
第四节已经学习过
6.2 Set方式注入【重点】
- 依赖注入: 使用set方法注入
- 依赖: bean对象的创建依赖于容器
- 注入: bean中的所有属性,有容器来注入
6.2.1 搭建测试环境:
-
复杂类型
public class Address { private String address; //getter setter toString...省略 }
-
实体类
public class Student { private String name; private Address address; private String[] books; private List<String> hobbies; private Map<String, String> card; private Set<String> games; private Properties info; //Getter Setter toString...省略 }
-
spring配置文件(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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="student" class="com.nych.entity.Student"> <!-- 1. 普通值注入 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()); } }
6.2.2 各种数据类型的注入方法
- applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.nych.entity.Address">
<property name="address" value="邯郸"/>
</bean>
<bean id="student" class="com.nych.entity.Student">
<!-- 1. 普通值注入 value -->
<property name="name" value="张三"/>
<!-- 2. bean注入 ref -->
<property name="address" ref="address"/>
<!-- 3. 数组 -->
<property name="books">
<array>
<value>《数据结构与算法》</value>
<value>《深入了解计算机底层原理》</value>
<value>《深入了解JVM虚拟机》</value>
<value>《Java程序设计》</value>
</array>
</property>
<!-- 4. list -->
<property name="hobbies">
<list>
<value>抽烟</value>
<value>喝酒</value>
<value>烫头</value>
</list>
</property>
<!-- 5. map -->
<property name="card">
<map>
<entry key="IdCard" value="1201303013213546"/>
<entry key="bankCard" value="06243768543962103"/>
<entry key="foodCard" value="202073"/>
</map>
</property>
<!-- 6. set -->
<property name="games">
<set>
<value>LoL</value>
<value>PUBG</value>
<value>CS:GO</value>
</set>
</property>
<!-- 7. properties -->
<property name="info">
<props>
<prop key="studentId">200809019000</prop>
<prop key="sex">男</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
<!-- 8. null -->
<!--
<property name="xxx">
<null/>
</property>
-->
</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.toString());
}
}
- 结果
6.3 扩展
-
p命名空间方法
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- p命名空间注入,相当于property --> <bean id="user" class="com.nych.entity.User" p:name="张三"/> </beans>
注意: 需要在beans中导入p的依赖:
xmlns:p="http://www.springframework.org/schema/p"
-
c命名空间方法
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- p命名空间注入,相当于property --> <bean id="user" class="com.nych.entity.User" p:name="张三"/> <!-- c命名空间注入, 相当于使用有参构造注入:constructor-arg --> <bean id="user2" class="com.nych.entity.User" c:name="李四"/> </beans>
注意:需要在beans标签中导入:
xmlns:c="http://www.springframework.org/schema/c"
-
测试
@Test public void Test(){ ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml"); User user1 = (User) context.getBean("user"); //用获取的对象的类的反射做第二个参数可以避免强转 User user2 = context.getBean("user2", User.class); System.out.println(user1.toString()); System.out.println(user2.toString()); }
-
结果
7. Bean的作用域
- scope参数:
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
- Spring默认单例模式
<!-- scope:作用域,默认单例 默认:scope="singleton" -->
<bean id="user" class="com.nych.entity.User">
<property name="name" value="张三"/>
</bean>
测试:
@Test
public void Test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
//每次get到的都是同一个对象
User user1 = (User) context.getBean("user");
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); //true
}
结果为 true
- prototype: 原型模式
<bean id="user" class="com.nych.entity.User" scope="prototype">
<property name="name" value="张三"/>
</bean>
测试:
@Test
public void Test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("userBeans.xml");
//每次get都会获得新的对象
User user1 = (User) context.getBean("user");
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); //false
}
结果为 false
- 其余的request、session、application,这些都是在Web开发中使用!
8. Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻址,并自动给bean装配属性
Spring有三种装配方式
- 在xml中显示的装配
- 在java中显示的装配
- 隐式的自动装配
8.1 搭建测试环境
-
实体类
public class Dog { public void bark(){ System.out.println("汪汪汪..."); } }
public class Cat { public void bark(){ System.out.println("喵喵喵..."); } }
public class Human { private String name; private Cat cat; private Dog dog; //getter and setter and toString...省略 }
-
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 id="cat" class="com.nych.entity.Cat"/> <bean id="dog" class="com.nych.entity.Dog"/> <bean id="human" class="com.nych.entity.Human"> <property name="name" value="张三"/> <property name="cat" ref="cat"/> <property name="dog" ref="dog"/> </bean> </beans>
-
环境测试
public class MyTest { @Test public void Test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Human human = context.getBean("human", Human.class); human.getCat().bark(); //喵喵喵... human.getDog().bark(); //汪汪汪... } }
8.2 ByName自动装配
-
在bean标签中添加
autowire="byName"
<?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="cat" class="com.nych.entity.Cat"/> <bean id="dog" class="com.nych.entity.Dog"/> <bean id="human" class="com.nych.entity.Human" autowire="byName"> <property name="name" value="张三"/> </bean> </beans>
- cat 和 dog的id必须与human类中的setter方法中set后面的变量名相同,否则会报错
-
测试
@Test public void Test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Human human = context.getBean("human", Human.class); human.getCat().bark(); //喵喵喵... human.getDog().bark(); //汪汪汪... }
8.3 ByType
-
在bean标签中添加
autowire="byType"
<?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"> <!--使用byType,如果没有其他用处,id可以省略 --> <bean id="cat111" class="com.nych.entity.Cat"/> <bean class="com.nych.entity.Dog"/> <bean id="human" class="com.nych.entity.Human" autowire="byType"> <property name="name" value="张三"/> </bean> </beans>
- 根据属性类型在上下文中匹配相同类型
- 即使不写id也可以装配成功
- 但上下文中类型必须唯一
-
测试
@Test public void Test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); Human human = context.getBean("human", Human.class); human.getCat().bark(); human.getDog().bark(); }
8.4 注解实现自动装配
- jdk1.5 开始支持注解, spring2.5开始支持注解
使用注解需要:
-
导入约束:
xmlns:context="http://www.springframework.org/schema/context" https://www.springframework.org/schema/context/spring-context.xsd
-
注解的支持:
<context:annotation-config/>
以测试环境代码为例:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注解支持 -->
<context:annotation-config/>
<bean id="cat" class="com.nych.entity.Cat"/>
<bean id="cat2" class="com.nych.entity.Cat"/>
<bean id="dog" class="com.nych.entity.Dog"/>
<bean id="human" class="com.nych.entity.Human"/>
</beans>
@AutoWired
- 可以用在成员变量上,也可以用在set方法上
- 首先@AutoWired会根据类型查找,如果有多个相同类型,则按名字查找(id与变量名相同)
package com.nych.entity;
import org.springframework.beans.factory.annotation.Autowired;
public class Human {
private String name;
@Autowired //自动装配
@Qualifier(value="cat2") //手动指定一个bean的ID
private Cat cat;
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
@Autowired //自动装配
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Human{" +
"name='" + name + '\'' +
", cat=" + cat +
", dog=" + dog +
'}';
}
}
@Qualifier(value=“xxx”)
-
如果@AutoWird无法正确的装配,则可以手动指定一个对象名(bean的id),根据这个名字在上下文中找
-
可以和@AutoWird配合使用
@Resource
- java的注解,也可以实现自动装配
- 可以根据byName查找,如果没有相同name,则按照byTyep查找
- @Resource(name=“xxx”) 指定要装配的bean的id
public class Human {
private String name;
@Resource
private Cat cat;
@Resource(name="dog123") //指定要装配的bean
private Dog dog;
//getter and setter and toString ...省略
}
9. 使用注解开发
-
在spring 4以后,要使用注解开发,需要导入aop的包
-
添加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 https://www.springframework.org/schema/context/spring-context.xsd"> </beans>
-
注解的支持
<context:annotation-config/>
9.1 bean
**@Component : ** 组件,放在类上,然后在xml中使用<context:component-scan base-package="com.nych.entity"/>
就可以被spring管理了;
//@Component 相对于在容器中管理该类的对象 <bean id="user" class="com.nych.entity.User" />
@Component
public class User {
private int id;
private String name;
//getter and setter toString... 省略
}
该注解需要在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
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注解支持 -->
<context:annotation-config/>
<!-- 在指定的包中扫描组件 -->
<context:component-scan base-package="com.nych.entity"/>
</beans>
测试:
public class MyTest {
@Test
public void test01(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = context.getBean("user", User.class);
user.setId(1);
user.setName("张三");
System.out.println(user.toString()); //输出结果: User{id=1, name='张三'}
}
}
9.2 属性如何注入
**@Value(“xxx”) : ** 值注入;
@Component
public class User {
@Value("2")
private int id;
@Value("李四")
private String name;
//getter and setter toString... 省略
}
测试:
@Test
public void test02(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user.toString()); //输出 User{id=2, name='李四'}
}
9.3 衍生的注解
在web开发中,在mvc三层架构中,有几个和@Component注解功能一样的注解
- dao层 @Repository
- service层 @Service
- controller层 @Controller
功能是相同的,都是将某个类在spring中管理
9.4 自动装配
@AutoWired
@Qualifier(value=“xxx”)
@Resource
**@Nullable : ** 字段标注了这个注解,那么这个字段可以为空
9.5 作用域
@Scope(“prototype / singleton”)
@Component
@Scope("prototype")
public class User {
@Value("2")
private int id;
@Value("李四")
private String name;
}
9.6 总结
- 使用注解,代码量少,但维护复杂,不方便理解
- 使用xml,适用于任何场景功能,方便维护
10. 使用Java配置spring
- 完全使用java来进行spring的配置,不需要xml文件
- JavaConfig在spring4之后推荐使用
测试实体类:
@Component
public class User {
@Value("AA")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
配置类:
- @Configuration 表示这个一个配置类
- @Impor 引入另一个配置类
//@Configuration 表示这个类是一个配置类,表示这个类的功能和xml配置文件是一样的
@Configuration
@ComponentScan("com.nych.entity") //扫描该包中的组件(@component)
@Import(SpringConfig2.class) //引入另一个配置类
public class SpringConfig {
//@Bean表示注册了一个bean,相当于xml文件中的bean标签
@Bean
public User getUser(){ //getUser()这个方法名相当于bean的id
return new User(); //返回值相当于bean标签中的class属性
}
}
测试:
public class MyTest {
@Test
public void test01(){
//如果使用java类配置spring,使用AnnotationConfigApplicationContext()传入配置类的class对象来获取容器
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
//getBean() 使用方法名作为参数1,对象类型的class对象作为参数2
User user = context.getBean("getUser", User.class);
System.out.println(user.getName());
//输入 AA
}
}
- 使用java方式配置,在SpringBoot中很常见
11. 代理模式
AOP的底层就是代理模式
代理模式分类:
- 静态代理
- 动态代理
代理相当于中介,婚庆公司这些会帮你做一些事情
11.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或抽象类来解决
- 真实角色:被代理的角色
- 代理角色: 代理真实角色,代理真实角色后,一般会做一些附属操作
- 客户:访问代理对象的人
实现步骤:
-
接口
//出租 public interface Rent { public void rent(); }
-
真实角色
//房东 public class Landlord implements Rent{ @Override public void rent() { System.out.println("房东要出租房屋!"); } }
-
代理角色
package com.nych.demo01; public class Proxy implements Rent{ private Landlord landlord; public Proxy() { } public Proxy(Landlord landlord) { this.landlord = landlord; } //出租 @Override public void rent() { landlord.rent(); for (int i = 0; i < 3; i++) { seeHouse(); } chargeFees(); signContract(); } //看房 public void seeHouse(){ System.out.println("中介带着去看房!"); } //收费 public void chargeFees(){ System.out.println("收中介费!"); } //签合同 public void signContract(){ System.out.println("签租赁合同!"); } }
-
客户端访问代理角色
//客户,租客 public class Client { public static void main(String[] args) { //房东要出租房子 Landlord landlord = new Landlord(); //交个中介,但是中介还有一些附加操作 (代理) Proxy proxy = new Proxy(landlord); //找中介租房 proxy.rent(); } }
结果:
代理模式的好处:
- 可以使真实角色的工作简单纯粹,不用去关心其他业务
- 公共业务就交给了代理角色,实现了业务的分工
- 公共业务扩展时,方便管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会增加,开发效率变低
11.2 静态代理加深理解
- 接口
public interface UserService {
void addUser();
void deleteUser();
void updateUser();
void getUser();
}
- 实现
package com.nych.demo02;
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("添加一个用户!");
}
@Override
public void deleteUser() {
System.out.println("删除一个用户!");
}
@Override
public void updateUser() {
System.out.println("修改一个用户!");
}
@Override
public void getUser() {
System.out.println("获取用户!");
}
}
-
代理
- 比如需要加新功能,不要去修改源代码,而是使用代理
package com.nych.demo02; public class UserServiceProxy implements UserService{ private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Override public void addUser() { log("addUser"); userService.addUser(); } @Override public void deleteUser() { log("deleteUser"); userService.deleteUser(); } @Override public void updateUser() { log("updateUser"); userService.updateUser(); } @Override public void getUser() { log("getUser"); userService.getUser(); } //新功能, 比如打印日志... void log(String str){ System.out.println("日志: 使用了"+ str + "方法!"); } }
-
用户
package com.nych.demo02; public class Client { public static void main(String[] args) { UserService userService = new UserServiceImpl(); UserServiceProxy proxy = new UserServiceProxy(); proxy.setUserService(userService); proxy.deleteUser(); } }
11.3 动态代理
- 动态代理和静态代理角色是一样的
- 动态代理的代理类是动态生成的,不是我们直接写的
- 动态代理分为两大类:基于接口的动态代理, 基于类的动态代理
- 基于接口实现: JDK动态代理
- 基于类实现: cglib
- java字节码实现: Javassist
需要了解两个类: Proxy(代理) 、 InvocationHandler(调用处理程序)
12. AOP
什么是AOP
AOP(Aspect Oriented Programming) 意为:面向切面编程,通过预编译的方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP在Spring中的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志、安全、缓存、事务等等
- 切面(Aspect): 横切关注点,被模块化的特殊对象。它是一个类
- 通知(Advice): 切面必须要完成的工作。它是类中的一个方法
- 目标(Target): 被通知对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 切入点(PointCut): 切面通知执行的“地点” 的定义
- 连接点(JointPoint):与切入点匹配的执行点
导入包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
使用Spring的API接口实现
public interface UserService {
void add();
void delete();
void update();
void select();
}
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("查询一个用户");
}
}
public class Log implements MethodBeforeAdvice {
//method 要执行的目标对象的方法
//objects 参数(args)
//target 目标对象
@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 {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了 " +method.getName() + ",返回结果为:" + returnValue);
}
}
<?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="com.nych.service.UserServiceImpl"/>
<bean id="log" class="com.nych.log.Log"/>
<bean id="afterLog" class="com.nych.log.AfterLog"/>
<!-- 方式一: 使用原生spirng API 接口 -->
<!-- 配置AOP 需要在beans中导入约束 -->
<aop:config>
<!-- 切入点 expression:表达式 execution(要执行的位置!* * * * *) -->
<aop:pointcut id="pointcut" expression="execution(* com.nych.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<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 = context.getBean("userService", UserService.class);
userService.add();
}
}
自定义实现AOP
public class CustomizePointCut {
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: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="com.nych.service.UserServiceImpl"/>
<!-- 方式二: 自定义 -->
<bean id="diy" class="com.nych.customize.CustomizePointCut"/>
<aop:config>
<!-- 自定义切面, ref 要引用的类 -->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.nych.service.UserServiceImpl.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
- 使用方法一的测试测试
注解方式实现
@Aspect //标注为切面
public class AnnotationPointCut {
@Before("execution(* com.nych.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("方法执行前...");
}
@After("execution(* com.nych.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("方法执行后...");
}
@Around("execution(* com.nych.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("前-环绕通知");
point.proceed();
System.out.println("后-环绕通知");
//其他方法 输出签名
System.out.println(point.getSignature());
}
}
<?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="com.nych.service.UserServiceImpl"/>
<!-- 方式三: 注解 -->
<bean id="annotationPointCut" class="com.nych.annotation.AnnotationPointCut"/>
<!-- 开启注解支持; proxy-target-class="false" :jdk方式(默认); proxy-target-class="true": cglib -->
<aop:aspectj-autoproxy/>
</beans>
使用方式一的测试测试
13. 整合MyBaits:
导入jar包:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.8</version>
</dependency>
<!-- spring操作数据库需要 spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
13.1 回顾mybatis:
-
实体类
@Data public class User { private int id; private String name; private String pwd; }
-
核心配置文件
- db.properties
driver = com.mysql.cj.jdbc.Driver url = jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=ture&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username = root password = 123456
- mybatis-config.xml
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!-- mybatis的配置都可以在spring的配置中完成 --> <configuration> <properties resource="db.properties"/> <typeAliases> <package name="com.nych.entity"/> </typeAliases> <!--数据源被spring中的配置代替了--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!-- 注册mapper 被spring中的配置代替了 --> <mappers> <!--注册mapper的三种方法--> <!-- <mapper resource="com/nych/mapper/UserMapper.xml"/>--> <!-- <mapper class="com.nych.mapper.UserMapper"/>--> <package name="com.nych.mapper"/> </mappers> </configuration>
-
接口
public interface UserMapper { List<User> getUserList(); }
-
mapper.xml
<?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.nych.mapper.UserMapper"> <select id="getUserList" resultType="user"> select * from `user` </select> </mapper>
-
获取SqlSession对象的工具类
//获取SqlSession对象的工具类也被spring配置代替了 public class MybatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //使用mybatis的第一步 //创建sqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } //有了 SqlSessionFactory,我们可以从中获得 SqlSession 的实例. // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法. public static SqlSession getSession(){ SqlSession sqlSession = sqlSessionFactory.openSession(); return sqlSession; } }
-
测试
@Test public void test1(){ SqlSession sqlSession = MybatisUtil.getSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.getUserList(); for (User user : userList) { System.out.println(user); } }
13.2 Mybatis-spring
spring 可以配置原本在mybatis中配置的东西
- 配置数据源
- sqlSessionFactory
- sqlSessionTemplate
- 接口实现类()
- 测试
-
mybatis-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"> <!-- 使用spring的数据源 代替 mybatis的配置数据源 --> <!-- 使用spring提供的jdbc: org.springframework.jdbc.datasource.DriverManagerDataSource --> <!-- DataSource --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&a`mp;useUnicode=ture&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="nyc991019"/> </bean> <!-- sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 绑定Mybatis配置文件 mybatis可有可无 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- mapper.xml 的位置,代替在mybatis中配置mapper.xml --> <property name="mapperLocations" value="classpath:com/nych/mapper/*.xml"/> </bean> <!-- SqlSessionTemplate 就相当于是mybatis中的sqlSession --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <!-- 使用构造器注入 --> <!-- <constructor-arg index="0" ref="sqlSessionFactory"/>--> <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
-
applicationContext.xml
<?xml version="1.0" encoding="UTF8" ?> <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"> <!-- 引入数据源配置等固定配置 --> <import resource="mybatis-spring.xml"/> <!-- 方式一 --> <!-- UserMapper的实现类,相当于以前的业务层(service) --> <bean id="userMapperImpl" class="com.nych.mapper.UserMapperImpl"> <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/> </bean> <!-- 方式二 --> <bean id="userMapperImpl2" class="com.nych.mapper.UserMapperImpl2"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
-
UserMapperImpl
public class UserMapperImpl implements UserMapper{ private SqlSessionTemplate sqlSessionTemplate; public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSessionTemplate = sqlSessionTemplate; } @Override public List<User> getUserList() { UserMapper userMapper = sqlSessionTemplate.getMapper(UserMapper.class); return userMapper.getUserList(); } }
-
UserMapperImpl2
//方式二 public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{ @Override public List<User> getUserList() { SqlSession sqlSession = getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); return userMapper.getUserList(); } }
14. 声明式事务
14.1 事务回顾
-
把一系列的操作当成一个事务: 要么都成功,要么都失败!
-
确保事务的完整性一致性 等(ACID)
事务的ACID原则:
- 原子性
- 一致性
- 隔离性
- 持久性
14.2 spring中的事务管理
-
声明式事务: AOP
<!-- 配置声明式事务 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--<constructor-arg ref="dataSource" />--> <property name="dataSource" ref="dataSource"/> </bean> <!-- 结合AOP实现事务的织入 --> <!-- 事务通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 给哪些方法配置事务; propagation:传播特性 REQUIRED:默认,支持当前事务,如果当前没有事务,就新建一个事务。--> <tx:attributes> <tx:method name="getUserList" propagation="REQUIRED"/> <tx:method name="deleteUser" propagation="REQUIRED"/> <!--全部方法都创建事务--> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置事务切入 --> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.nych.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config>
-
编程式事务: 需要在代码中进行事务的管理