Spring sort of knowledge



Often forget this forget that, then look for information on the Internet wasted most of the time, even just remember IOC, DI, AOP, as for other details long forgotten when using Spring, so look W3Cschool B station video refresh their memories and to deepen understanding, by the way the learning process is recorded (the last two will be labeled address)




1. Architecture

Spring is modular and can be used to select the appropriate module, which architecture is divided into five parts, namely:


Core Container

The core container: Spring the most important module, the main provider of the IOC, DI, BeanFactory, Context, etc., which studied the listed Spring students should know


Data Access/Integration

Data Access / Integration: that is JDBC abstraction layer, ORM Object Relational Mapping API, as well as transaction support (important), etc.


Web

Web: Web-based features such as Servlet, http, Web-MVC, Web-Socket, etc.


Test

Test: Spring support component testing has Junit or TestNG framework


other

AOP, Aspects (Aspect Oriented Programming Framework), etc.












2. IOC


2.1 introduces the concept of coupling

Coupling: that is, the dependencies between classes or between methods rely on compile time can lead to post-maintenance very difficult to change a dependent of causes other places are subject to change, so be decoupled

Decoupling: lift the dependencies between procedures, but in the actual development we can do does not depend on compile time, run time can only rely on, no dependencies exist that is not necessary

Solution: use Java's reflection mechanism to avoid (to get the object fully qualified class name by reading the configuration file) new keywords, use the factory pattern


2.2 IOC container

Spring core framework, mainly used to store Bean object, which has a bottom BeanFactory interface provides only the simplest container functions (load delay characteristics), is generally not used. Common Interface is a subclass ApplicationContext interface (instantiated immediately create container objects, inheritance BeanFactory interface), provides advanced features (access to resources, information parsing the file to load multiple inheritance context, interceptors, etc.).

ApplicationContext interface has achieved three categories: ClassPathXmlApplicationContext, FileSystemoXmlApplication, AnnotionalConfigApplication, from the name you can know the difference between them, the following explanation will be around ApplicationContext interface.

Map of the container structure, as key id, Object value of the object.


2.2.1 Bean created way


No-argument constructor

With only the id, class label attribute (this time must be no-argument function, remember to pay back the no-argument constructor argument constructor when added)


Create a common factory

Others may be written class or jar package, we can not modify its source code (only the byte-code) to provide a no-argument constructor, 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>


Create a static factory

<!--  class使用静态工厂类,方法为静态方法生产实例对象  -->
<bean id="User" class="com.howl.entity.UserFactory" factory-method="getUser"></bean>


2.2.2 Bean tag

The tag represents one managed objects in the applicationContext.xml Bean, Spring xml configuration file to read the contents into the Spring Bean definitions registry and to instantiate the object based on the registry Bean to Bean into the buffer pool , the application obtains from the cache when using an object pool

Attributes description
class Designated to create the bean class
id Unique identifier, or ID can be used to specify the name attribute identifier bean
scope Object scope, singleton (default) / prototype
lazy-init Are lazy to create a true / false
init-method Initialization method calls
destroy-method x destruction method call
autowire Not recommended, automated assembly byType, byName, constructor
factory-bean Specifies the factory class
factory-method Specifies the factory method
element description
constructor-arg Constructor injection
properties Properties injection
Properties of the elements description
type In accordance with the type of injection
index According to the next mark
name Injection by name, the most commonly used
value To the basic types and String injection
ref To other types of bean injection
Label elements description
<list>
<Set>
<Map>
<props>


2.2.3 Use

Note: The default use the no-argument constructor, if there is to write the configuration parameters, remember to pay back the no-argument constructor


XML

<bean id="User" class="com.howl.entity.User"></bean>
ApplicationContext ac = new ClassPathXmlApplicationContext
("applicationContext.xml");
User user = (User) ac.getBean("User");
user.getName();


annotation

Premise open bean in xml configuration file scanning

<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="[email protected]"></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="[email protected]"></property>
    <property name="birthday" ref="brithday"></property>
</bean>

<bean id="brithday" class="java.util.Date"></bean>


3.3 注入集合

内部有复杂标签 这里使用setter注入

<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 注解事务

  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弄乱开发者代码
  • 整合测试

  • 方便集成其他框架




参考

W3Cschool
B站视频




Guess you like

Origin www.cnblogs.com/Howlet/p/12389510.html