共计 15210 个字符,预计需要花费 39 分钟才能阅读完成。
前言
在工作中常常会发现很多共事连最罕用的 SSM 框架应用起来也常常忘这忘那的,对于 spring 甚至只记得 IOC、DI、AOP,而后就在网上找材料节约大部分工夫,所以本文重温了一遍帮大伙加深了解,同时做个整顿,当前再忘来看这篇文章就好了。
我平时也会收集一些不错的 spring 学习书籍 PDF,毕竟程序员的书都挺贵的,不可能每本都买实体,本人啃完也会梳理学习笔记,都在这了 >>spring 一网打尽,间接点击就能够无偿获取。
1. 体系结构
Spring 是模块化的,能够抉择适合的模块来应用,其体系结构分为 5 个局部,别离为:
Core Container
外围容器:Spring 最次要的模块,次要提供了 IOC、DI、BeanFactory、Context 等,列出的这些学习过 Spring 的同学应该都意识
Data Access/Integration
数据拜访 / 集成:即有 JDBC 的形象层、ORM 对象关系映射 API、还有事务反对(重要)等
Web
Web:根底的 Web 性能如 Servlet、http、Web-MVC、Web-Socket 等
Test
测试:反对具备 Junit 或 TestNG 框架的 Spring 组件测试
其余
AOP、Aspects(面向切面编程框架)等
2. IOC
2.1 引入耦合概念
耦合:即是类间或办法间的依赖关系,编译期间的依赖会导致前期保护十分困难,一处的改变导致其余依赖的中央都需改变,所以要解耦
解耦:解除程序间的依赖关系,但在理论开发中咱们只能做到编译期间不依赖,运行期间才依赖即可,没有依赖关系即没有必要存在了
解决思路:应用 Java 的反射机制来防止 new 关键字(通过读取配置文件来获取对象全限定类名)、应用工厂模式
2.2 IOC 容器
Spring 框架的外围,次要用来寄存 Bean 对象,其中有个底层 BeanFactory 接口只提供最简略的容器性能(特点提早加载),个别不应用。罕用的是其子类接口 ApplicationContext 接口(创立容器时立刻实例化对象,继承 BeanFactory 接口),提供了高级性能(拜访资源,解析文件信息,载入多个继承关系的上下文,拦截器等)。
ApplicationContext 接口有三个实现类:ClassPathXmlApplicationContext、FileSystemoXmlApplication、AnnotionalConfigApplication,从名字能够晓得他们的区别,上面解说都将围绕 ApplicationContext 接口。
容器为 Map 构造,键为 id,值为 Object 对象。
2.2.1 Bean 的创立形式
无参结构
只配了 id、class 标签属性(此时肯定要有无参函数,增加有参结构时记得补回无参结构)
一般工厂创立
可能是他人写好的类或者 jar 包,咱们无奈批改其源码(只有字节码)来提供无参构造函数,eg:
// 这是他人的 jar 包是应用工厂来获取实例对象的
public class InstanceFactory {public User getUser() {return new User();
}
}
<!-- 工厂类 -->
<bean id="UserFactory" class="com.howl.entity.UserFactory"></bean>
<!-- 指定工厂类及其生产实例对象的办法 -->
<bean id="User" factory-bean="UserFactory" factory-method="getUser"></bean>
动态工厂创立
<!-- class 应用动态工厂类, 办法为静态方法生产实例对象 -->
<bean id="User" class="com.howl.entity.UserFactory" factory-method="getUser"></bean>
2.2.2 Bean 标签
该标签在 applicationContext.xml 中示意一个被治理的 Bean 对象,Spring 读取 xml 配置文件后把内容放入 Spring 的 Bean 定义注册表,而后依据该注册表来实例化 Bean 对象将其放入 Bean 缓存池中,应用程序应用对象时从缓存池中获取
属性 | 形容 |
---|---|
class | 指定用来创立 bean 类 |
id | 惟一的标识符,可用 ID 或 name 属性来指定 bean 标识符 |
scope | 对象的作用域,singleton(默认)/prototype |
lazy-init | 是否懒创立 true/false |
init-method | 初始化调用的办法 |
destroy-method | x 销毁调用的办法 |
autowire | 不倡议应用,主动拆卸 byType、byName、constructor |
factory-bean | 指定工厂类 |
factory-method | 指定工厂办法 |
元素 | 形容 |
constructor-arg | 构造函数注入 |
properties | 属性注入 |
元素的属性 | 形容 |
type | 依照类型注入 |
index | 依照下标注入 |
name | 依照名字注入,最罕用 |
value | 给根本类型和 String 注入 |
ref | 给其余 bean 类型注入 |
元素的标签 | 形容 |
<list> |
|
<Set> |
|
<Map> |
|
<props> |
2.2.3 应用
留神:默认应用无参构造函数的,若本人写了有参结构,记得补回无参结构
XML
<bean id="User" class="com.howl.entity.User"></bean>
ApplicationContext ac = new ClassPathXmlApplicationContext
("applicationContext.xml");
User user = (User) ac.getBean("User");
user.getName();
注解
前提在 xml 配置文件中开启 bean 扫描
<context:component-scan base-package="com.howl.entity"></context:component-scan>
// 默认是类名首字母小写
@Component(value="User")
public class User{
int id;
String name;
String eamil;}
2.2.4 生命周期
单例:与容器同生共死
多例:应用时创立,GC 回收时死亡
3. DI
Spring 框架的外围性能之一就是通过依赖注入的形式来治理 Bean 之间的依赖关系,能注入的数据类型有三类:根本类型和 String,其余 Bean 类型,汇合类型。注入形式有:构造函数,set 办法,注解
3.1 基于构造函数的注入
<!-- 把对象的创立交给 Spring 治理 -->
<bean id="User" class="com.howl.entity.User">
<constructor-arg type="int" value="1"></constructor-arg>
<constructor-arg index="1" value="Howl"></constructor-arg>
<constructor-arg name="email" value="xxx@qq.com"></constructor-arg>
<constructor-arg name="birthday" ref="brithday"></constructor-arg>
</bean>
<bean id="brithday" class="java.util.Date"></bean>
3.2 基于 setter 注入(罕用)
被注入的 bean 肯定要有 setter 函数才可注入,而且其不关怀属性叫什么名字,只关怀 setter 叫什么名字
<bean id="User" class="com.howl.entity.User">
<property name="id" value="1"></property>
<property name="name" value="Howl"></property>
<property name="email" value="XXX@qq.com"></property>
<property name="birthday" ref="brithday"></property>
</bean>
<bean id="brithday" class="java.util.Date"></bean>
3.3 注入汇合
<bean style=”margin: 0px; padding: 0px;”> 外部有简单标签 <list style=”margin: 0px; padding: 0px;”>、<set style=”margin: 0px; padding: 0px;”>、<map style=”margin: 0px; padding: 0px;”>、<props style=”margin: 0px; padding: 0px;”> 这里应用 setter 注入 </props></map></set></list></bean>
<bean id="User" class="com.howl.entity.User">
<property name="addressList">
<list>
<value>INDIA</value>
<value>Pakistan</value>
<value>USA</value>
<ref bean="address2"/>
</list>
</property>
<property name="addressSet">
<set>
<value>INDIA</value>
<ref bean="address2"/>
<value>USA</value>
<value>USA</value>
</set>
</property>
<property name="addressMap">
<map>
<entry key="1" value="INDIA"/>
<entry key="2" value-ref="address1"/>
<entry key="3" value="USA"/>
</map>
</property>
<property name="addressProp">
<props>
<prop key="one">INDIA</prop>
<prop key="two">Pakistan</prop>
<prop key="three">USA</prop>
<prop key="four">USA</prop>
</props>
</property>
</bean>
3.4 注解
@Autowired:主动依照类型注入(所以应用注解时 setter 办法不是必须的,可用在变量上,也可在办法上)。若容器中有惟一的一个 bean 对象类型和要注入的变量类型匹配就能够注入;若一个类型匹配都没有,则报错;若有多个类型匹配时:先匹配全副的类型,再持续匹配 id 是否有统一的,有则注入,没有则报错
@Qualifier:在依照类型注入根底上按 id 注入,给类成员变量注入时不能独自应用,给办法参数注入时能够独自应用
@Resource: 下面二者的联合
留神:以上三个注入只能注入 bean 类型数据,不能注入根本类型和 String,汇合类型的注入只能通过 XMl 形式实现
@Value:注入根本类型和 String 数据
承接下面有个 User 类了
@Component(value = "oneUser")
@Scope(value = "singleton")
public class OneUser {
@Autowired // 按类型注入
User user;
@Value(value = "注入的 String 类型")
String str;
public void UserToString() {System.out.println(user + str);
}
}
3.5 配置类(在 SpringBoot 中常常会遇到)
配置类等同于 aplicationContext.xml,个别配置类要配置的是须要参数注入的 bean 对象,不须要参数配置的间接在类上加 @Component
/**
* 该类是个配置类,作用与 applicationContext.xml 相等
* @Configuration 示意配置类
* @ComponentScan(value = {""})内容能够传多个,示意数组
* @Bean 示意将返回值放入容器,默认办法名为 id
* @Import 导入其余配置类
* @EnableAspectJAutoProxy 示意开启注解
*/
@Configuration
@Import(OtherConfiguration.class)
@EnableAspectJAutoProxy
@ComponentScan(value = {"com.howl.entity"})
public class SpringConfiguration {@Bean(value = "userFactory")
@Scope(value = "prototype")
public UserFactory createUserFactory(){
// 这里的对象容器治理不到,即不能用 @Autowired,要本人 new 进去
User user = new User();
// 这里是基于构造函数注入
return new UserFactory(user);
}
}
@Configuration
public class OtherConfiguration {@Bean("user")
public User createUser(){User user = new User();
// 这里是基于 setter 注入
user.setId(1);
user.setName("Howl");
return user;
}
}
4. AOP
4.1 动静代理
动静代理:基于接口(invoke)和基于子类(Enhancer 的 create 办法),基于子类的须要第三方包 cglib,这里只阐明基于接口的动静代理,笔者 动静代理的博文
Object ob = Proxy.newProxyInstance(mydog.getClass().getClassLoader(), mydog.getClass().getInterfaces(),new InvocationHandler(){
// 参数顺次为:被代理类个别不应用、应用的办法、参数的数组
// 返回值为创立的代理对象
// 该办法会拦挡类的所有办法,并在每个办法内注入 invoke 内容
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只加强 eat 办法
if(method.getName().equals("eat")){System.out.println("吃肉前洗手");
method.invoke(mydog, args);
}else{method.invoke(mydog, args);
}
return proxy;
}
})
4.2 AOP
相干术语:
连接点:这里指被拦挡的办法(Spring 只反对办法)
告诉:拦挡到连接点要执行的工作
切入点:拦挡中要被加强的办法
织入:加强办法的过程
代理对象:加强性能后返回的对象
切面:整体的联合,什么时候,如何加强办法
xml 配置
<!-- 须要额定的 jar 包,aspectjweaver 表达式须要 -->
<!-- 被切入的办法 -->
<bean id="accountServiceImpl" class="com.howl.interfaces.impl.AccountServiceImpl"></bean>
<!-- 告诉 bean 也交给容器治理 -->
<bean id="logger" class="com.howl.util.Logger"></bean>
<!-- 配置 aop -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.howl.interfaces..*(..))"/>
<aop:aspect id="logAdvice" ref="logger">
<aop:before method="beforeLog" pointcut-ref="pt1"></aop:before>
<aop:after-returning method="afterReturningLog" pointcut-ref="pt1"></aop:after-returning>
<aop:after-throwing method="afterThrowingLog" pointcut-ref="pt1"></aop:after-throwing>
<aop:after method="afterLog" pointcut="execution(* com.howl.interfaces..*(..))"></aop:after>
<!-- 配置盘绕告诉, 测试时请把下面四个正文掉,排除烦扰 -->
<aop:around method="aroundLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
<!-- 切入表达式 -->
<!-- 拜访修饰符 . 返回值 . 包名 . 包名 . 包名。。。. 类名 . 办法名(参数列表)-->
<!-- public void com.howl.Service.UserService.deleteUser() -->
<!-- 拜访修饰符能够省略 -->
<!-- * 示意通配,可用于修饰符,返回值,包名,办法名 -->
<!-- .. 标记以后包及其子包 -->
<!-- .. 能够示意有无参数,* 示意有参数 -->
<!-- * com.howl.service.*(..) -->
<!-- 盘绕告诉是手动编码方式实现加强办法适合执行的形式,相似于 invoke? -->
即盘绕告诉是手动配置切入办法的,且 Spring 框架提供了 ProceedingJoinPoint,该接口有一个 proceed()和 getArgs()办法。此办法就明确相当于调用切入点办法和获取参数。在程序执行时,spring 框架会为咱们提供该接口的实现类供咱们应用
// 抽取了公共的代码(日志)public class Logger {public void beforeLog(){System.out.println("前置告诉");
}
public void afterReturningLog(){System.out.println("后置告诉");
}
public void afterThrowingLog(){System.out.println("异样告诉");
}
public void afterLog(){System.out.println("最终告诉");
}
// 这里就是盘绕告诉
public Object aroundLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
// 获取办法参数
Object[] args = pjp.getArgs();
System.out.println("前置告诉");
// 调用业务层办法
rtValue = pjp.proceed();
System.out.println("后置告诉");
} catch (Throwable t) {System.out.println("异样告诉");
t.printStackTrace();} finally {System.out.println("最终告诉");
}
return rtValue;
}
}
基于注解的 AOP
<!-- 配置 Spring 创立容器时要扫描的包, 次要扫描被切入的类,以及切面类 -->
<context:compinent-scan base-package="com.howl.*"></context:compinent-scan>
<!-- 这二者的类上要注解 @Compinent / @Service -->
<!-- 开启 AOP 注解反对 -->
<aop:aspectj:autoproxy></aop:aspectj:autoproxy>>
留神要在切面类上加上注解示意是个切面类,四个告诉在注解中告诉程序是不能决定的且乱序,不倡议应用,不过可用盘绕告诉代替。即注解中倡议应用盘绕告诉来代替其余四个告诉
// 抽取了公共的日志
@Component(value = "logger")
@Aspect
public class Logger {@Pointcut("execution(* com.howl.interfaces..*(..))")
private void pt1(){}
@Before("pt1()")
public void beforeLog(){System.out.println("前置告诉");
}
@AfterReturning("pt1()")
public void afterReturningLog(){System.out.println("后置告诉");
}
@AfterThrowing("pt1()")
public void afterThrowingLog(){System.out.println("异样告诉");
}
@After("pt1()")
public void afterLog(){System.out.println("最终告诉");
}
@Around("pt1()")
public Object aroundLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try {
// 获取办法参数
Object[] args = pjp.getArgs();
System.out.println("前置告诉");
// 调用业务层办法
rtValue = pjp.proceed();
System.out.println("后置告诉");
} catch (Throwable t) {System.out.println("异样告诉");
t.printStackTrace();} finally {System.out.println("最终告诉");
}
return rtValue;
}
}
5. 事务
Spring 提供了申明式事务和编程式事务,后者难于应用而抉择放弃,Spring 提供的事务在业务层,是基于 AOP 的
5.1 申明式事务
从业务代码中拆散事务管理,仅仅应用正文或 XML 配置来治理事务,Spring 把事务形象成接口 org.springframework.transaction.PlatformTransactionManager,其内容如下,重要的是其只是个接口,真正实现类是:org.springframework.jdbc.datasource.DataSourceTransactionManager
public interface PlatformTransactionManager {
// 依据定义创立或获取以后事务
TransactionStatus getTransaction(TransactionDefinition definition);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
TransactionDefinition 事务定义信息
public interface TransactionDefinition {int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();}
因为不相熟所以把过程全副贴下来
5.2 xml 配置
建表
CREATE TABLE `account` (`id` int(11) NOT NULL AUTO_INCREMENT,
`money` int(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
entity
public class Account {
private int id;
private int money;
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public int getMoney() {return money;}
public void setMoney(int money) {this.money = money;}
public Account(int id, int money) {
this.id = id;
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", money=" + money +
'}';
}
}
Dao 层
public interface AccountDao {
// 查找账户
public Account selectAccountById(int id);
// 更新账户
public void updateAccountById(@Param(value = "id") int id, @Param(value = "money") int money);
}
Mapper 层
<?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.howl.dao.AccountDao">
<select id="selectAccountById" resultType="com.howl.entity.Account">
SELECT * FROM account WHERE id = #{id};
</select>
<update id="updateAccountById">
UPDATE account SET money = #{money} WHERE id = #{id}
</update>
</mapper>
Service 层
public interface AccountService {public Account selectAccountById(int id);
public void transfer(int fid,int sid,int money);
}
Service 层 Impl
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public Account selectAccountById(int id) {return accountDao.selectAccountById(id);
}
// 这里只思考事务,不关怀钱额是否短缺
public void transfer(int fid, int sid, int money) {Account sourceAccount = accountDao.selectAccountById(fid);
Account targetAccount = accountDao.selectAccountById(sid);
accountDao.updateAccountById(fid, sourceAccount.getMoney() - money);
// 异样
int i = 1 / 0;
accountDao.updateAccountById(sid, targetAccount.getMoney() + money);
}
}
applicationContext.xml 配置
<!-- 配置数据源,spring 自带的没有连接池性能 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/spring"></property>
<property name="username" value="root"></property>
<property name="password" value=""></property>
</bean>
<!-- 配置 sqlSessionFactory 工厂 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 业务层 bean -->
<bean id="accountServiceImpl" class="com.howl.service.impl.AccountServiceImpl" lazy-init="true">
<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务告诉,能够了解为 Logger -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:隔离界别,默认应用数据库的
propagation:转播行为,默认 REQUIRED
read-only:只有查询方法才须要设置 true
timeout:默认 - 1 永不超时
no-rollback-for
rollback-for
-->
<tx:attributes>
<!-- name 中是抉择匹配的办法 -->
<tx:method name="select*" propagation="SUPPORTS" read-only="true"></tx:method>
<tx:method name="*" propagation="REQUIRED" read-only="false"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置 AOP -->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.howl.service.impl.AccountServiceImpl.transfer(..))"/>
<!-- 建设切入点表达式与事务告诉的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
测试
public class UI {public static void main(String[] args) {ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");
Account account = accountService.selectAccountById(1);
System.out.println(account);
accountService.transfer(1,2,100);
}
}
失常或产生异样都完满运行
集体感觉重点在于配置事务管理器(而像数据源这样是日常须要)
事务管理器:治理获取的数据库连贯
事务告诉:依据事务管理器来配置所须要的告诉(相似于前后置告诉)
下面两个能够认为是合一起配一个告诉,而上面的配置办法与告诉的映射关系
AOP 配置:用特有的 <aop:advisor>
标签来阐明这是一个事务,须要在哪些地方切入
5.3 注解事务
- 配置事务管理器(和 xml 一样必须的)
- 开启 Spring 事务注解反对
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 在须要注解的中央应用 @Transaction
- 不须要 AOP,是因为 @Transaction 注解放在了哪个类上就阐明哪个类须要切入,外面所有办法都是切入点,映射关系曾经存在了
在 AccountServiceImpl 中简化成,xml 中能够抉择办法匹配,注解不可,只能这样配
@Service(value = "accountServiceImpl")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements AccountService {
// 这里为了获取 Dao 层
@Autowired
private AccountDao accountDao;
// 业务正式开始
public Account selectAccountById(int id) {return accountDao.selectAccountById(id);
}
// 这里只思考事务,不关怀钱额是否短缺
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transfer(int fid, int sid, int money) {Account sourceAccount = accountDao.selectAccountById(fid);
Account targetAccount = accountDao.selectAccountById(sid);
accountDao.updateAccountById(fid, sourceAccount.getMoney() - money);
// 异样
// int i = 1 / 0;
accountDao.updateAccountById(sid, targetAccount.getMoney() + money);
}
}
6. Test
应用程序的入口是 main 办法,而 JUnit 单元测试中,没有 main 办法也能执行,因为其外部集成了一个 main 办法,该办法会主动判断以后测试类哪些办法有 @Test 注解,有就执行。
JUnit 不会晓得咱们是否用了 Spring 框架,所以在执行测试方法时,不会为咱们读取 Spring 的配置文件来创立外围容器,所以不能应用 @Autowired 来注入依赖。
解决办法:
- 导入 JUnit 包
- 导入 Spring 整合 JUnit 的包
- 替换 Running,@RunWith(SpringJUnit4ClassRunner.class)
- 退出配置文件,@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
//@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class UITest {
@Autowired
UserFactory userFactory;
@Test
public void User(){System.out.println(userFactory.getUser().toString());
}
}
7. 注解总览
@Component
@Controller
@Service
@Repository
@Autowired
@Qualifier
@Resource
@Value
@Scope
@Configuration
@ComponentScan
@Bean
@Import
@PropertySource()
@RunWith
@ContextConfiguration
@Transactional
8. 总结
学完 Spring 之后感觉有什么劣势呢?
- IOC、DI:不便降耦
- AOP:反复的性能造成组件,在须要处切入,切入出只需关怀本身业务甚至不晓得有组件切入,也可把切入的组件放到开发的最初才实现
- 申明式事务的反对
- 最小侵入性:不必继承或实现他们的类和接口,没有绑定了编程,Spring 尽可能不让本身 API 弄乱开发者代码
- 整合测试
-
不便集成其余框架
好了,本文就写到这里吧,我的学习秘籍都在这了 >>spring 一网打尽,间接点击就能够间接白嫖了!