Spring

  2019-5-28 


Spring思想

Spring框架中,core组件是其它组件的基础(其它组件继承core组件)

①控制反转IoC:控制权的反转,把创建对象的权利交给框架(或工厂),包括依赖注入和依赖查找,目标是降低耦合,依赖关系(当前类中需要用到的其它类对象)的管理全部交给Spring维护(只需要配置中写上)

反射可以做到解耦:编译期不依赖、运行时依赖

bean:可重用组件(拥有get、set等方法,且其它成员和方法全为private的JAVA类)

利用反射机制,加载bean配置文件来加载类[类加载解耦]+(单例)bean工厂[类依赖解耦] => IoC模式=====>再发展一下,专门用框架来做这个bean工厂做的事情=>Spring框架(一部分)

②面向切片AOP:由Spring框架提供动态代理技术具体实现

Spring IoC核心容器-ApplicationContext

Spring IoC核心容器替代那个工厂,可以用xml形式、注解形式描述bean

Spring容器的配置文件是一个map结构(注意配置properties是内存中的数据结构,和bean描述文件xml不一样)

instance1="com.aisaka.instance1"
instance2="com.aisaka.instance2"

然后在我们的程序中就可以获取Spring IoC核心容器对象

  1. 两个接口-BeanFactoryApplicationContext

    ApplicationContext:立即创建对象或延迟创建对象(读取完配置文件立即加载)——单例对象适用;更多采用此接口,因为ApplicationContext可以根据对象是单例还是多例智能选择立即创建还是延迟创建(也可以自己设置)

    BeanFactory:延迟创建对象(什么时候根据id获取对象,什么时候加载;也有XMLBeanFactory()等,使用方法一样)——多例对象适用

  2. ApplicationContext加载bean配置

    ApplicationContext是抽象的,主要有三种具体实现(对应三种 bean配置加载方法)

    ClassPathXmlApplicationContext()加载类路径下的XML Bean配置描述文件

    FileSystemApplicationContext()加载磁盘任意路径下的XML Bean配置描述文件

    AnnotationConfigApplicationContext()读取注解创建容器(实验室项目都是这个),括号里需要写清basepackage

    注意:Spring核心容器只是加载配置,它是原先工厂的角色,实际类是实际类,只是工厂来负责和它交流

  3. Bean创建的三种方式

    ①默认构造器:如果没有其它属性,会使用默认构造器创建,没有则报错

    <bean id="instance",class="com.aisaka.Instance"></bean>

    显然,创建对象的时候,Spring都是通过反射找到类进行创建对象的(与前面Spring思想将的那个工厂一样)

    ②使用普通工厂中的方法创建对象,并将该工厂存入Spring容器

    <!--工厂-->
    <bean id="instanceFactory",class="com.aisaka.Factory.instanceFactory"> </bean>
    <!--工厂负责创建的对象,factory-bean为工厂类id,factory-method为工厂类的获取对象方法-->
    <bean id="instance",factory-bean="instanceFactory",factory-method="getInstance"> </bean>

    ③使用工厂中的静态方法创建对象,并将工厂存入Spring容器

    <!--"com.aisaka.StaticFactory.instanceFactory"为静态工厂,getInstance为静态工厂的创建对象方法,"instance"为负责创建对象的id-->
    <bean id="instance",class="com.aisaka.StaticFactory.instanceFactory",factory-method="getInstance"></bean>

    注意,bean对象其实就是一般的对象实例化(用反射的方式),只是让Spring来做了而已,类加载和类实例化两个阶段依然和以前一样

  4. bean对象的作用范围

    scope属性指定bean的作用范围:

    "singleton"单例的(默认值)

    "prototype":多例的

    "request":作用于web应用的请求范围

    "session":作用于web应用的会话范围

    "global-session":作用于集群环境的会话范围(全局会话范围),当不是集群的时候就是session

  5. bean对象的生命周期

    单例对象:和IoC容器生命周期一样(容器创建时创建,容器销毁时销毁)

    多例对象:使用对象时Spring框架为我们创建,且不随容器的销毁而销毁,可由JAVA GC销毁

  6. 使用getbean()方法从容器获取对象

  7. ApplicationContext接口类型不包含close销毁方法,其子类才包含销毁方法

Spring 依赖注入

依赖注入:Spring对依赖关系的维护,即将当前类中需要用到的其它类对象交给当前类,这原来是在程序中写,而现在我们直接在xml bean配置文件中搞定

