Spring框架基本原理
直接new对象, 当类不存在时, 代码就会编译错误 - 耦合性高
为了降低耦合性:(重点)
-
通过反射的方式, 传递的是类的全限定类名字符串
-
将全限定类名通过配置文件的方式读取到程序中
properties xml
配置文件写法: key = value -
解决方案:提供工厂类来解决, 单例模式
BeanFactory
/**
* 对象工厂
* 目的: 通过类的全限定类名, 获得指定的对象
**/
public class BeanFactory {
private static Properties props;
private static Map<String, Object> maps = new HashMap<>();
static {
try {
props = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
// 开始读取配置文件, 并且创建对象
Enumeration<Object> keys = props.keys();
while(keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String classPath = props.getProperty(key);
// 手动设置 多例模式 -> 单例
Object obj = Class.forName(classPath).newInstance();
maps.put(key, obj);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化错误");
}
}
public static Object getInstance(String className) {
// className - AccountDao - key
// 通过key 获得value - classPath
return maps.get(className);
}
}
bean.properties
AccountService = com.zzxx.service.impl.AccountServiceImpl
public class Client {
public static void main(String[] args) {
// AccountService as = new AccountServiceImpl();
for (int i = 0; i < 5; i++) {
AccountService as = (AccountService) BeanFactory.getInstance("AccountService");
System.out.println(as); // 相同
}
}
}
SpringIOC(实现依赖注入)
Inversion Of Control 反转控制
使用spring框架步骤
1、依赖jar包 spring-context
2、配置文件 <bean id= class= />
3、使用核心容器 ApplicationContext getBean
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">
<!-- 在核心容器中管理AccountService对象 -->
<bean id="accountService" class="com.zzxx.service.impl.AccountServiceImpl"/>
</beans>
public class Client {
public static void main(String[] args) {
// AccountService as = new AccountServiceImpl();
// 通过springIOC 来获得 accountService对象
ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
for (int i = 0; i < 5; i++) {
// 从容器中获得对象(单例模式)
AccountService as = (AccountService) ac.getBean("accountService");
System.out.println(as); // 相同
}
}
}
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标签就是在spring核心容器中添加对象
id/name: 对象的唯一标识
class: 对象的全限定类名
-->
<!-- 通过空参构造器的方式来创建对象 -->
<bean id="user" class="com.zzxx.domain.User"/>
<bean id="user1" class="com.zzxx.domain.User"/>
<!-- 通过静态工厂来创建对象
class: 指定的是工厂类的全限定类名
factory-method: 静态方法
-->
<bean id="user1" class="com.zzxx.factory.UserFactory" factory-method="getInstance"/>
<!-- 通过工厂方法来创建对象
class: 要创建的类的全限定类名
factory-bean: 工厂对象
factory-method: 普通的工厂方法
-->
<bean id="user2" class="com.zzxx.domain.User"
factory-bean="factory" factory-method="init"/>
<bean id="factory" class="com.zzxx.factory.UserFactory"/>
</beans>
// 工厂类
public class UserFactory {
// 静态工厂方法
public static User getInstance() {
return new User(1, "张张");
}
// 工厂方法
public User init() {
return new User(1);
}
}
ApplicationContext
三种实现类
ClassPathXmlApplicationContext: 类路径XML
FileSystemXmlApplicationContext: 文件系统
AnnotationConfigApplicationContext: 注解
- ApplicationContext
对象单例模式, 立即加载, 初始化容器的时候, 就已经将容器中注册的对象全部创建好 - BeanFactory: - 了解
延迟加载, 当程序根据id来获得对象的时候, 才创建对象
bean.xml
<!-- bean属性设置
scope: 作用域 - 生命周期 (重要)
singleton: 默认值, 单例, 容器初始化时创建, 容器关闭时销毁
prototype: 多例, 根据id获得对象时创建, 用GC回收时销毁, destroy-method 失效
Struts2框架 XXAction 必须是多例的
request: 作用于HttpServletRequest对象
session: 作用于HttpSession对象
init-method: 初始化方法, 创建完对象后调用的方法
destroy-method: 销毁对象之前调用的方法
-->
<bean id="accountService" class="com.zzxx.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy"
/>
DI(Dependency Injection) - 依赖注入
1、构造器注入 - 掌握
2、set方法注入 - 掌握\重点
bean.xml
<!-- 调用指定构造器方式来创建对象,
好处: 在创建对象时, 必须指定具体的参数
问题: 改变了创建对象的方式
-->
<bean id="accountService1" class="com.zzxx.service.impl.AccountServiceImpl">
<!-- constructor-arg 构造器参数
name: 构造器的参数名
index: 构造器中的参数索引
type: 构造器中的参数类型 == 自动识别类型
======以上三个都是为了确定参数的
value: 传递的实际参数值
ref: 实际传递的参数对象, 对象在spring容器中的唯一标识
-->
<constructor-arg name="name" index="1" value="张张" />
<constructor-arg name="ac" index="0" ref="accountDao"/>
</bean>
<!-- set方式注入: 就是在调用对象的setXX()方法 -->
<bean id="accountService2" class="com.zzxx.service.impl.AccountServiceImpl">
<!-- property: 要注入的属性
name: 属性名字 看的是setXX方法
value
ref
-->
<property name="accountDao" ref="accountDao" />
</bean>
3、p命名空间注入 - 了解
4、spel表达式注入 - 了解
bean.xml
<!-- p命名空间注入
1.需要修改/添加约束
xmlns:p="http://www.springframework.org/schema/p"
2.bean标签中添加
p:属性名 = "" -> 简单类型赋值 基本数据类型+String
p:属性名-ref = "对象的id/name" -> 引用类型赋值
-->
<bean id="user1" class="com.zzxx.domain.User"
p:id="1" p:name="张三" p:date-ref="now"
/>
<bean name="now" class="java.util.Date" />
<!-- spel spring Expression Language
#{对象id.对象的属性}
使用其他对象的属性值
-->
<bean name="user2" class="com.zzxx.domain.User">
<property name="id" value="#{user1.id}"/>
</bean>
复杂类型注入
bean.xml
<!-- set注入-->
<bean name="user" class="com.zzxx.domain.User">
<!-- 注入数组类型 -->
<!-- 1.如果数组中只有一个元素
直接通过value属性注入
-->
<!-- <property name="arr" value="10" />-->
<!-- 2.如果数组中有多个元素
通过<array>标签注入
值: <value>
引用: <ref>
-->
<property name="arr">
<array>
<value>10</value>
<value>20</value>
<value>30</value>
</array>
</property>
</bean>
<!-- 给 list 属性赋值 使用<list>标签
给 set 属性赋值 使用<set>标签
-->
<bean name="user1" class="com.zzxx.domain.User">
<property name="list">
<list>
<value>张三</value>
<value>李四</value>
<value>狗剩</value>
</list>
</property>
</bean>
<!-- 给 map 属性赋值 用<map>
每一个元素: <entry>标签
-->
<bean name="user2" class="com.zzxx.domain.User">
<property name="map">
<map>
<entry key="10" value="aa"/>
<entry key="20" value="bb"/>
<entry key="30" value="cc"/>
</map>
</property>
</bean>
<!-- 给 properties 属性赋值 用<props>
每一个元素: <prop>标签 key value[标签体中]
-->
<bean name="user3" class="com.zzxx.domain.User">
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driver</prop>
<prop key="url">jdbc:mysql:///mybatis?serverTimezone=GMT</prop>
<prop key="username">username</prop>
<prop key="password">password</prop>
</props>
</property>
</bean>
Spring注解
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 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.zzxx"/>
</beans>
2、在JavaBean中使用注解代替 bean标签
– Component、Repository、Controller、Service
– Scope
– 注:@PostConstruct、@PreDestroy(使用之前需要添加依赖)
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
注意: 使用之前需要添加依赖
依赖注入:
@Value
@Autowired 自动装配, 会自己根据类型从Spring容器中找到对应的对象, 赋值上去, 同类型对象只有一个
@Autowired
@Qualifier("date1970") --> 指定对象的id
-- @Resource() 等同于@Autowired
-- @Resource("date1970") 等同于 Autowired+Qualifier
public class User {
@Value("10") // user.id = 10
private int id;
@Value("aa")
private String name;
/*@Autowired
@Qualifier("date1970")*/
@Resource(name="date1970")
private Date date;
@PostConstruct // 在构造器之后执行 init-method
public void init() {
System.out.println("user init");
}
@PreDestroy // 在销毁之前执行 destroy-method
public void destroy() {
System.out.println("user destroy");
}
}
bean.xml
<bean id="date1970" class="java.util.Date">
<constructor-arg name="date" type="long" value="0"/>
</bean>
3、通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
SpringConfiguration
/*
* 取代beans.xml配置文件
* Configuration:
* 作用: 指定当前类为注解配置类
* 注意: 如果当前类作为AnnotationConfigApplicationContext创建对象的参数时, 注解可以省略
* ComponentScan:
* 取代<context:component-scan base-package="com.zzxx"/>
* 属性: basePackages/value
* Bean:
* 取代 <bean></bean>
* 作用: 将方法的返回值对象 交给spring容器中
* 方法参数的注入: 效果等同于 Autowired
* 如果容器中有多个参数类型的对象, 可以使用 @Qualifier("ds2") 来指定注入的对象名
* Import:
* 取代 <import resource=""/>
* 作用: 关联其他的配置类
*/
@Configuration
@ComponentScan({"com.zzxx"})
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
JdbcConfig
/*
* PropertySource:
* 取代 <context:property-placeholder location="classpath:jdbc.properties"/>
* 作用: 读取properties配置文件
* 使用: 借助 Value注解来将读取出来的值注入到配置类中
* @Value("${driver}")
*/
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
@Scope("prototype")
public JdbcTemplate createJdbcTemplate(@Qualifier("ds") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean("ds")
public DataSource createDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean("ds2")
public DataSource createDataSource2() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///ssm");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
Spring和junit的整合
junit + spring-test
// 将当前测试类 和 Spring 容器绑定, 在执行@Test方法时, 默认打开spring容器
@RunWith(SpringJUnit4ClassRunner.class)
// 指定容器的配置文件
@ContextConfiguration(locations = "classpath:beans.xml")
Spring和jdbc整合
JdbcTemplate
步骤
- 将JdbcTemplate注册到spring容器中
jdbcTemplate依赖于连接池
需要注册DataSource - 注册UserDaoImpl对象
- 将jdbcTemplate 注入到 UserDaoImpl 中
- 注册UserServiceImpl对象
- 将userDaoImpl 注入到 UserServiceImpl 中
bean.xml
<!-- 方法一 -->
<!-- 管理 JdbcTemplate 对象
new JdbcTemplate(dataSource)
-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<!-- 管理DataSource对象
new DruidDataSource()
dataSource.setXX
-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 方法二 -->
<!-- 继承JdbcDaoSupport -->
<bean class="com.zzxx.dao.impl.UserDaoBImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
AOP - 面向切面编程(实现方法增强)
动态代理特点:代理对象和被代理对象拥有相同的父接口
如果被代理对象没有父接口,能不能进行方法增强?
能 CGLib Enhancer类:代理对象 继承 被代理对象类
基于XML的AOP配置
完成步骤
- 1.导包 spring-aspect
2.准备通知类 - 对应的方法
3.在spring中注册通知类对象
4.配置aop <aop:config >
注册切入点 <aop:pointcut expression=“execution(方法名)”>
配置通知 + 织入 <aop:aspect >
通知种类:前置通知 , 3种后置通知 , 环绕通知
after:在切入点执行之后执行
after-returning:在切入点正确执行之后执行
after-throwing:在切入点出现异常后执行
before:在切入点执行之前执行
around:环绕通知
通知类
public class MyAdvice {
public void before() {
System.out.println("前置通知代码");
}
public void afterReturning() {
System.out.println("后置通知代码");
}
}
beans.xml
<!-- 2.注册通知类 -->
<bean id="advice" class="com.zzxx.MyAdvice"/>
<!-- 3.织入 spring 的 aop 配置 -->
<aop:config>
<!-- 1.注册切入点
expression: execution(切入点方法的全限定类名)
void com.zzxx.service.impl.AccountServiceImpl.addAccount(Account)
void com.zzxx.service.impl.AccountServiceImpl.*(Account)
* com.zzxx.service.impl.AccountServiceImpl.*(Account)
* com.zzxx.service.impl.*ServiceImpl.*(Account)
* com.zzxx.service.impl.*ServiceImpl.*(..)
-->
<aop:pointcut id="pc" expression="execution(* com.zzxx.service.impl.*ServiceImpl.*(..))"/>
<!-- 配置通知, 织入 -->
<aop:aspect id="ad" ref="advice">
<aop:before method="before" pointcut-ref="pc" />
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<!-- <aop:after-throwing method=""/>-->
<!-- <aop:after method=""/>-->
<!-- <aop:around method=""/>-->
</aop:aspect>
</aop:config>
基于注解的AOP配置
通知类
@Component("advice")
@Aspect
public class MyAdvice {
@Before("execution(* com.zzxx.service.impl.*ServiceImpl.*(..))")
public void before() {
System.out.println("前置通知");
}
@After("execution(* com.zzxx.service.impl.*ServiceImpl.*(..))")
public void afterReturning() {
System.out.println("后置通知");
}
@AfterThrowing("execution(* com.zzxx.service.impl.*ServiceImpl.*(..))")
public void afterThrowing() {
System.out.println("异常通知");
}
}
beans.xml
<aop:aspectj-autoproxy />
Spring对于事务管理
基于SpringAOP技术
对于事务管理的属性设置
- 1、隔离级别
2、传播行为
3、只读性(只有查询操作只读)
基于XML的事务配置
beans.xml
<!-- 注册dataSource 对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring"/>
<property name="username" value="username"/>
<property name="password" value="username"/>
</bean>
<!-- 注册spring事务管理的通知类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut id="pc" expression="execution(* com.zzxx.service.impl.*ServiceImpl.*(..))"/>
<!-- 事务管理通知的织入 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
</aop:config>
<!-- 配置spring事务管理的属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 不同的业务方法设置不同的事务管理属性
isolation 隔离级别
propagation 传播行为
read-only 只读性
-->
<tx:method name="find*" isolation="REPEATABLE_READ" propagation="SUPPORTS" read-only="true"/>
<tx:method name="add*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
</tx:attributes>
</tx:advice>
基于注解的事务配置
beans.xml
<!-- 注册dataSource 对象 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///spring"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 注册spring事务管理的通知类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启注解扫描 -->
<context:component-scan base-package="com.zzxx"/>
<!-- 开启注解管理事务: 注解驱动器
事务管理通知对象默认的id
transactionManager
-->
<tx:annotation-driven transaction-manager="transactionManager" />
AccountServiceImpl
@Service("accountService")
// 这个类中所有的方法都是用这个事务属性设置
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(int srcId, int destId, double money) {
// 减钱
accountDao.decreaseMoney(srcId, money);
// 异常
int a = 1/0;
// 加钱
accountDao.increaseMoney(destId, money);
}
}
MyBatis与Spring整合
步骤
- 1、jar包 mybatis-spring-xx
2、MyBatis 核心对象 交给Spring容器管理
SqlSessionFactory 注册到Spring容器中
Dao 注册到Spring容器中(注意点: 没有实现类)
beans.xml
<!-- 注册 SqlSessionFactory 工厂对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 配置连接池环境 -->
<property name="dataSource" ref="dataSource"/>
<!-- 设置别名 -->
<property name="typeAliasesPackage" value="com.zzxx.domain"/>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///ssm"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 注册 Dao 层 对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 生成代理对象需要用到session -> session.getMapper() -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 配置dao 和映射文件所在的包
将指定包下所有的 dao 接口 都会自动生成对应的代理对象
-->
<property name="basePackage" value="com.zzxx.dao"/>
</bean>
AccountDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zzxx.dao.AccountDao">
<select id="findAll" resultType="account">
select * from account
</select>
</mapper>