《JavaEE互联网轻量级框架整合开发》学习笔记——Spring篇

Spring IoC概述

Spring IoC阐述
  • 控制反转是一种通过描述(在Java中可以是XML或者注解)并通过第三方去产生或获得特定对象的方式。
  • 主动创建对象,责任归于开发者;被动创建对象,责任归于IoC容器。
  • 降低了开发难度,对模块解耦,有利于测试。

Spring IoC容器

Spring IoC容器的设计
  • 主要基于BeanFactory和ApplicationContext两个接口,前者是最底层接口,后者是前者的子接口,在之前的功能上做了许多扩展。

  • 初始化容器,并获得特定对象

    ApplicationContext context = new 								ClassPathXmlApplicationContext("ApplicationContext.xml"); 
    Person2 p2 = (Person2) context.getBean("p2");
    p2.say();Spring IoC容器的初始化和依赖注入
    
Spring IoC容器的初始化和依赖注入
  • Bean的初始化和依赖注入在Spring IoC容器中是两大步骤,它是在初始化后,才进行依赖注入。
Bean的初始化步骤:
  1. Resource定位,定位配置文件。
  2. BeanDefinition的载入,加载配置文件生成对应的POJO实例。
  3. BeanDefinition的注册,将POJO实例放入IoC容器中。
  • 完成以上3步,Bean在IoC容器中得到了初始化,但没有完成依赖注入,所以Bean还不能完全使用。
  • SpringBean有一个配置选项:lazy-init,表示是否初始化SpringBean,默认值为default,实际值是false,IoC会自动初始化Bean。如果值为true,当使用getBean()方法时,Bean才初始化。
Spring Bean的生命周期
  • Spring IoC容器本质是管理Bean
  • BeanPostProcessor接口的方法针对每一个Bean
  • DisposableBean接口的方法针对IoC容器,使用destory方法执行销毁
  • BeanNameAware,BeanFactoryAware,ApplicationContextAware,InitializingBean接口的方法,和Bean自定义的初始化、销毁方法是针对单个Bean

装配Spring Bean

依赖注入的3种方式
  1. 构造器注入,使用constructor-arg标签
  2. setter注入,使用property标签
  3. 接口注入,获取外界资源时使用
装配Bean概述
  • 装配Bean就是将开发Bean放入IoC容器中
  • 在XML中显示配置,当使用第三方类库时,因为无法修改里面的代码,所以使用XML方式配置
  • 在Java的接口和类中实现配置,能避免XML配置泛滥
  • 隐式Bean的发现机制和自动装配原则,基于约定优于配置的原则,推荐使用该方式,能减少程序开发者的决定权,简单又灵活
通过XML配置装配Bean
  • 装配简易值:使用bean标签定义一个Bean,id属性为该Bean的编号,不是必须的属性。如果没有,Spring将采用“全限定名#{number}”格式生成编号。因为自动生成的编号较为繁琐,因此都会定义id。class属性为全限定名,通过反射生成该类的Class对象。property标签定义类的属性,name属性定义名称,value属性定义值或使用ref属性,引用其他的Bean。
  • 装配集合List属性使用list标签,通过value标签设值;Map属性使用map标签,通过entry标签设值,使用keyvalue属性设值;Properties属性使用props标签,通过prop元素设值,key属性设值;Set属性使用set标签,通过value标签设值;Array属性使用array标签,通过value标签设值;当集合使用的是类对象时,使用ref替换value
  • 命名空间配置:需要引入对应的命名空间和XML模式(XSD)文件。
通过注解装配Bean
  • Spring中,提供两种方式让IoC容器发现Bean;1.组件扫描;2.自动装配
  • 使用@Component装配Bean,IoC会把这个类扫描生成Bean实例,value属性代表这个类的Bean的id,如果不写,会默认为是首字母小写的类名。
  • @Value代表值的注入。
  • @ComponentScan:注解配置类,代表进行扫描,默认是扫描当前包的路径,POJO报名和它保持一致才能扫描,否则没有。存在两个配置项,basePackages扫描到包,basePackageClasses扫描到类。采用多个@ComponentScan去定义对应的包时,配置的Bean会生成多个实例。因为basePackages可读性更好,优先使用。
  • @Autowired:用在属性、参数上,自动装配,Spring先完成Bean的定义和生成,然后在寻找对应的资源。Spring会根据类型去寻找定义的Bean然后将其注入。当某个接口下有多个实现类时,会产生自动装配的歧义性,不知道装配哪个类,抛出BeansException。
  • @Primary:注解在类上,自动装配时,让Spring优先注入该类。只能解决首要性问题,并不能解决选择性问题。
  • @Qualifier:用在属性上,自动装配时是按名称注入。
  • @Bean:用在方法上,将方法返回的对象作为Bean放入IoC容器。可使用initMethoddestroyMethod配置项定义Bean的初始化和销毁方法。
  • 装配的混合使用:对于自己开发的类,使用注解;对于第三方包或者服务的类,使用XML方式。
  • @ImportResource:注解在配置类上,用于引入配置文件,该注解内容是一个数组,因此可以配置多个XML文件。
  • @Import:注解在配置类上,用于注入其他的配置类,因为所有配置都放在一个配置类里面会造成配置复杂,内容是数组,可以注入多个配置类。
  • 在XML文件中,可以使用import标签引入其他XML文件;可以使用context:component-scan标签代替@ComponentScan功能;暂时不支持使用XML加载Java配置类。