同时,bean对象的初始化的时候需要传入的初始化参数(见注入方式)也全部写在bean配置中。注意这个初始化参数是在程序跑起来之前需要传递的,可以理解为配置数据,比如数据库连接对象参数什么的,都用注入来传递,动态接受的数据就程序里赋值就行了。

比如DAO层、server层,我们通过容器初始化DAO单例对象和server单例对象,然后将DAO层的单例bean对象以参数形式注入进server层的单例bean对象中(也可以在server中获取IoC核心容器来获取DAO对象,但这样不是非侵入式的,改动参数得改动代码),这样程序就大大解耦了

  1. 三类能注入的数据

    ①基本类型和String

    ②其他bean类型(在配置文件中或注解配置过的bean)

    ③复杂类型、集合类型

  2. 注入方式

    ①使用构造函数注入(除了必须采用这种方式,一般不用)

    默认使用默认构造器

    或者添加<bean>的下级标签constructor-arg

    <bean id="instance",class="com.aisaka.Instance">
    	<constructor-arg type="",index="",name="",value="",ref=""></constructor-arg>
        <constructor-arg type="",index="",name="",value="",ref=""></constructor-arg>
    </bean>

    type,index,name来指定构造器哪个参数,常用name

    value用于给指定好的构造器参数赋值;

    ref用于给指定好的构造器参数赋值,但是赋的值是一个对象的id,这个对象必须是在配置文件中或注解配置过的bean

    每一个标签指定一个参数并给其赋值

    这种方式的优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功

    弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据也必须提供

    使用set方法注入(常用)

    添加<bean>的下级标签property

    <bean id="instance",class="com.aisaka.Instance">
        <property name="",value="",ref=""></property>
        <property name="",value="",ref=""></property>
    </bean>

    属性有name,value,ref

    name寻找注入对象中的set方法名称。假如是setName(),那么name="name",小写且只看后面的

    其它同理

    set方法创建对象的时候没有明确限制,可以使用默认构造函数,但获取对象时set方法有可能没有执行(毕竟自己手写的,可能漏了)

    对于集合,需要使用集合标签注入,比如:

    <bean id="instance",class="com.aisaka.Instance">
        <property name="myStrs">
        	<array>
            	<value>abc</value>
                <value>bfg</value>
            </array>
        </property>
        <property>
        	<map>
            	<entry key="1",value="abc"></entry>
                <entry key="2">
                	<value>efg</value>
                </entry>
            </map>
        </property>
    </bean>

使用注解

替代XML描述JAVA BEAN对象

使用注解需要在XML标签的头部换成另一个!!!加入content注解

<context:component-scan base-package=”com.aisaka”>,引入注解扫描区域com.aisaka

可以创建对象、注入数据、改变作用范围、生命周期相关

  1. 创建bean对象

    @Component:把当前类对象存入Spring容器中

    属性,@Component(value)value指定bean的id,如果不写属性则默认为类名首字母小写

    与在xml中写<bean>对象是一样的

    Controller注解表现层,Service注解业务层,Repository注解持久层(这四个功能都一样)

    这个只是为了让代码清晰

    @Bean:把当前方法的返回值(对象)作为bean对象存入spring的IoC容器中

    属性name用于指定bean的id,默认值(不写)是当前方法的名称

    @Component一样可以创建bean对象并存入spring容器中,两者区别:https://blog.csdn.net/w605283073/article/details/89221522

    注意默认都是创建单例bean对象,需要用scope改变

    通常使用@Bean的方式创建,都是把所有@Bean创建方法写在一个beanConfig类里面

    @Component@Bean的区别:

    @Component用于自动检测和使用类路径扫描自动配置bean,其写在一个完整的类定义的上面bean类的声明和定义在一起

    而@Bean是作用于一个返回对象的方法(create方法),bean类的声明和定义是分开的,且不会自动扫描,所以通常搭配@Configuration来让Spring扫描配置类,然后配置类里面配置一堆Bean,这是和xml中声明bean最相似的方法

  2. 注入其它bean类型

    @Autowired自动按照类型(可以是高层接口类型)注入数据,Spring会自动搜索满足该类型的bean对象类完成注入。此时类中的set方法就不用写了

    @Qualifier:按照类型+名称注入,属性value指定注入bean的id

    @Resource:属性name指定bean的id

    集合只能用XML注入

    @Value:注入基本数据和String,属性value即注入值 【SpEL写法?】

  3. 改变bean对象作用范围

    @Scope:作用同xml中的scope

    默认单例,如果是多例就要写@Scope("prototype")

  4. @Configuration:配置类注解

    使用了这个注解就相当于在XML中配置了<context:component-scan base-package=”com.aisaka”>

    它指示一个类声明一个或多个@Bean方法,并且可以由Spring容器处理,以便在运行时为这些bean生成BeanDefinition和服务请求,举例:

    @Configuration
    public class AppConfig {
    
        @Bean
        public MyBean myBean() {
            // instantiate, configure and return bean ...
        }
    }

    @ComponentScan:指定注解创建容器的时候扫描的包

    比如上面新建一个配置类Appconfig,然后给它标上@Configuration,那么然后注解IoC核心容器的参数写Appconfig.class,即写成AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Appconfig.class);就可以解析bean配置获取IoC容器了

    加载class,即加载的是字节码

    可以通过@Import,参数填其他配置配的字节码XXX.class,来加载其他配置类,且有import的类即为主配置类,会被IoC核心容器加载(IoC核心容器参数就填主配置类)

    IoC核心容器的参数其实也可以直接填多个配置类(这样就不用@Configuration声明了),但这样不好

  5. @Configuration、+注解创建bean,就可以完全替代XML配置Bean办法了(但这并不一定是最好的)

  6. @PropertySource:指定properties文件(Key=Map格式)的位置(略)

    https://www.cnblogs.com/cxuanBlog/p/10927823.html

