简介
Spring是分层的 Java SE/EE应用** full-stack(全栈式) **轻量级开源框架。 <br />全栈是说整合了其它主流框架,并且支持三层开发规范(spring mvc + spring + spring jdbc template)
两大核心:以 IOC(Inverse Of Control:控制反转)和 AOP(Aspect Oriented Programming:面向 切面编程)为内核。
控制反转指容器来创建对象。AOP指面向切面编程,在不修改源码的情况下对方法进行增强,底层实现是动态代理。
Spring的优点
1)方便解耦,简化开发 Spring就是一个容器,可以将所有对象创建和关系维护交给Spring管理 什么是耦合度?对象之间的关系,通常说当一个模块(对象)更改时也需要更改其他模块(对象),这就是 耦合,耦合度过高会使代码的维护成本增加。要尽量解耦
2)AOP编程的支持 Spring提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
3)声明式事务的支持 通过配置完成事务的管理,无需手动编程
4)方便测试,降低JavaEE API的使用 Spring对Junit4支持,可以使用注解测试
5)方便集成各种优秀框架 不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持
其实最重要的就是第一条。解耦是最核心的。一条解耦的基本思路就是降低编译期依赖,尽可能的消除new关键字。很常见的解决手段就是使用配置文件+反射的组合来解耦。
IOC简介
把对象的创建和销毁交给容器
自己写个最最最简单的IOC容器
第一步 搞个xml存我们要托管的bean
<beans>
<bean id="userDao" class="com.ning.dao.impl.UserDaoImpl"></bean>
</beans>
第二步 做个类把这些(个)bean装载到Map里。
public class BeanFactory {
private static Map<String, Object> iocMap = new HashMap<>();
static {
try {
final InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
final Document doc = new SAXReader().read(resourceAsStream);
String xpath = "//bean";
final List<Element> list = doc.selectNodes(xpath);
for (Element element : list) {
final String id = element.attributeValue("id");
final String aClass = element.attributeValue("class");
final Object o = Class.forName(aClass).getDeclaredConstructor().newInstance();
iocMap.put(id, o);
}
} catch (DocumentException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static Object getBean(String beanId) {
return iocMap.get(beanId);
}
}
Spring快速入门
先引入pom
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
再写一个配置文件,约定俗成地,大家都叫它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="userDao" class="com.ning.dao.impl.UserDaoImpl"></bean>
</beans>
然后写个测试类充当Service层
@Test
public void test2() {
final ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
final IUserDao userDao = (IUserDao)classPathXmlApplicationContext.getBean("userDao");
userDao.save();
}
可以看出,和我们写的玩具一模一样
Spring相关API
最重要的是BeanFactory和ApplicationContext
BeanFactory Vs ApplicationContext
BeanFactory是 IOC 容器的核心接口,它定义了IOC的基本功能。
特点:在第一次调用getBean()方法时,创建指定对象的实例
BeanFactory beanFactory
= new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
applicationContext代表应用上下文对象,可以获得spring中IOC容器的Bean对象。 <br /> 特点:在spring容器启动时,加载并创建所有对象的实例
ApplicationContext app =
new ClassPathXmlApplicationContext("applicationContext.xml");
常用实现类
1. ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种。
2. FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
3. AnnotationConfigApplicationContext
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
常用方法
1. Object getBean(String name);
根据Bean的id从容器中获得Bean实例,返回是Object,需要强转。
2. <T> T getBean(Class<T> requiredType);
根据类型从容器中匹配Bean实例,当容器中相同类型的Bean有多个时,则此方法会报错。
3. <T> T getBean(String name,Class<T> requiredType);
根据Bean的id和类型获得Bean实例,解决容器中相同类型Bean有多个情况。
配置文件详解
其实主要就是说Bean
<bean id="" class="" scope="" init-method="" destroy-method=""></bean>
* 用于配置对象交由Spring来创建。
* 基本属性:
id:Bean实例在Spring容器中的唯一标识
class:Bean的全限定名
* 默认情况下它调用的是类中的 无参构造函数,如果没有无参构造函数则不能创建成功。
init-method:指定类中的初始化方法名称
destroy-method:指定类中销毁方法名称
scope
默认是单例的。其它值如下所示:
1. 当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
Bean的生命周期:
对象创建:当应用加载,创建容器时,对象就被创建了
对象运行:只要容器在,对象一直活着
对象销毁:当应用卸载,销毁容器时,对象就被销毁了
2. 当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
Bean的生命周期:
对象创建:当使用对象时,创建新的对象实例
对象运行:只要对象在使用中,就一直活着
对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean实例化三种方式
无参构造方法实例化
工厂静态方法实例化
工厂普通方法实例化
工厂静态方法实例化
应用场景
依赖的jar包中有个A类,A类中有个静态方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B对象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1方法,直接从IOC容器获得。
public class StaticFactoryBean {
public static UserDao createUserDao(){
return new UserDaoImpl();
}
}
<bean id="userDao" class="com.lagou.factory.StaticFactoryBean"
factory-method="createUserDao" />
工厂普通方法实例化
应用场景
依赖的jar包中有个A类,A类中有个普通方法m1,m1方法的返回值是一个B对象。如果我们频繁使用B象,此时我们可以将B对象的创建权交给spring的IOC容器,以后我们在使用B对象时,无需调用A类中的m1 方法,直接从IOC容器获得。
public class DynamicFactoryBean {
public UserDao createUserDao(){
return new UserDaoImpl();
}
}
<bean id="dynamicFactoryBean" class="com.lagou.factory.DynamicFactoryBean"/>
<bean id="userDao" factory-bean="dynamicFactoryBean" factorymethod="createUserDao"/>
依赖注入
它是 Spring 框架核心 IOC 的具体实现。 在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。
例如:业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用Spring 之后,就让 Spring 来维护了。
简单的说,就是通 过框架把持久层对象传入业务层,而不用我们自己去获取。
构造函数依赖注入
public class UserServiceImpl implements UserService {
private UserDao userDao;
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<!--<constructor-arg index="0" type="com.lagou.dao.UserDao" ref="userDao"/>-->
<constructor-arg name="userDao" ref="userDao"/>
</bean>
Setter依赖注入
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
如果想注入数字/字符串,把ref变成value
P命名空间注入
本质也是set方法注入
xmlns:p="http://www.springframework.org/schema/p"
先得引用p命名空间
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl"/>
<bean id="userService"
class="com.lagou.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
普通数据类型注入
public class User {
private String username;
private String age;
public void setUsername(String username) {
this.username = username;
}
public void setAge(String age) {
this.age = age;
}
}
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="jack"/>
<property name="age" value="18"/>
</bean>
集合依赖注入
List
public class UserDaoImpl implements UserDao {
private List<Object> list;
public void save() {
System.out.println(list);
System.out.println("保存成功了...");
}
}
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="jack"/>
<property name="age" value="18"/>
</bean>
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
<property name="list">
<list>
<value>aaa</value>
<ref bean="user"></ref>
</list>
</property>
</bean>
Set
public class UserDaoImpl implements UserDao {
private Set<Object> set;
public void setSet(Set<Object> set) {
this.set = set;
}
public void save() {
System.out.println(set);
System.out.println("保存成功了...");
}
}
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="jack"/>
<property name="age" value="18"/>
</bean>
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
<property name="set">
<set>
<value>bbb</value>
<ref bean="user"></ref>
</set>
</property>
</bean>
Array
public class UserDaoImpl implements UserDao {
private Object[] array;
public void setArray(Object[] array) {
this.array = array;
}
public void save() {
System.out.println(Arrays.toString(array));
System.out.println("保存成功了...");
}
}
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="jack"/>
<property name="age" value="18"/>
</bean>
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
<property name="array">
<array>
<value>ccc</value>
<ref bean="user"></ref>
</array>
</property>
</bean>
Map
public class UserDaoImpl implements UserDao {
private Map<String, Object> map;
public void setMap(Map<String, Object> map) {
this.map = map;
}
public void save() {
System.out.println(map);
System.out.println("保存成功了...");
}
}
<bean id="user" class="com.lagou.domain.User">
<property name="username" value="jack"/>
<property name="age" value="18"/>
</bean>
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
<property name="map">
<map>
<entry key="k1" value="ddd"/>
<entry key="k2" value-ref="user"></entry>
</map>
</property>
</bean>
Properties
public class UserDaoImpl implements UserDao {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
public void save() {
System.out.println(properties);
System.out.println("保存成功了...");
}
}
<bean id="userDao" class="com.lagou.dao.impl.UserDaoImpl">
<property name="properties">
<props>
<prop key="k1">v1</prop>
<prop key="k2">v2</prop>
<prop key="k3">v3</prop>
</props>
</property>
</bean>
配置文件模块化
可以拆解配置文件。拆分要么按层,要么按模块。
同层就这样
ApplicationContext act =
new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");
主从就这样(更常用)
<import resource="applicationContext-xxx.xml"/>
注意:
同一个xml中不能出现相同id的bean,如果出现会报错
多个xml如果出现相同id的bean,不会报错,但是后加载的会覆盖前加载的bean
使得spring能引入jdbc.properties
观察这个文件即可
<?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
http://www.springframework.org/schema/context/spring-context.xsd"
>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"/>
</bean>
<bean id="accountDao" class="com.ning.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"/>
</bean>
<bean id="accountService" class="com.ning.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
</beans>
注解开发
Java11以后移除了javax扩展,要是非得用就需要
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
如果使用注解,需要指定一个路径让spring去扫描
<!--注解的组件扫描-->
<context:component-scan base-package="com.lagou"></context:component-scan>
新注解
使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置如下:
* 非自定义的Bean的配置:<bean>
* 加载properties文件的配置:<context:property-placeholder>
* 组件扫描的配置:<context:component-scan>
* 引入其他文件:<import>
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12718322/1634893271356-fa119a26-7619-46e0-b20f-45ceb2c6515c.png#clientId=u45f5b138-5fcf-4&from=paste&height=249&id=u664a3313&margin=%5Bobject%20Object%5D&name=image.png&originHeight=497&originWidth=1486&originalType=binary&ratio=1&size=139996&status=done&style=none&taskId=u3f8821ad-57fd-4a3a-b5d8-7bc242705f7&width=743)
示例
@PropertySource(value = "classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driverClassName}")
private String driverName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(value = "dataSource")
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
@Configuration
@ComponentScan(value = "com.ning")
@Import(value = DataSourceConfig.class)
public class SpringConfig {
@Bean(value = "queryRunner")
public QueryRunner getQueryRunner(@Autowired DataSource dataSource) {
return new QueryRunner(dataSource);
}
}
Spring整合Junit
在普通的测试类中,需要开发者手动加载配置文件并创建Spring容器,然后通过Spring相关API获得 Bean实例;如果不这么做,那么无法从容器中获得对象。
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService =
applicationContext.getBean(AccountService.class);
我们可以让SpringJunit负责创建Spring容器来简化这个操作,开发者可以直接在测试类注入Bean实 例;但是需要将配置文件的名称告诉它。
具体步骤
- 导入spring集成Junit的坐标
- 使用@Runwith注解替换原来的运行器
- 使用@ContextConfiguration指定配置文件或配置类
- 使用@Autowired注入需要测试的对象
- 创建测试方法进行测试
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(value = {"classpath:applicationContext.xml"}) 加载spring
核心配置文件
@ContextConfiguration(classes = {SpringConfig.class}) // 加载spring核心配置类
public class SpringJunitTest {
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class SpringJunitTest {
@Autowired
private AccountService accountService;
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class SpringJunitTest {
@Autowired
private AccountService accountService;
//测试查询
@Test
public void testFindById() {
Account account = accountService.findById(3);
System.out.println(account);
}
}