使用Profile
  • @Profile:注解在数据源上,内容设为“dev”代表用于开发,“test”代表用于测试。
  • 使用XML定义Profile:在XML中beans根标签使用profile=“dev“属性,会使整个配置文件的Bean都放在dev的Profile下。因此可在XML文件中使用多个beans根标签+profile属性来配置多个Profile。
  • 启动Profile:使用**@ActiveProfiles(”?“)**,注解在类上,来指定加载哪个Profile。
加载属性(properties)文件
  • 使用属性文件可有效减少硬编码,修改环境时只需修改配置文件即可,提高操作遍历性。

  • @PropertySource,注解在配置类上,用于加载配置文件,配置项:name,配置这次属性配置的名称;value,字符数组,配置多个属性文件;ignoreResourceNotFound,boolean值,当找不到配置文件时是否抛出异常,默认为false;encoding,编码,默认为”“。使用:

    String url = context.getEnvironment().getProperty("jdbc.database.url");
    

    只能获得对应的配置属性,没有解析属性占位符的能力。

  • Spring推荐使用PropertySourcesPlaceholderConfigurer属性文件解析类去解析对应的属性文件,并可通过占位符引用对应的配置。占位符引用${jdbc.database.url}

  • 使用XML方式加载属性文件

    <context:property-placeholder ignore-resource-not-found="true" location="classpath:database-config.properties, XXX-config.properties"/>
    
条件化装配Bean
  • @Condition:注解在方法上,当属性文件不存在的时候,就不需要装配Bean。

    @Condition({DataSourceCondition.class})
    public DataSource getDataSource() {
    }
    

    注解中的类需要实现Condition接口。

Bean的作用域
  • 有4种作用域:1.单例,默认选项,整个应用中,只有一个Bean实例。2.原型,每次从IoC容器获取Bean时,Spring都会创建一个实例。3.会话,在Web应用中使用,会话过程中只创建一个实例。4.请求,在Web应用中使用,一次请求创建一个实例,不同的请求会创建不同的实例。
  • @Scope:注解在类上,表示该Bean的作用域为单例。
使用Spring表达式(Spring EL)
  • Spring EL比上述的注入方式更加强大。

  • 属性文件的读取中使用的是“$”,Spring EL使用“#”读取。

    @Value"#{1}"private Long id;
    @Value"#{'role_name_1'}")
    private String roleName;
    @Value"#{role.id}")
    private Long roleId;
    @Value"#{role.getNote?.toString()}")//问号含义是判断是否返回为null,如果不是就调用后面方法
    private String note;
    
  • 使用类的静态常量和方法

    @Value"#{T(Math).PI}")
    private double pi;
    @Value"#{T(Math).random()}")
    private double random;
    
  • Spring EL运算

    @Value"#{role.id + 1}")
    private int num;
    @Value"#{role.roleName + role.note}")
    private String str;
    @Value"#{role.id == 1}")
    private boolean flag;
    @Value"#{role.id > 1 ? 5 : 1}")
    private int max;
    

面向切面编程

面向切面编程的术语
  • 切面:相当于一个拦截器类
  • 通知:切面开启后,切面中的方法
  • 引入:引入允许我们在现有的类中添加自定义的类和方法
  • 切点:被切面拦截的方法就是一个切点
  • 连接点:是一个判断条件,可指定哪些方法是切点
  • 织入:生成代理对象的过程