关于测试junit

测试方法中(@Test标签)没有IoC容器,如果Spring要整合junit配置:①导入spring整合junit的jar包②使用junit提供的一个注解把原有的main方法替换成spring提供的:@Runwith(SpringJUnit4ClassRunner.class)③告知spring运行器,spring和ioc创建是基于xml还是注解的,并说明位置,用@ContextConfiguration

【略】

事务控制与动态代理

  1. 什么是事务:

    就是一组操作数据库的动作集合。

    一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。

    事务必须满足ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)的缩写。

    事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。持久性表示当系统或介质发生故障时,确保已提交事务的更新不能丢失。持久性通过数据库备份和恢复来保证。

  2. 最原始的事物处理:(中间产生的类建议写在一个专门的工具包里)

    通过ThreadLocal对象把Connection(业务从DAO获取一个连接,即指向DAO数据源对象的一个引用)和当前线程绑定,从而使得一个线程中只有一个能控制事务的对象

    为什么?假如一个事务由ABCDE5个步骤组成,其中一步翻车,那这个事务就应该全部失败,如果不使用将事务与线程绑定就会这样,所以需要将这5个步骤即一个事务绑定到一个线程

    于是我们要建立一个用于连接(Connection)的工具类,它专门用于从DAO中获取一个DAO引用(保证多个操作获得的是同一个连接),使用ThreadLocal类实现和线程的绑定

    然后再建立一个专门处理该连接的事务处理工具类,用于处理事务:开启事务、提交事务、回滚事务、释放连接ThreadLocal类对象的方法,不过这个类里先注入连接工具类对象)

    注意这些操作,连接不会和线程解绑,所以我们处理完该事务,要把连接和线程解绑

    于是我们在业务层就需要有一个连接DAO的工具类对象,和一个DAO事务处理类对象(这个DAO事务管理类是能够获取到当前线程和与当前线程绑定的连接的)

    在每一个业务层的操作都会调用:try:开启事务、执行操作(非事务处理工具类,即业务代码)、提交事务、回滚事务(catch)、释放连接(finally)

    然后我们就发现,各种依赖关系,太复杂了,于是引入后面的Spring事务控制

  3. 通过动态代理实现事务控制与目标业务的分离

    静态代理可以做到非侵入式的修改,方法增强

    但是我们给每个类都写一个静态代理可太麻烦了,而且如果我们想对这些类统一做一个修改,比如在生成实际类对象之前有统一的操作,那么还是得一一修改代理类

    于是我们用动态代理,可以做到动态指定代理对象和代理方法(就是AOP面向切面编程的基本思想)具体回顾前面:JAVA类型信息-动态代理

    动态代理特点:class字节码随用随修改,可以在不修改原码的基础上对方法加强

    分为:①基于接口的动态代理(前面所讲,需要被代理类实现一个接口)

    ②基于子类(非接口)的动态代理

    需要第三包jar包支持:cglib(略,得学)

    然后我们就可以用动态代理来实现事务控制:被代理对象即业务层对象,业务层对象中的任何方法调用都会先经过动态代理对象,在动态代理对象中,就会在原先的事物处理中加上事物控制代码。此时,事物控制代码(增强代码)和业务代码(原先目标代码)就做到了解耦

  4. 基于动态代理技术,自然引出Spring的第二大核心思想——AOP

