前言

在工作中常常会发现很多共事连最罕用的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-methodx销毁调用的办法
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);    }}
@Configurationpublic 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")@Aspectpublic 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 注解事务

  1. 配置事务管理器(和xml一样必须的)
  2. 开启Spring事务注解反对<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  3. 在须要注解的中央应用@Transaction
  4. 不须要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来注入依赖。

解决办法:

  1. 导入JUnit包
  2. 导入Spring整合JUnit的包
  3. 替换Running,@RunWith(SpringJUnit4ClassRunner.class)
  4. 退出配置文件,@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一网打尽,间接点击就能够间接白嫖了!