Spring学习
something needed
JUnit
与spring-test
单元测试框架Dbutils
数据库工具,QueryRunner类是DbUtils的核心类spring-context
spring容器c3p0
JDBC连接池aspectj
面向切片的框架spring-tx
注解事务
概述
- 概述
- Spring是分层的全栈轻量级开源框架,以IoC和AOP为内核,提供表现层Spring MVC和持久层Spring JDBC以及业务层事务管理等。
- 两大核心:IoC和AOP。
- IoC:控制反转,把创建对象的权利交给框架,削减耦合。
- AOP:面向切片编程
- 优势:方便解耦,简化开发;AOP编程的支持;声明式事务控制;方便测试;集成框架
- 体系结构
- 程序的耦合及解耦
- 耦合:程序间的依赖关系
- 解耦:降低依赖关系。在实际开发中,应编译期不依赖,运行时才依赖。
- 通过读取配置文件来获取要创建的对象全限定类名
- 使用反射来创建对象
Class.forName("com.mysql.jdbc.Driver")
,而避免使用new关键字DriverManager.registerDriver(new com.mysql.jdbc.Driver)
。其他类之间的关系也是一样
基于XML的IOC配置
- IOC概念:控制反转,把创建对象的权利交给框架,削减耦合。
- 基于XML
- pom.xml
- 在java文件夹下面新建dao service ui文件夹,dao是连接数据库的持久层,service是中间处理的逻辑层,ui为表现层
- resources下新建bean.xml,通过这个 把对象创建交给spring来管理,根据标志获取对象
- 在ui中创建一个类,获取spring的ioc容器,获取对象
ApplicationContext
:在构建核心容器时,创建对象采用立即加载,只要一读取完配置文件就马上创建配置文件中的配置对象
- spring对bean的管理细节
- 创建bean的三种方式
- 使用默认构造函数创建。在配置文件中使用bean标签,配以id和class属性之后,且没有其他标签和属性,若类中没有默认构造函数 则创建失败
- 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器),需要指定方法名,属性:
id、factory-bean、factory-method
- 使用静态工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器),属性:
id、class、factory-method
- 在使用外部jar包中的东西时,可能会用到第二三种方法
- bean对象的作用范围,通过bean标签中的scope属性调整。
- 取值:
singleton
单例、默认值;prototype
多例;request
作用于web应用的请求范围;session
作用于web应用的会话范围;global-session
全局会话
- 取值:
- bean对象的生命周期
- 单例:容器创建时 对象出生,容器在 对象在,容器亡 对象亡。
- 多例:使用对象时创建;在使用过程中一直活着;当对象长时间没用,且没有其他机制引用时,由java垃圾回收机制销毁。
- 创建bean的三种方式
- 依赖注入:Dependency Injection
- 依赖关系的维护称为依赖注入
- 能注入的数据:基本类型和String;其他bean类型(在配置文件中或者注解配置过的bean);复杂类型、集合类型
- 注入方式:
- 使用构造函数提供,使用标签
constructor-arg
,放在bean
标签内部,其中的属性有:type 指定要注入的数据的类型;index 指定构造函数中指定索引位置;name 指定构造函数中的数据名称;value 提供数据;ref 指定出现过的bean对象
// 类实现中
private String name;
private Integer age;
private Date date;
public AccountServiceImpl(String name, Integer age, Date date){
this.name = name;
this.age = age;
this.date = date;
}
// xml中
<bean id="accountService" class="com.spring1.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="月亮"></constructor-arg>
<constructor-arg name="age" value="12"></constructor-arg>
<constructor-arg name="date" ref="date"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date"></bean>
- 使用set方法提供,使用标签
property
,放在bean
标签内部,其中的属性有:name 指定注入时所调用的set方法名称;value 提供数据;ref 指定出现过的bean对象 - 使用注解提供。
- 复杂类型的注入。数组、List、Set、Map、Properties。用于给List结构注入的标签有list、array、set;用于给Map结构集合注入的标签有map、props。总结:结构相同 标签可以互换
基于注解的IOC配置
- 常用ioc注解
- 用于创建对象的注解,与在xml配置文件中编写一个
<bean>
标签实现的功能是一样的。@Component
用于把当前类对象存入spring容器中,放在实现类前面,属性为value
用于指定bean的id(默认值为当前类名首字母改小写)[容器.getBean(bean的id)]。下面仨注解的作用和属性与这个一样,但可以使三层对象更加清晰。@Controller
表现层@Service
业务层@Repository
持久层
- 用于注入数据的注解,其作用与
<bean>
标签中写一个<property>
标签的作用一样@Autowired
自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功,如果有多个类型匹配时 再匹配名称。可以放在变量前 也可以放在方法前@Qualifier
在按照类型注入的基础上再按照名称注入,在给类成员注入时不能单独使用(要与Autowired
配合使用),但给方法参数注入时可以。属性:value 用于指定注入bean的id@Resource
直接按照bean的id注入,可以独立使用,属性name
指定bean的id
- 以上3个注入都只能注入其他bean类型的数据,而不能注入基本类型和String类型,另外 集合类型只能通过XML来实现
@Value
用户注入基本类型和String类型的数据,属性value
用于指定数据的值,可以用spring的SpEL(写法:${表达式}
)
- 用于改变作用范围的注解,与使用
scope
属性的作用一样@Scope
指定bean的作用范围,属性value
指定范围的取值,常用singleton prototype
- 和生命周期相关的注解,与在
<bean>
使用init-method
和deatroy-method
属性一样。@PreDestroy
销毁@PostConstruct
初始化
- 用于创建对象的注解,与在xml配置文件中编写一个
- 案例使用xml方式和注解方式实现单表的CRUD操作
- 改造基于注解的ioc案例 -> 纯注解
- 新注解
@Configuration
,指定当前类是一个配置类@ComponentScan
,用于 通过注解指定spring在创建容器时要扫描的包,相当于bean.xml中的context:component-scan
@Bean
,用于把当前方法的返回值作为bean对象存入spring的ioc容器中,属性name
,指定bean的id,默认值为当前方法的名称@Import
,用于导入其他的配置类@PropertySource
,用于指定properties文件的位置,属性 value指定文件的名称和路径
- spring和Junit的整合
把获取容器与获取对象的句子抽离出来,减少冗余
- 导入spring整合junit的jar
spring-test
- 使用Junit提供的一个注解
@RunWith
,把原有的main方法替换了,替换成spring提供的 - 告知spring的运行器,spring的ioc创建是基于xml还是注解的,并说明位置,使用注解
@ContextConfiguration
,属性 locations指定xml文件的位置,加上classpath关键字 表示在类路径下;classes指定注解类所在的位置
动态代理
- 特点:字节码随用岁创建,随用随加载
- 作用:不修改原码的基础上对方法增强
- 分类:
- 基于接口的动态代理
- 涉及的类:Proxy,使用Proxy类中的newProxyInstance方法创建代理对象,被代理类最少实现一个接口。
- newProxyInstance方法的参数:
- ClassLoader 类加载器,用于加载代理对象字节码,和被代理对象使用相同的类加载器。写法固定
- Class[] 字节码数组,用于让代理对象和被代理对象有相同的方法。写法固定
- InvocationHandler,用于提供增强的代码,让我们写如何代理,一般需要写一个该接口的实现类,通常是匿名内部类,但不是必须得。此接口的实现类都是谁用水写。
- 做法:建立一个类
factory.BeanFactory
,用于创建Service的代理对象。减少了写事务相关的代码
- 基于子类的动态代理
- 涉及的类:Enhancer,使用Enhancer中的create方法创建代理对象,被代理类不能是最终类。create中的参数:
- Class 字节码,指定被代理对象的字节码。
- Callback 用于提供增强的代码,一般写的都是该接口的子接口实现类 MethodInterceptor
- 基于接口的动态代理
AOP 面向切片编程
- 把通用逻辑从业务逻辑中分离出来,降低耦合
- 基本术语:
- Join point: 拦截点,如某个业务方法。
- Pointcut: 切入点,Joinpoint 的表达式,表示拦截哪些方法。一个 Pointcut 对应多个 Joinpoint。
- Advice: 要切入的逻辑。
? BeforeAdvice 在方法前切入。
? After Advice 在方法后切入,抛出异常时也会切入。
? AfterReturningAdvice 在方法返回后切入,抛出异常则不会切入。
? AfterThrowingAdvice 在方法抛出异常时切入。
? Around Advice 在方法执行前后切入,可以中断或忽略原有流程的执行。
spring中基于xml的AOP配置步骤:
- 把通知Bean交给spring来管理
- 使用
aop:config
标签开始AOP配置 - 使用
aop:aspect
标签表明配置切面- id属性:是给切面提供一个唯一标识
- ref属性:指定通知类bean的id
- 在
aop:aspect
标签的内部使用对应标签来配置通知的类型aop:before
:表示配置前置通知(在切入点方法执行之前执行)。method
属性指定Logger类中哪个方法是前置通知;pointcut
属性 指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:关键字
execution
,表达式:访问修饰符 返回值 包名.包名.包名….类名.方法名(参数列表)- 全通配写法,访问修饰符可以省略: .. . (..)
- 实际开发中,切入点表达式的通常写法:切到业务层下的所有方法: com.spring5.service.impl. . *(..)
有多个通知时,可以把表达式抽离出来单独写,如:
aop:pointcut
可以放在aspect
前面,这样就变成了所有切面可用<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<aop:pointcut id="pt1" expression="execution(* com.spring5.service.*.*(..))"/>
环绕通知
aop:around
,根据位置不同而设置为不同的通知
基于注解的AOP配置,使用注解的环绕比其他几个要好
@Aspect
表示当前类是一个切片类切入点表达式:
@Pointcut("execution(* com.spring5.service.*.*(..))")
private void pti(){ }
@Before("pti()")
前置通知…@Around("pti()")
环绕通知,根据语句的位置不同而设置为不同的通知
JdbcTemplate
- spring的JdbcTemplate,操作关系型数据库
- 作用:用于和数据库交互的,实现对表的CRUD操作
如何创建该对象
- 先在
bean.xml
中配置,再获取容器、获取对象:ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
- 先在
对象中的常用方法
- 保存:
jt.update("insert into account(name,money) value(?,?)","aho",123);
- 更新:
jt.update("update account set money=? where id=?",90,2);
- 删除:
jt.update("delete from account where id=?",3);
查询所有:
List<Account> accounts = jt.query("select * from account where money>?",new BeanPropertyRowMapper<Account>(Account.class),1000);
for 输出
查询一个:
List<Account> accounts = jt.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),4);
System.out.println(accounts.ifEmpty() ? "没有内容" : accounts.get(0));
查询返回一行一列:
Long count = jt.queryForObject("select count(*) from account where money>?",long.class,1000);
- 保存:
Spring事务控制
- 当一个方法中要与数据库进行多次操作(如 获取数据之后再存入数据),每次操作的获取一个单独的连接,从而当中间出现异常时,前面的连接正常执行 而之后的连接就无法进行。
- so需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象
基于xml的声明式事务控制
spring
中基于xml的声明式事务控制的步骤:- 配置事务管理器
- 配置事务的通知
- 此时需要导入事务的约束、tx的命名空间和约束,也需要aop的
- 使用
tx:advice
标签配置事务通知,属性:id
给事务通知起一个唯一标识;transaction-manager
给事务通知提供一个事务管理器引用
- 配置AOP中的通用切入点表达式
- 建立事务通知和切入点表达式的对应关系
- 配置事务的属性
- 在事务的通知标签内部配置,有不少的参数,没写
基于注解的事务控制
spring
中基于注解的声明式事务控制配置步骤:- 配置事务管理器
- 开启
spring
对注解事务的支持 - 在需要事务支持的地方使用
@Transactional
,其余都换成注解方式的数据注入