说在前面
github地址
https://github.com/Michilay/Spring
spring系列视频如下
Spring三天入门(一):IOC、注入以及注解
Spring三天入门(二):代理模式,动态代理,spring实现AOP,切入
Spring三天入门(三):spring整合mybatis,aop事务切入
spring简介
传统的ssh指的是struct2+spring+hibernate
现在通常使用ssm指的是spring+spring mvc+mybatis
spring相当于是个大杂烩,整合了现有的技术框架
- spring是一个开源的免费容器
- spring是一个轻量级的非入侵的框架,引入了spring不会对原来的项目有任何影响
- 控制反转IOC和面向切面编程AOP
- 支持事务的处理,声明式事务因为AOP。对框架整合的支持
springIOC思想
idea创建一个maven项目,导入项目依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
导入spriing-webmvc一个包会有多个依赖项,junit用来测试
dao层写用户和用户实现
package com.michilay.dao;
public interface UserDao {
void getUser();
}
package com.michilay.dao;
public class UserDaoImpl implements UserDao{
@Override
public void getUser() {
System.out.println("默认获取用户的数据");
}
}
service写业务类和业务实现类
package com.michilay.service;
public interface UserService {
void getUser();
}
package com.michilay.service;
import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
新建测试看看能不能走通
import com.michilay.dao.UserDaoImpl;
import com.michilay.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
public class MyTest {
@Test
public void test(){
//用户实际上调用业务层,dao层不需要接触
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
默认获取用户的数据
进程已结束,退出代码为 0
这样是完全没问题的
但是当我们需要有新的持久层需求的时候,比如这时候有mysql的持久层,新加了一个UserDaoMySqlImpl.class
package com.michilay.dao;
public class UserDaoMySqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("mysql获取用户数据");
}
}
如果要和业务层链接的话,就得在业务层修改mysql的实例化
package com.michilay.service;
import com.michilay.dao.UserDao;
import com.michilay.dao.UserDaoImpl;
import com.michilay.dao.UserDaoMySqlImpl;
public class UserServiceImpl implements UserService{
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
这样就很麻烦,用户的需求可能会影响我们原来的代码,如果代码量大的话,修改一次的成本就会十分昂贵,需要在代码里面修改,大大降低了维护性,这就完全违反了IOC,那么我们要怎么去修改呢
使用一个Set接口实现
private UserDao userDao;
//利用set进行动态实现值的注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
使用了set注入,程序不再具有主动性,而是成为了被动的接受对象
@Test
public void test(){
//用户实际上调用业务层,dao层不需要接触
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(new UserDaoImpl());
//userService.setUserDao(new UserDaoMySqlImpl());
userService.getUser();
}
想获取什么样子的dao就可以获取什么样的,这种思想从本质上解决的IOC的问题,可以更加专注于业务层,降低耦合性。
IOC的本质
DI(依赖注入)是实现ioc的一种方法,依赖对象的方式反转了。是哦那个Bean的xml来配置,将配置文件放在Bean内,可以将定义信息和实现分离,之后可以使用注解,达到零配置
但是这样仍然有点麻烦,如果是内部的对象好说,但是外部的其他类型的非java对象该怎样导入进来呢?
spring基本配置
创建一个新的模块
创建pojo.hello的class,声明一个str对象,自动生成get,set和tostring方法
package com.michilay.pojo;
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;
}
}
这里spring提供beans来创建对象,在配置文件中创建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">
<!-- 使用spring来创建对象,在spring这些都成为Bean-->
<bean id="hello" class="com.michilay.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
测试类如下
@Test
public void test(){
//获取spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//对象都交给spring管理,使用对象直接取出
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
依赖倒转原则,使用set来注入,有主动的程序的创建对象变成了被动的接受对象
那么回到刚才的项目,如果是多个sql需求我们应该怎么改动呢?
在配置文件里下如下内容
<bean id="mysqlImpl" class="com.michilay.dao.UserDaoMySqlImpl"/>
<bean id="impl" class="com.michilay.dao.UserDaoImpl"/>
<bean id="userServiceImpl" class="com.michilay.service.UserServiceImpl">
<!-- ref:引用spring容器中创建好的对象-->
<property name="userDao" ref="mysqlImpl"/>
</bean>
如果是对象属性,就是用ref,如果是普通属性,就用value
实例化的对象,直接交给spring以每一个bean的形式创建,将id,set注入给业务层实现类,测试类如下
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
UserServiceImpl userServiceImpl= (UserServiceImpl) context.getBean("userServiceImpl");
userServiceImpl.getUser();
}
IOC就是由spring来创建,管理,装配!
ioc创建对象的方式
- 默认使用无参构造创建对象
private String name;
public User(){
System.out.println("user的无参构造");
}
<bean id="user" class="com.michilay.pojo.User">
<property name="name" value="michilay"/>
</bean>
- 如果没有无参构造函数的话
- 第一种方式,下标赋值
private String name;
public User(String name){
this.name = name;
}
<!-- 下表赋值-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg index="0" value="michilay"/>
<!-- <property name="name" value="michilay"/>-->
</bean>
- 第二种方式,通过类型创建
<!-- 不建议使用-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg type="java.lang.String" value="michilay"/>
</bean>
- 第三种方式,直接通过参数名来设置
<!-- 直接通过参数名来设置-->
<bean id="user" class="com.michilay.pojo.User">
<constructor-arg name="name" value="michilay"/>
</bean>
注意:在context获取上下文对象之后,Beans里面的对象就已经都被创建了,而且这个对象只有一个地址,不管它被指向其他对象多少次
spring配置深入
spring别名
使用getBean拿到的对象是一个
bean属性
一个对象就是一个Bean,
id = 对象名名,相当于spring根据这个名字获得配置对象,id有唯一性
class = new 的对象,对象所在的全路径,bean对象的全限定名:报名+属性
name 对象名字,别名,而且name可以同时取多个
property 相当于给对象设置值
import
import适用于团队开发,可以将多个配置文件,导入为一个总的配置文件
<import resource="Beans1.xml"/>
<import resource="Beans2.xml"/>
<import resource="Beans3.xml"/>
各种beans里面的对象name相同,就会选取一个,如果后来的name相同,就用后面bean对象里的属性值覆盖前者
注入
构造器注入
构造器注入在ioc创建对象的方式中已有提到
set注入[重要]
依赖注入,也就是set注入,依赖注入分为依赖和注入,所谓依赖,就是bean创建的对象依赖于容器;注入,就是bean对象中的所有属性,由容器来注入
- 测试对象student
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; //还有set,get方法和tostring
- 复杂对象
public class Address { private String address; //还有set,get方法和tostring
- 各种类型对象的注入方式
<bean id="address" class="com.michilay.pojo.Address"> <property name="address" value="湖北"></property> </bean> <bean id="student" class="com.michilay.pojo.Student"> <!-- 普通值注入,value--> <property name="name" value="michilay"/> <!-- Bean注入,value--> <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>吃饭</value> </list> </property> <!-- map--> <property name="card"> <map> <entry key="身份证" value="123214123"/> <entry key="银行卡" value="92324322341"/> </map> </property> <!-- set--> <property name="games"> <set> <value>LOL</value> <value>MC</value> <value>WAR3</value> </set> </property> <!-- null--> <property name="wife"> <null/> </property> <!-- properties--> <property name="info"> <props> <prop key="id">200123</prop> <prop key="sex">男</prop> <prop key="username">张三</prop> </props> </property> </bean>
拓展方式注入
User实体类
public class User {
private String name;
private int age;
//加上get,set和tostring以及无参构造和有参构造
-
p命名空间
对简单的对象进行注入
在beans标签里添加下面一行
-
c命名空间
对构造器进行注入
添加标签
bean的作用域
-
单例模式
默认使用的就是单例模式,可以添加scope="singleton"属性
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user1 = context.getBean("user2", User.class); User user = context.getBean("user2", User.class); System.out.println(user == user1); }
true
-
原型模式
每次从容器中get的时候,都会产生一个新的对象,scope=“prototype”
@Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); User user1 = context.getBean("user2", User.class); User user = context.getBean("user2", User.class); System.out.println(user1.hashCode()); System.out.println(user.hashCode()); System.out.println(user == user1); }
846254484
592983282
false
- 其余的request、session、application存在于web开发中
Bean自动装配
自动装配是Spring满足bean依赖的一种方式
spring会在上下文中寻找,并自动给bean装配属性!
在spring中有三种装配的方式
- 在xml中显示的配置
- 在java中显示配置
- 隐式的自动装配
people实体类
public class People {
private Cat cat;
private Dog dog;
private String name;
//set和get以及tostring方法
byName自动装配
会自动在容器上下文中查找和自己对象set方法后面的值对应的bean的id,要保证所有bean的id唯一
<bean id="cat" class="com.michilay.pojo.Cat"/>
<bean id="dog" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byName">
<property name="name" value="michilay"/>
</bean>
byType自动装配
会自动在容器上下文中查找和自己对象属性类型对应的bean,必须保证class全局唯一
<bean id="cat" class="com.michilay.pojo.Cat"/>
<bean id="do11g" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byType">
<property name="name" value="michilay"/>
</bean>
注解
使用注解自动装配
在spring4之后,使用注解开发必须要导入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
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方法上使用
注解是通过反射的原理实现,可以不需要set方法,但是需要将改属性在IOC容器中存在
当注入在IOC容器中的类型只有一个的时候,按照byType进行匹配,默认也是这样;
当注入在IOC容器中存在多个同一类型的对象时,按照byName进行匹配。
如果它的required属性为false,说明这个对象可以为null,否则不允许为空
@Nullable
字段标记了这个注解,改注解就可以为null
@Qualifier(value=“xxx”)
如果@Autowired装配的对象比较复杂,就可以使用@Qualifier设定和它id相同的值对应,指定一个唯一的bean
<bean id="cat111" class="com.michilay.pojo.Cat"/>
<bean id="dog" class="com.michilay.pojo.Dog"/>
<bean id="people" class="com.michilay.pojo.People" autowire="byType"/>
public class People {
@Autowired
@Qualifier(value = "cat111")
private Cat cat;
@Autowired
private Dog dog;
private String name;
@Resource
这个一个java的注解,但是也能起到Autowired的作用,它会先和名字匹配,再和类型匹配,它也可以取value值
注解开发
在使用下面注解之前,需要在配置文件里添加包扫描器
<context:component-scan base-package="com.michilay"/>
<context:annotation-config/>
@Component
组件,放在类的上面,说明这个类被spring给管理了,相当于这一行代码
@Value
只有类的托管还不够,类的属性就由Value注解,它相当于这一行代码,如果这个属性有set方法,把Value注解放在set方法效果相同**(优先级高于类属性)**
@Component
public class User {
public String name = "Michilay";
@Value("123")
public void setName(String name) {
this.name = name;
}
}
@Repository
@Service
@Controller
这三个注解和component是一样效果,只不过它们分别用在web开发中不同的位置
dao层用repository注解,service层用service注解,controller层用controller注解
@Scope
作用域注解,和bean的作用域这一小节效果相同,注解后面加上(“singleton”)改属性
注解总结
注解相对于xml方便,当类和属性不多的时候
注解不是自己的类使用不了,维护更加负责
开发java最常用的组合就是
xml用来管理bean,注解只负责完成属性的注入
使用注解代替xml配置文件
完全不使用spring的xml配置文件,把所有的内容全部交给java来做
javaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能
这里新建个config包写一个config类
package com.michilay.config;
import com.michilay.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.michilay.pojo")
@Import(myConfig2.class)
public class myConfig {
@Bean
public User getUser(){
return new User();//就是返回要注入到bean的对象
}
}
@Configuration
代表这个类是一个注解类,相当于beans标签
@ComponentScan
是一个包扫描器标签
@Import
相当于import标签,当有多个配置文件的时候导入
@Bean
相当于Bean标签,方法名就是bean中的id属性,返回值就是bean中的class属性
pojo包中的User实体类
package com.michilay.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//说明这个类被spring接管了,注册到了IOC中
//@Component
public class User {
private String name;
public String getName() {
return name;
}
@Value("michilay")
public void setName(String name) {
this.name = name;
}
}
测试类
import com.michilay.config.myConfig;
import com.michilay.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MyTest {
@Test
public void test(){
//如果完全使用配置类,就只能用这个上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
User getUser = context.getBean("getUser", User.class);
System.out.println(getUser.getName());
}
}
这种没有xml的配置方式,在springboot中相当常见