P.S. 一般J2EE服务器支持三种类型的事务管理。即:JDBC事务,JTA事务,容器(如Spring)管理事务,三者只存在一个

Spring AOP

AOP是Spring的第二个核心,即面向切片编程

AOP使用的就是动态代理技术

Spring AOP为我们提供配置的方式来实现动态代理

  1. 一些术语

    joinpoint-连接点:被拦截到的点

    pointout-切入点:需要增强的连接点才是切入点(被动态代理的方法不一定要增强,在代理对象中如果判断不增强那就不是切入点)

    advice-通知:拦截到之后要干的事情,根据增强的语句与切入点的位置关系分为前置、后置、异常(catch)、最终(finally块中的)、环绕(包含以上四种)通知

    Introduction-引介(略)

    Target-目标:被代理对象(目标对象)

    Weaving-织入:增强应用到目标对象的过程

    Proxy:代理对象

    Aspect-切面:切入点与通知的结合(即整个增强后的代码块)

    一个团队里有业务编程人员(目标功能实现),AOP编程人员(抽取公共代码,配置AOP,织入写切面)

  2. 配置Spring AOP

    需要导入org.aspectj包来解析切入点表达式

    通知类就是储存增强方法的类切入点就是被切入类的待增强方法

    ①基于XML配置AOP

    Spring Framework中AOP的内容也要导入XML开头声明

    先配置Spring的IoC,把service的bean对象配置进xml

    S1:把通知bean也交给Spring来管理

    S2:使用aop:config标签表明开始AOP的配置

    S3:使用aop:aspect标签表明开始配置切面

    其中id属性:给切片提供一个唯一标识符,ref属性:指定通知类的bean的id

    S4:在aop:aspect标签的内部使用对应标签来配置通知的类型

    aop:before标签配置前置通知(后置aop:after-returning、异常(aop:after-throwing)、最终aop:after标签一样)

    对于以上通知配置标签中:

    属性method通知类中的哪个方法属于前置通知

    属性pointcut是用于切入点表达式,该表达式含义指的是对业务层中哪个类的哪个方法进行增强

    切入点表达式的写法:关键字:execution(表达式),表达式的写法:访问修饰符 返回值 包名.类名.方法名(参数列表) (访问修饰符可以省略;解析切入点表达式就是aspectj提供的功能)

    切入点表达式的全通配符写法* *..*.*(..) (自己根据实际定义哪些需要通配)

    <!--配置业务类-->
    <bean id="serviceImp" class="com.aisaka.serviceImp"></bean>
    
    <!--配置通知类-->
    <bean id="advice" class="com.aisaka.service.adivce"></bean>
    
    <!--配置AOP-->
    <aop:config>
    	<!--配置切面-->
        <aop:aspect id="advice" ref="advice">
        	<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="adviceMethod" pointcut="execution(* com.aisaka.service.adivce.*(*))"></aop:before>
        </aop:aspect>
    </aop:config>

    还有个特殊的配置切入点表达式标签:<aop:pointcut>,改为下面这样可以方便表达式重用(可以写在切面层级够整个切面用,或里面的层级用)

    <aop:before method="adviceMethod" pointcut="ex1"></aop:before>
    <aop:pointcut id="ex1" expression="execution(* com.aisaka.service.adivce.*(*))"></aop:pointcut>
        

    环绕通知<aop:around >比较特殊,通知方法必须传入一个已经由Spring实现的ProceedingJoinPoint实现类,然后在通知方法中具体实现ProceedingJoinPoint类对象.proceed()来明确调用的业务层方法在什么位置(且该方法必须抛出Throwable错误,而不是Exception),写在其前面就是前置,写在其后面就是后置,写在异常就是balbala。实际上环绕通知就是Spring提供给我们可以在通知类的通知方法代码中手动控制增强方法合适执行的方式。

    示例:

     @Around("pt1()")
        public Object aroundPringLog(ProceedingJoinPoint pjp){
            Object obj = null;
            try {
                Object[] args = pjp.getArgs();// 得到方法所需的参数
                System.out.println("环绕通知:前置...");
                //明确调用业务层方法
                obj = pjp.proceed(args);
                System.out.println("环绕通知:后置...");
                return obj;
            } catch (Throwable throwable) {
                System.out.println("环绕通知:异常...");
                throw new RuntimeException(throwable);
            }finally {
                System.out.println("环绕通知:最终...");
            }
        }
    
    //示例参考链接:https://blog.csdn.net/weixin_43464964/java/article/details/86504129