使用@AspectJ注解开发Spring AOP
  • Spring只能支持方法拦截的AOP

  • @Aspect:注解在类上,表示该类为一个切面类

  • @Before:前置通知,@After:后置通知,@Around:环绕通知,强大并灵活,但可控性不强,在不需要大量改变业务逻辑时,不需要使用。@AfterReturning,返回通知,@AfterThrowing:异常通知。都是注解在方法上。

  • 连接点:Spring通过正则表达式判断需要拦截的方法

    @Before("execution(* com.ssm.RoleService.printRole(..))")
    

    execution:代表执行方法的时候会触发。

    **:代表任意返回类型。

    com.ssm.RoleService:代表类的全限定名。

    printRole:被拦截方法的名称。

    (…):代表任意参数。

  • @Pointcut:注解在方法上,定义为切点,可以减少正则表达式的重复书写

    @Pointcut("execution(* com.ssm.RoleService.printRole(..))")
    public void print(){}
    @Before("print()")
    public void before(){}
    
  • 测试AOP:在配置类上使用**@EnableAspectJAutoProxy**开启AspectJ框架的自动代理,然后通过@Bean得到一个被@Aspect注解的切面类对象。

    @Configuration
    @EnableAspectJAutoProxy
    @ComponentScan("com.ssm.aop")
    public class AopConfig {
        @Bean
        public RoleAspect getAspect() {
            return new RoleAspect();
        }
    }
    

    在XML文件定义切面

    <!-- 需要导入aop的命名空间 -->
    <aop:aspectj-autoproxy/>//等同于@EnableAspectJAutoProxy
    
  • 给通知传递参数

    public void printRole(Role role, int sort) {
    }
    //将切点方法中的参数role和sort通过args属性传递给了通知方法
    @Before("execution(* com.ssm.RoleService.printRole(..))" + "&& args(role, sort)" )
    public void before(Role role, int sort){
    }
    
  • 引入:在切面中引入其他类,调用引入类方法,对原方法进行增强操作

    public interface RoleVerifier {
        public boolean verify(Role role);
    }
    //使用verify方法检测对象是否为空
    public class RoleVerifierImpl implements RoleVerifier {
        public boolean verify(Role role) {
        	return role != null;
        }
    }
    @DeclareParents(value = "com.ssm.impl.RoleServiceImpl+", defaultImpl = "RoleVerifierImpl.class") 
    public RoleVerifier roleVerifier;
    

    @DeclareParents:注解在属性上,表示引入的增强类对象

    value = “com.ssm.impl.RoleServiceImpl+”:表示对RoleServiceImpl类进行增强,相当于为RoleServiceImpl类引入另一个新的接口,该接口为增强类的接口(RoleVerifier)。

    defaultImpl = “RoleVerifierImpl.class”:指定增强类接口的实现类,这里RoleVerifierImpl做为RoleVerifier接口的实现类。

    具体使用

    //得到被代理类对象
    RoleService roleService = context.getBean("RoleService.class");
    //将被代理对象转换为增强类对象,因为RoleServiceImpl实现了两个接口,所以可以相互转换
    RoleVerifier roleVerifier = (RoleVerifier)roleService;
    //使用增强类方法,判断角色是否为空
    if (roleVerifier.verify(role)) {
        //再执行切面拦截方法
        roleService.printRole(role);
    }
    
使用XML配置开发Spring AOP
  • 通过XML配置切面以及多个通知

    <!-- 不定义切点时,表达式需要多次书写 -->
    <aop:config>
    	<!-- 引用切面 -->
        <aop:aspect ref="xmlAspect">
            <!-- 定义前置通知 -->
        	<aop:before method="before"
                        pointcut="execution(* com.ssm.RoleService.printRole(..))"/>
            <!-- 其他通知的定义上同 -->
        </aop:aspect>
    </aop:config>
    
    <!-- 定义切点时 -->
    <aop:config>
    	<!-- 引用切面 -->
        <aop:aspect ref="xmlAspect">
            <aop:pointcut id="printRole" expression="execution(* com.ssm.RoleService.printRole(..))"/>
            <!-- 定义前置通知,引入切点 -->
        	<aop:before method="before" pointcut-ref="printRole"/>
            <!-- 其他通知的定义上同 -->
        </aop:aspect>
    </aop:config>
    
  • 传递参数

    <!-- 给通知传递参数 -->
    <aop:pointcut id="printRole" expression="execution(* com.ssm.RoleService.printRole(..)) and args(role)"/>
    
  • 引入自定义的类或方法

    <aop:declare-parents types-matching="com.ssm.impl.RoleServiceImpl+" 
                         implement-interface="com.ssm.RoleVerifier"
                         default-impl="com.ssm.RoleVerifierImpl"/>
    