②基于注解配置AOP

在通知类前声明@Aspect

在通知方法前声明不同通知类型:前置@Before、后置@AfterReturning、异常@AfterThrowing、最终@After

环绕通知:@Around

切入点表达式,直接作为上述通知类型的属性

也可以在通知类里定义一个切入点表达式方法:

@Pointcut(切入点表达式)
private void pt1(){}

然后这个pt1()名字传入上述通知类型的属性中,如@Before("pt1()")

然后再xml中写上<aop:aspectj-autoproxy></aop:aspectj-autoproxy>,表示开启注解AOP支持

或者写:@EnableAspectJAutoProxy这样XML就可以删了

注解AOP方式非常简单好写,建议注解

  1. AOP实现事务控制

    显然我们就可以把前面提到的事务处理工具类作为通知类了,然后将其中的开启事务、提交事务、回滚事务、释放连接方法分别以前置、后置、异常、最终通,为服务层类配置AOP

    P.S.但注意,在AOP中这样会有通知调用顺序问题,这时候需要用环绕通知来配置原因:

    在使用除环绕通知的其他通知时,他们的顺序并不是一定的,最终通知会执行优先于后置通知与环绕通知(这是java异常处理里的内容,有时候finally块会先执行【】),因此当我们使用最终通知来释放一些资源的时候,可能会出现资源已经释放,但是后置通知仍在使用的情况,这时就会出现错误,因此我们遇到这种情况时要特别注意,为了保证通知的顺序,我们必要的使用环绕通知,环绕通知的执行顺序是一致的。)

Spring 事务控制【略】

事务是在业务层的

Spring提供的事务管理模块:spring-tx

提供了一堆接口,其中实现类之一为org.springframework.jdbc.datasource.DataSourceTransactionManager,使用Spring JDBC或iBatis进行持久化数据时使用

事务控制有基于XML的,基于注解的,也可以基于纯编程的,参考5种Spring配置事务的方式

Spring JdbcTemplate【略】

JDBC:JAVA DataBase Connectivity ,JAVA数据库访问技术,是JAVA数据库访问的标准规范,一切JAVA数据库访问都可以通过JDBC来完成

JdbcTemplate是Spring框架提供的一个对象,是对原始JDBC API的简单封装

相关依赖:spring-jdbc,spring-tx,mysql-connector-java

通过IoC容器JdbcTemplate实例(并将数据源Datasource注入其中),然后就可以进行

CRUD操作:增加(Create)、读取(Read)、更新(Update)和删除(Delete)

几个Tips:

  1. query结果封装

    query中封装用Spring提供的new BeanPropertyRowMapper<目标类>(目标类.class)

    https://blog.csdn.net/qq_22339269/article/details/82978717

  2. JdbcDaoSupport

    简化Jdbc Template类的注入相关工作,不用我们手写Jdbc Template工具类(包含两组get set方法)了,只需要DAO层类继承该类,然后在XML里将DataSource或Jdbc Template 对象注入DAO层对象即可

    https://blog.csdn.net/mChenys/article/details/89855169

    (不过就只能采用XML配置了,因为我们无法用注解修改Spring提供的JdbcDaoSupport)

表现层、业务层、持久层

表现层[Controller]:表示层(UI)
业务层[Service]:业务逻辑层(BLL):负责关键业务的处理和数据的传递。复杂的逻辑判断和涉及到数据库的数据验证都需要在此做出处理。根据传入的值返回用户想得到的值,或者处理相关的逻辑。
持久层[Repository]:数据访问层(DAO):负责数据库数据的访问。主要为业务逻辑层提供数据,根据传入的值来操作数据库,增、删、改、查;不过注意DAO是负责获取数据库访问对象,并借助该对象直接操作数据库

@controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层

@service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理

@repository(实现dao访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件


且听风吟