多个切面
  • 当使用多个切面拦截同一个方法时,切面的通知执行顺序是无序的。

  • @Order(n):注解在切面类上,可以使多个切面按照n从小到大按顺序执行通知。

    @Aspect
    //@Order(1) 实现了Ordered接口,代替了@Order注解
    public class Aspect1 implements Ordered {
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
  • 在XML定义顺序

    <aop:aspect ref="aspect1" order="1">
    	...
    </aop:aspect>
    
小结
  • AOP是Spring两大核心之一,通过AOP可以把一些比较公共的方法抽取出来,减少开发者的工作量。

Spring和数据库编程

传统的JDBC代码的弊端
  • 即使是执行一条简单的SQL,也需要有许多的操作,而且包含大量的try…catch…finally…语句,造成代码泛滥,可读性下降。
配置数据资源
  • 使用简单数据库配置:Spring提供的类是SimpleDriverDataSource,只是一个很简单的数据库连接的应用,不支持数据库连接池,一般只用于测试。
  • 使用第三方数据库连接池:比如DBCP数据库连接池。
jdbcTemplate
  • 是Spring为解决传统JDBC代码的弊端,所提供的解决方案,但不算成功。
MyBatis-Spring项目
  • 配置SqlSessionFactoryBean,提供SqlSessionFactoryBean去支持SqlSessionFactory的配置,然后产生SqlSession。

    引入MyBatis的配置文件,使得SqlSessionFactoryBean的配置不全部依赖于Spring提供的规则,导致配置复杂。

    <bean id="SqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    	<property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:sqlMapConfig.xml"/>
    </bean>
    

    MyBatis配置文件

    <configuration>
        <!-- 配置设置 -->
    	<settings>
        	<!-- 开启缓存 -->
            <setting name="cacheEnabled" value="true"/>
            <!-- 自动生成主键 -->
            <setting name="useGeneratedKeys" value="true"/>
            ...
        </settings>
        
      	<!-- 别名配置 -->
        <typeAliases>
        	<typeAlias alias="role" type="com.ssm.Role"/>
        </typeAliases>
        
        <!-- 指定映射器路径 -->
        <mappers>
        	<mapper resource="com/ssm/mapper/RoleMapper.xml"/>
        </mappers>
    </configuration>
    
  • 配置MapperFactoryBean,因为RoleMapper是一个接口,不是类,没有办法产生实例,所以需要对映射器接口进行配置,可以通过配置MapperFactoryBean实现我们想要的Mapper。

    <bean id="roleMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <!-- 指定被扫描为Mapper的接口类 -->
    	<property name="mapperInterface" value="com.ssm.mapper.RoleMapper"/>
    	<property name="SqlSessionFactory" ref="SqlSessionFactory"/>
    </bean>
    

    mapperInterface是映射器的接口。

    当项目比较大时,一个个配置Mapper会造成配置量大的问题,不利于开发。

  • 配置MapperScannerConfiger:通过扫描的形式进行配置Mapper类

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    	<property name="basePackage" value="com.ssm.mapper"/>
        <property name="SqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 指定标注才扫描成为Mapper -->
        <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>
    

    basePackage:指定让Spring自动扫描什么包。

    annotationClass:如果类被**@Repository**注解标识了,才会对其进行扫描,注册成为Mapper。

    SqlSessionFactoryBeanName:指定在Spring中定义的SqlSessionFactory的Bean的名称。

    makerInterface:指定实现了什么接口就认为是Mapper,需要提供一个公共的接口去标记。

    @Repository:注解在类上,标志这是一个DAO层,当Spring扫描时,将有该注解的接口扫描为Mapper对象,然后放入Spring IoC容器中。

深入Spring数据库事务管理

Spring数据库事务管理器的设计
  • Spring中数据库事务是通过PlatformTransactionManager这个事务管理器进行管理的,支持事务的TransactionTemplate模板是Spring提供的事务管理器的模板。

  • 1.TransactionTemplate中有一个PlatformTransactionManager接口的属性,事务的创建、提交和回滚都是通过PlatformTransactionManager接口来完成。

    2.当事务发生异常时,默认所有的异常都会回滚,但可以通过配置去修改某个异常是否需要回滚。

    3.当没有异常时,提交事务。

配置事务管理器
  • Mybatis框架使用最多的是DataSourceTransactionManager数据源事务管理器。

  • 在XML配置文件中配置事务管理器,需要在XML中加入事务命名空间

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 注入数据库连接池 -->
    	<property name="dataSource" ref="dataSource"/>
    </bean>
    

    在事务管理器中注入数据源,表示你将数据库事务委托给该事务管理器管理。

  • Spring中可使用声明式事务和编程式事务,声明式事务又分为XML配置和注解事务,推荐使用注解@Transactional

  • 用Java配置方式实现Spring数据库事务

    需在配置类中实现TransactionManagementConfigurer接口的annotationDrivenTransactionManager方法Spring会把该方法返回的事务管理器作为程序中的事务管理器。

    @Configuration
    @ComponentScan("com.ssm.*")
    @EnableTransactionManagement
    public class JavaConfig implements TransactionManagementConfigurer {
        @Override
        @Bean(name = "transactionManager")
        public PlatformTransactionManager annotationDrivenTransactionManager() {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
    }
    

    @EnableTransactionManagement:注解在配置类上,表示使用事务管理器。

声明式事务
  • Transactional的配置项:编程式事务允许的自定义接口–TransactionDefinition,可以又XML或者注解**@Transactional**进行配置。

    使用声明式事务需要配置注解驱动:

    <tx:annotation-driven transaction-manager="tansactionManager"/>
    
  • 声明式事务的约定流程

    @Transactional注解可使用在类或方法上,IoC容器初始化时,Spring读入这个注解或XML配置的事务信息,并放入事务定义类(TransactionDefinition接口的子类)里面。运行时,Spring拦截带有注解的方法或类里的所有方法,进行事务操作,运用了Spring AOP技术,开发者只需编写业务代码和配置事务属性。

    首先Spring通过事务管理器创建事务,将事务定义类中的属性根据配置在事务上设置,然后执行开发者提供的业务代码,最后提交或者回滚事务。

数据库的相关知识
  • 数据库事务ACID特性

    1.原子性:整个事务的所有操作,要么全部完成,要么全部不完成,不会停留在中间环节,如果发生错误会回滚。

    2.一致性:事务必须始终保持系统处于一致的状态。

    3.隔离性:两个事务间的隔离程度。

    4.持久性:事务完成后,数据持久保存在数据库中,不会回滚。

  • 隔离级别:隔离级别在一定程度上可以减少丢失更新

    按照SQL的标准规范,把隔离级别分为4层:

    1.脏读:允许一个事务去读取另一个事务没提交的数据。

    2.读/写提交:一个事务只能读取另一个事务已经提交的数据。消除了脏读的问题。但会造成不可重复读问题。

    3.可重复读:针对数据库同一条记录而言,使得同一条数据库记录的读/写按照一个序列化进行操作,不会产生交叉情况,保证了一条数据的一致性。消除了不可重复读问题,造成幻读问题。

    4.序列化:让SQL按照顺序读/写的方式,能消除数据库事务间产生数据不一致问题。

选择隔离级别和传播行为
  • 选择隔离级别:从脏读到序列化,系统性能会直线下降。

    为了提高并发,压制脏读,都会选择读/写操作的方式设置事务。但会带来数据不一致性问题。

    当并发量不大或根本不用考虑时,使用序列化隔离级别。

  • 传播行为:指方法之间的调用事务策略的问题。一个方法调用另一个方法,可以对事务的特性进行传播配置,成为传播行为。

    Spring中的传播行为的类型,通过一个枚举类去定义,该类为:org.springframework.transaction.annotation.Propagation,共有7种传播行为。

    REQUIRED:最常用,也是默认传播行为。含义:方法调用时,如果当前不存在事务,就启动事务;如果存在,就沿用下来。

    REQUIRES_NEW:无论是否存在当前事务,方法都会在新的事务中运行。

    NESTED:嵌套事务,如果调用方法时抛出异常,只回滚自己内部执行的SQL,不会回滚主方法的SQL。

@Transactional的自调用失效问题
  • 注解@Transactional的底层实现是Spring AOP技术,使用的是动态代理,这意味这对于静态方法和非public方法,使用@Transactional是失效的。

  • 自调用。自己的一个方法调用自己的另一个方法的过程,不会产生代理对象,所以@Transactional会失效。

    解决:1.使用两个类存放方法。2.从容器中获取接口的代理对象。

典型错误用法的剖析
  • 错误使用Service:当我们想插入两个角色时,我们可能会使用两次对应Service的插入方法,这是很常见的情况,但这样会使两个插入操作不在一个事务当中,而是开启了两个事务。
  • 过长时间占用事务:与数据库无关的操作应该放在Controller中。使事务完成相关业务操作后,立即释放数据库事务资源,避免长时间占用数据库事务,导致系统性能下降。
  • 错误捕捉异常:对于业务代码中的try…catch,发生异常时,我们应该抛出异常,而不是做其他处理,这样才符合Spring事务管理的约定流程。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值