IOC 和 DI模块
在spring中IOC和DI是两个不同的概念,但是又互相依赖、相辅相成。
第一个Spring程序 helloWord
新建一个HelloWorld类
public class HelloWorld {
private String username;
public void setUsername(String username) {
this.username = username;
}
public void sayHello(){
System.out.println("欢迎你"+username);
}
}
在不用Spring之前,我们的测试方法是这样的
@Test
public void test(){
HelloWorld h = new HelloWorld();
h.setUsername("123");
h.sayHello();
}
来看看使用了Spring之后的 先在项目中新建resources 目录 然后在该目录下新建applicationContext.xml加入scheme
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
">
</beans>
然后新建lib目录 拷贝spring-beans-5.0.4.RELEASE.jar、spring-core-5.0.4.RELEASE.jar 并buildPath
下面通过配置文件来告诉Spring帮我们管理哪些类
在applicationContext..xml中 加入
<bean id="HelloWorld" class="com.xiaowww.hello.HelloWorld" />
小知识:很多框架都是基于反射,所以一般xml都需要配置id和全限定名 就像web.xml中的过滤器和webServlet一样
配置好了之后我们来用Spring的方法测试
@Test
public void testSpring(){
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
HelloWorld bean = (HelloWorld)beanFactory.getBean("HelloWorld");
bean.setUsername("123");
bean.sayHello();
}
运行之后报错
这是因为Spring依赖于commons的这个jar包 去maven上面下载 commons-logging-1.1.1.jar 添加到项目中,方法正常运行
以上就是用Spring的 IOC去管理我们所需要的类 而被Spring所管理的类我们称之为Bean
那什么是DI呢 DI就是依赖注入 也就是用Spring来帮我们注入某个类的属性值
我们刚刚在applicationContext.xml中创建好了一个HelloWorld 的Bean 实例 我们为这个Bean注入username属性
将刚才applicationContext.xml中的Bean改为上图 测试代码修改为
@Test
public void testSpring(){
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
HelloWorld bean = (HelloWorld)beanFactory.getBean("HelloWorld");
bean.sayHello();
}
还是会输出 注意:此时我们并没有使用Set方法给username去赋值 而是通过Spring的配置去为Bean注入属性值 这个过程就叫做DI 依赖注入 (注入Bean的属性必须存在Set方法否则会报错)
思考:Spring注入值得方式是使用set方法来注入的,那么你能模仿Spring的DI来写一个方法吗?
Spring的IOC和DI的原理 : 解析applicationContext.xml -->获取Bean元素 -->利用全限定名创建对象(反射)-->获取Bean元素中的property子元素的name-->找到该属性的set方法(内省机制) -->为该属性注入值 -->输出Bean元素
Spring创建的对象必须存在无参构造器,与访问权限无关。
BeanFactory获取Bean的三种方式:
Object getBean(String name) throws BeansException;//根据配置的ID获取Bean也就是上面我们使用的
<T> T getBean(Class<T> requiredType) throws BeansException;//根据类型去获取Bean
<T> T getBean(String name, Class<T> requiredType) throws BeansException;//根据名称和类型去获取Bean
一般不同框架的配置我们会分开使用但是会跟Spring整合
Spring引入配置文件 <import resource="classpath:springMvc.xml"/> 默认从ClassPath根路径开始找
SpringJunit的使用
我们平时测试都是用Junit 而Spring集成的Junit会让我们使用起来比上面的方法更简单方便
首先引入jar包spring-context-5.0.4.RELEASE.jar、spring-aop-5.0.4.RELEASE.jar、spring-expression-5.0.4.RELEASE.jar、spring-test-5.0.4.RELEASE.jar 因为相互依赖的关系所以我们需要引入这4个jar包
这就是完整的SpringJunit测试类 Junit5更简单 只需要一个注解 有兴趣的同学自行研究一下
这里的Autowired 就是根据 类型来获取Bean
Spring的IOC模块(container容器)
上面我们介绍了Spring管理Bean的类 也就是BeanFactory ,在实际使用中我们多数不会使用这个接口,而是使用该类的子接口 也就是ApplicationContext (该类除了管理Bean之外还提供了AOP集成、国际化处理、事件传播、统一资源等功能)
BeanFactory和ApplicationContext有什么区别呢?
BeanFactory是延迟加载 而 ApplicationContext 是即时加载; 下面我们来测试一下
//Spring的测试Bean
public class HelloWorld {
public HelloWorld(){
System.out.println("hello");
}
}
//applicationContext.xml中的配置Bean
<bean id="HelloWorld" class="com.xiaowww.hello.HelloWorld" ></bean>
//分别用两个方法测试
@Test
public void testBeanFactory(){
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource );
System.out.println("-------");
HelloWorld hello = (HelloWorld) beanFactory.getBean("HelloWorld");
System.out.println(hello);
}
-------
hello
com.xiaowww.hello.HelloWorld@7dc7cbad
@Test
public void testApplicationContext(){
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("-------");
HelloWorld hello = (HelloWorld) ctx.getBean("HelloWorld");
System.out.println(hello);
}
hello
-------
com.xiaowww.hello.HelloWorld@15d0c81b
我们会发现ApplicationContext对象在获取Bean之前就调用了Bean的构造器,这里大家可以尝试再添加一个Bean
说明ApplicationContext构建对象是在Spring容器启动的时候就构建的
在开发web项目中启动tomcat可能会耗时比较久 ,因为要创建大量的对象 而这么做的好处就是,我一旦启动成功 用户访问的时候便不需要再次创建对象。访问速度就会提升很多 ,这就是为什么使用反射的效率低而Spring却使用大量的反射
lazy-init可以将对象设置为懒加载但是我们一般不这么做 。
小知识:
Spring可以直接帮我们注入BeanFactory和ApplicationContext对象
Bean实例化的几种方式
1、通过无参构造器实例化(最常用,我们上述讲的都是使用这种
public class HelloWorld {
public HelloWorld(){
System.out.println("创建了hello");
}
}
<bean id="HelloWorld" class="com.xiaowww.hello.HelloWorld" ></bean>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class HelloTest {
@Autowired
HelloWorld helloWorld;
@Test
public void testBeanFactory(){
System.out.println(helloWorld);
}
}
调用构造器打印出Bean对象
2、通过静态工厂方法创建对象-->无非就是告诉Spring你有一个类,类里面有个方法可以创建这个对象然后返回
public class HelloWorld {
public HelloWorld(){
System.out.println("创建了hello");
}
}
public class HelloWorldFactory {
public static HelloWorld creat(){
HelloWorld h = new HelloWorld();
return h;
}
}
<bean id="h1" class="com.xiaowww.hello.HelloWorldFactory" factory-method="creat" ></bean>
注意这里配的不是Bean而是Bean的Factory
public class HelloTest {
@Autowired
HelloWorld helloWorld;
@Test
public void testBeanFactory(){
System.out.println(helloWorld);
}
}
同样可以成功创建 -->Autowired 注解始终是以类型装配的所以我的id取h1也不会影响
3、通过实例工厂创建Bean
public class HelloWorld {
public HelloWorld(){
System.out.println("创建了hello");
}
}
public class HelloWorldFactory {
public HelloWorld creat(){
HelloWorld h = new HelloWorld();
return h;
}
}
<bean id="HelloWorldFactory" class="com.xiaowww.hello.HelloWorldFactory" ></bean>
<bean id="HelloWorld" factory-bean="HelloWorldFactory" factory-method="creat"></bean>
public class HelloTest {
@Autowired
HelloWorld helloWorld;
@Test
public void testBeanFactory(){
System.out.println(helloWorld);
}
}
跟上个方法基本一样,只不过一个通过类调用静态方法,一个通过类的实例调用对象普通方法,注意xml配置的变化
4、实现FactoryBean来创建对象
public class HelloWorld {
public HelloWorld(){
System.out.println("创建了hello");
}
}
//泛型就是你要创建的对象类型
public class HelloWorldFactory implements FactoryBean<HelloWorld>{
//创建你需要的对象
@Override
public HelloWorld getObject() throws Exception {
HelloWorld h = new HelloWorld();
return h;
}
//返回对象的类型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return HelloWorld.class;
}
}
//由于你实现了Spring的接口,所以Spring就知道肯定是调用getObject()来获取Class<?>的对象,这也是为什么第三种方法你需要配置factory-method="" 而这种方法不需要
<bean id="HelloWorld" class="com.xiaowww.hello.HelloWorldFactory"></bean>
public class HelloTest {
@Autowired
HelloWorld helloWorld;
@Test
public void testBeanFactory(){
System.out.println(helloWorld);
}
}
FactoryBean 接口中有一个方法 是否使用单例来创建对象,你也可以覆盖该方法 return false; 来强行使用多例 、意义不大;
FactoryBean的实现类中也可以注入属性
Bean的作用域
Spring默认的作用域属性为singleton (单例) 我们使用最多的也是这种 一般也无需设置
Bean的初始化和销毁
在没有spring之前我们是怎么做初始化和销毁的
public class HelloWorld {
public HelloWorld(){
System.out.println("创建了HelloWorld");
}
public void init(){
System.out.println("初始化了HelloWorld");
}
public void doSome(){
System.out.println("执行了HelloWorld");
}
public void destory(){
System.out.println("销毁了HelloWorld");
}
}
@Test
public void testBeanFactory(){
HelloWorld h = new HelloWorld();
h.init();//相当于数据库连接 Connection conn =
DriverManager.getConnection(url,username,pwd);
h.doSome();//相当于逻辑操作增删改查 也是我们最重要的东西
h.destory();//相当于 rs.close;statement.close;conn.close;
}
但我们最关心的是doSome()方法,所以我们会把 init() 和 destory() 提取出来 把doSome做成一个抽象方法,这样就可以不必每次都写这样的代码, 而Spring也帮我们想到了这一点 下面我们来看下Spring是如何实现的
我们只需要在配置Bean的时候指定 init-method 、destroy-method 便可以实现
注意:如果Bean配置的作用域是多例的情况下,Spring默认不会执行destroy-method 还有种情况是Spring容器没有正常关闭也不会调用destory方法 类似于 tomcat 如果不正常关闭 也不会调用Servlet的destory方法
Bean的生命周期
1、启动Spring容器
2、调用Bean构造器创建Bean对象、为对象设置属性
3、调用 init-method
4、执行我们自己的逻辑
5、调用destory方法
6、关闭Spring容器
这些在前面我们基本上已经列举完了 但其中的细节还有非常之多
例如InitializingBean接口 ,这个接口有一个afterPropertiesSet 方法 ,该方法是在设置完属性调用init-method方法之前执行
我们可以让我们的Bean来实现该接口并覆盖 afterPropertiesSet 方法 会看到这样的执行顺序
创建了HelloWorld
设置完属性了
初始化了HelloWorld
执行了HelloWorld
销毁了HelloWorld
DI 依赖注入
简单来说就是给对象设置值 跟我们调用Setter方法 。或者用构造器设置属性值是一个意思
平时我们给对象设置属性的时候,一般会有三种值得类型 1、简单类型 2、对象类型 3、集合类型
DI无非也就是对这三种值设置的封装 ,方式不同而已 @Autowired 就是典型的DI
public class Person {
private Dog dog;
private String name;
private List<Integer> list;
private Map<String, String> map;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Integer> getList() {
return list;
}
public void setList(List<Integer> list) {
this.list = list;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
@Override
public String toString() {
return "Person [dog=" + dog + ", name=" + name + ", list=" + list + ", map=" +
map + "]";
}
}
public class Dog {
public Dog() {
System.out.println("创建了Dog");
}
}
<bean id="dog" class="com.xiaowww.hello.Dog"></bean>
<bean id="person" class="com.xiaowww.hello.Person">
<!-- 对象类型 -->
<property name="dog" ref="dog"></property>
<!-- 普通类型 -->
<property name="name" value="xiaoming"></property>
<!-- 集合类型 list set array 都通用-->
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
</list>
</property>
<!-- map集合 -->
<property name="map">
<map>
<entry key="k" value="v"></entry>
</map>
</property>
</bean>
public class HelloTest {
@Autowired
Person p;
@Test
public void test(){
System.out.println(p);
}
}
我们先创建一个Person 利用 xml配置为Person注入各种类型属性值 这就是用setter方法的注入
总的来说 就是 普通值用value 对象值用ref 集合值用对应的集合名字
Bean的properties注入方式还有构造器注入 ,这里不多赘述,开发基本不用 有兴趣自己去找资料
Bean元素的继承
Bean元素的继承有点像Java的继承,非常简单,我们直接看例子就好了
public class Person {
private int age;
private String name;
private int weight;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + ", weight=" + weight + "]";
}
}
public class Dog {
private int age;
private String name;
private int height;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public String toString() {
return "Dog [age=" + age + ", name=" + name + ", height=" + height + "]";
}
}
<bean id="base" abstract="true">
<property name="age" value="1"></property>
<property name="name" value="dd"></property>
</bean>
<bean id="person" class="com.xiaowww.hello.Person" parent="base">
<property name="weight" value="105"></property>
</bean>
<bean id="dog" class="com.xiaowww.hello.Dog" parent="base">
<property name="height" value="55"></property>
</bean>
@Autowired
Person p;
@Autowired
Dog d;
@Test
public void test(){
System.out.println(p);
System.out.println(d);
}
注意这里的Person自己的属性是weight 而Dog的属性是height,共同的属性就去使用parent配置属性去继承,要将被继承的类的abstract属性设置为true
使用Bean配置数据库连接池
先来回忆一下jdbc
@Test
public void test() throws Exception{
DruidDataSource da = new DruidDataSource();
da.setDriverClassName("com.mysql.jdbc.Driver");
da.setUrl("jdbc:mysql://localhost:3306/test");
da.setUsername("root");
da.setPassword("123456");
da.setInitialSize(8);
Connection conn = da.getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("select * from
student");
ResultSet rs = prepareStatement.executeQuery();
while(rs.next()){
System.out.println(rs.getInt(1));
System.out.println(rs.getString(2));
System.out.println(rs.getInt(3));
}
}
然后用我们的IOC和DI来创建一个数据库连接池
<bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="initialSize" value="1"></property>
</bean>
由于DruidDataSource也有初始化和销毁(关闭资源方法)init-method="init" destroy-method="close"
@Autowired
DruidDataSource da ;
@Test
public void test() throws Exception{
Connection conn = da.getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("select * from
student");
ResultSet rs = prepareStatement.executeQuery();
while(rs.next()){
System.out.println(rs.getInt(1));
System.out.println(rs.getString(2));
System.out.println(rs.getInt(3));
}
}
在开发中我们常使用properties文件来让代码更好的维护
下面来用Spring来配置读取properties文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
initialSize=1
这句话用来加载properties文件 接下来把刚才在Spring中注入的dataSourse对象改为
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSourse" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="initialSize" value="${initialSize}"></property>
</bean>
完成之后再去启动发现报错
这是因为我们的系统环境变量也有username,而默认使用的也是这个username 我们在加载properties文件这一行加上一个属性,值设置为never,就是不读取系统环境变量的意思 。再次运行成功执行
而在开发中我们经常使用这样的格式来写,也就不存在上面的问题了
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=1
注解方式的依赖注入
注解注入是我们常用的简化代码方式,Spring的注解也非常的简单方便
<context:annotation-config/> 在applicationContext.xml中加上这句代码,这个就是注解解析器
其实在前面已经提过在这段代码中我们用 @Autowired 注入了DataSource
@Autowired
DruidDataSource da ;
@Test
public void test() throws Exception{
Connection conn = da.getConnection();
PreparedStatement prepareStatement = conn.prepareStatement("select * from
student");
ResultSet rs = prepareStatement.executeQuery();
while(rs.next()){
System.out.println(rs.getInt(1));
System.out.println(rs.getString(2));
System.out.println(rs.getInt(3));
}
}
这个注解会根据类型去自动匹配 然后注入对象,但是同一个类可能会有多个对象
这时候就需要配合 @Qualifier("dataSourse") 这个注解 ,也就是 类型+id (必须唯一否则报错)
@Autowired
@Qualifier("dataSourse")
DruidDataSource da ;
在javaEE规范中 @resource 跟 @Autowired的作用是一样的 也就是说上面代码可以写成
@Resource(name="dataSourse") name不是必须的 没有名字就按类型来找
DruidDataSource da ;
对象的注入方式就是上面这样,那么用注解注入简单类型呢
@Value就是专门用来注入简单类型的值
@Value("123")
private int age; 等于 private int age=123;
@Value注解可以注入properties中的值 前提是applicationContext.xml中加载了properties文件
@Value("${password}")
private int age; 就可以注入properties中配置的password
加载多个配置文件
<context:property-placeholder location="classpath:db.properties,classpath:app.properties" system-properties- mode="NEVER"/>
也可以分开写
<context:property-placeholder location="classpath:db.properties" system-properties-mode="NEVER"/>
<context:property-placeholder location="classpath:app.properties" system-properties-mode="NEVER"/>
但是分开的时候Spring只会默认去解析第一个properties文件(如果@Value的值在app.properties就会报错)
此时需要设置一个属性 ignore-unresolvable="true"
<context:property-placeholder location="classpath:app.properties" ignore-unresolvable="true"/>
注解方式的控制反转
上面我们已经讲过如何通过Spring来创建Bean也就是所谓的IOC控制反转
如果我要注入一个Dog类 通过配置Bean <bean id="dog" class="com.xiaowww.hello.Dog"></bean>然后在测试类中使用
@Autowired
Dog d;
这样就可以成功创建出来一个Dog的实例
注解方式的实现 : 将配置文件中的<bean id="dog" class="com.xiaowww.hello.Dog"></bean> 删除
在配置文件中加入<context:component-scan base-package="com.xiaowww.hello" /> (扫描IOC注解 base-package配置需要扫描的包)
然后在我的Dog类上面贴上@commpent注解 再次测试,发现创建成功
@Component
public class Dog {
}
base-package 写你自己的类所在包
<context:component-scan base-package="com.xiaowww.hello" />
@Autowired
Dog d;
@Test
public void test() throws Exception{
System.out.println(d);
}
实际开发中为了代码的可读性 我们常使用@controller、@Service、@respository来替代@commpent
他们实际功能是一样的,但是你会发现你打开这个类就会知道他是 MVC 层中的哪一层
之前我们说xml中可以配置 对象的单例 、多例 以及初始化和销毁调用方法 下面用注解来配置一下
修改作用域 @scope 可以设置属性为单例 sigleton、prototype
指定初始化方法 @PostConstruct 指定销毁方法
静态代理模式
何为代理模式: 客户端使用的都是代理对象,不知道真实的对象是谁,在客户端和真实对象中间起到中介的作用
1、代理对象完全包含真实对象,客户端使用的都是代理对象的方法,和真实对象之间是没有直接关系的
2、代理对象可以让真实对象更关注本身的业务逻辑 消除大量重复代码
举个例子: 租房的时候,如果每个人去看房 房东都要来陪你看 房东一定觉得很麻烦 ,他把看房子这件事情交给了中介 ,每次看房是中介带你去看房,而当需要签合同的时候,房东就会过来跟你签,之后你的房租也交给房东
租房的人: 大量的客户端对象
房东: 需要处理业务逻辑的真实对象
中介: 代理房东处理他很繁琐的代理对象
我们都知道事务是一个需要些try{ }catch(){ } 并且需要在每个事务块中都要写的代码
那么如果使用静态代理 来处理这个事务会是什么样子呢
public interface DoWorkService {
public void doWork();
}
@Service
public class DoWorkServiceImpl implements DoWorkService{
@Override
public void doWork() {
System.out.println("doWork.....");
}
}
@Service("doWorkProxy")
public class DoWorkProxy implements DoWorkService{
@Autowired
@Qualifier("doWorkServiceImpl")
DoWorkService doWorkService;
@Autowired
TransactionManager transactionManager;
@Override
public void doWork() {
try {
transactionManager.begin();
doWorkService.doWork();
System.out.println("成功");
transactionManager.commit();
} catch (Exception e) {
System.out.println("报错");
transactionManager.rollback();
// TODO: handle exception
}
}
}
@Component
public class TransactionManager {
public void begin(){
System.out.println("事务开启");
}
public void commit(){
System.out.println("事务提交");
}
public void rollback(){
System.out.println("事务回滚");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DoWorkTest {
@Autowired
@Qualifier("doWorkProxy")
DoWorkProxy doWorkProxy;
@Test
public void test(){
doWorkProxy.doWork();
}
}
我们来分析一下上面的代码:
1、首先我定义了一个DoWorkService接口 也就是我们的业务逻辑 (面向接口编程)
2、用DoWorkServiceImpl 去实现这个类,写我们自己的业务逻辑
3、用DoWorkProxy同样实现DoWorkService 在doWork方法中调用DoWorkServiceImpl的doWork方法(这里代理对象已经实现了被代理对象的方法)
4、在doWorkServiceImpl.doWork() 的方法前后 写我们的增强代码,也就是事务
这样做就可以把事务交给 DoWorkProxy 这个代理对象 (中介) 来做
而我们的DoWorkServiceImpl 就可以只写我们的逻辑代码 (房东签合同、收房租)
动态代理
字节码的动态加载 : 我们写了一个helloWorld之后,为什么就能输出一个HelloWorld
helloWorld.java --> helloWorld.class --> java虚拟机 --> 操作系统 --> 系统硬件
其实看这个流程的话 如果你足够牛逼,完全可以不用写 helloWorld.java 而是直接用 helloWorld.class
那么运行过程就是 helloWorld.class --> java虚拟机 --> 操作系统 --> 系统硬件
动态代理看起来很繁琐,其实就一个类Proxy 就一个方法 newProxyInstance 而且是静态方法
直接查看 jdk 1.x 的 api 中 也可以看到 Proxy中只有一个方法可以创建 而该方法需要三个参数
1)classLoader类加载器 2)需要被增强的类所实现的接口 3)InvocationHandler接口,也就是让你告诉他应该怎么增强
classLoader类加载器 就是加载类所需的加载器,我们一般用真实对象的类加载器 obj.getClass().getClassLoader()
所实现的接口 静态代理中已经提到代理对象必须包含被代理的真实对象 而真实对象实现了哪些接口 代理对象也必须实现这些接口 ,所以我们需要告诉Proxy 帮我们去实现 obj.getClass().getInterfaces() (反射获取所有接口)
InvocationHandler接口 该类中有 invoke(Object proxy, Method method, Object[] args) 方法 包含三个参数
proxy:代理对象 method :真实对象中需要被增强的方法 args:被增强的方法中的所有参数
public interface DoWorkService {
public void doWork();
}
@Service
public class DoWorkServiceImpl implements DoWorkService{
public void doWork() {
System.out.println("doWork.....");
}
}
@Component
public class TransactionAdvice {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
public <T> T getProxy(){
return (T)Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new invocation());
}
class invocation implements InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object invoke = null;
try {
System.out.println("事务开启");
invoke = method.invoke(obj, args);
System.out.println("doWork完毕");
System.out.println("事务提交");
} catch (Exception e) {
e.printStackTrace();
System.out.println("doWork出错");
System.out.println("事务回滚");
}
return invoke;
}
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DoWorkTest {
@Autowired
TransactionAdvice t;
@Autowired
DoWorkService doWorkService;
@Test
public void test(){
t.setObj(doWorkService);
DoWorkService proxy = t.getProxy();
proxy.doWork();
}
}
注意:使用Proxy动态代理,必须要有实现接口
代码从上往下看 首先创建必须的接口 --->然后写我们的类实现方法(真实对象的类)--->创建一个动态代理类(必须传入真实类对象,且执行invoke方法时一定要注意使用真实对象 否则栈溢出 ) ---> 测试(获取动态代理对象-->执行动态代理中的方法)
如果你不小心把obj写成了动态代理的对象,就会出现栈溢出(方法执行要创建动态代理对象,而动态代理对象要执行方法死循环)
CGLIB动态代理
上面讲了JDK的动态代理,但是jdk的动态代理必须要有接口才可以,开发中有些时候我们不需要接口,只需要增强实现类的时候就需要用到CGLIB动态代理,那么为什么CGLIB也可以实现动态代理呢?
JDK动态代理中我们说到,需要告诉JDK我们的实现类
因为JDK需要也同样实现该接口 覆盖该方法,然后用该方法内部去调用传入对象的真实方法
而CGLIB之所以可以不用接口,是因为他用继承的方式 覆盖 父类的方法,然后再覆盖的方法里调用父类方法
简单来说 JDK的原理就是
//接口
public Interface Service(){
public void sayHello();
}
//真实对象 的类
public void myService implments Service{
public void sayHello(){
System.out.println("Hello");
}
}
//代理对象 的类
public void myServiceProxy implments Service{
@Autowired
Service service;
public void sayHello(){
System.out.println("增强上。。。。");
service.sayHello();
System.out.println("增强下。。。。");
}
}
而CGLIB的原理就是
//真实对象 的类
public void myService{
public void sayHello(){
System.out.println("Hello");
}
}
//代理对象 的类
public void myServiceProxy extends myService{
@Override
public void sayHello(){
System.out.println("增强上。。。。");
super.sayHello();
System.out.println("增强下。。。。");
}
}
下面我们来写一下CGLIB的代码
@Service
public class DoWorkServiceImpl{
public void doWork() {
System.out.println("doWork.....");
}
}
@Component
public class TransactionAdvice {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
public <T> T getProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass());
enhancer.setCallback(new invocation());
return (T)enhancer.create();
}
class invocation implements org.springframework.cglib.proxy.InvocationHandler{
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object invoke = null;
try {
System.out.println("事务开启");
invoke = method.invoke(obj, args);
System.out.println("doWork完毕");
System.out.println("事务提交");
} catch (Exception e) {
e.printStackTrace();
System.out.println("doWork出错");
System.out.println("事务回滚");
}
return invoke;
}
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DoWorkTest {
@Autowired
TransactionAdvice t;
@Autowired
DoWorkServiceImpl doWorkService;
@Test
public void test(){
t.setObj(doWorkService);
DoWorkServiceImpl proxy = t.getProxy();
proxy.doWork();
}
}
需要注意的两点是 CGLIB也是实现 InvocationHandler 覆盖 invoke 方法
但是 InvocationHandler 是org.springframework.cglib.proxy.InvocationHandler包里面的
而JDK动态代理实现的是 java.lang.reflect.InvocationHandler
再有不同的一点就是获取对象的方式变了,用Enhancer来获取 且需要设置 setSuperclass 其他基本不变
CGLIB中还有一个接口叫 org.springframework.cglib.proxy.MethodInterceptor 从类名上就可以看得出来是做方法切入的
这个跟上面的 org.springframework.cglib.proxy.InvocationHandler 是一个道理的,都是使用继承方式来实现
@Component
public class TransactionAdvice {
private Object obj;
public void setObj(Object obj) {
this.obj = obj;
}
public <T> T getProxy(){
return (T)Enhancer.create(obj.getClass(),new invocation());
}
class invocation implements org.springframework.cglib.proxy.MethodInterceptor{
@Override
public Object intercept(Object arg0, Method method, Object[] args, MethodProxy
arg3) throws Throwable {
System.out.println(System.currentTimeMillis());
Object invoke = method.invoke(obj, args);
System.out.println(System.currentTimeMillis());
return invoke;
}
}
}
@Service
public class DoWorkServiceImpl{
public void doWork() {
System.out.println("doWork.....");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DoWorkTest {
@Autowired
TransactionAdvice t;
@Autowired
DoWorkServiceImpl doWorkService;
@Test
public void test(){
t.setObj(doWorkService);
DoWorkServiceImpl proxy = t.getProxy();
proxy.doWork();
}
}
除了接口不一样以外其他的基本一致 Enhancer.create ()方法 需要两个参数 ,一个是class对象,也就是被代理真实对象的字节码 、另外一个是CallBack接口 从字面上看就是一个回调函数 , 其实 就是我们代理类所执行的所有代码 也就是
注意 : 我们使用的 MethodInterceptor 就是 CallBack 的子接口
AOP思想
前面讲完了静态代理、动态代理,都是为了AOP 什么是AOP
面向对象我们成为OOP 第一个O就是Object 而AOP 的A 则是 aspect 切面的意思
AOP的术语: 中文我就不翻译了,感觉翻译过来反而影响理解
JoinPoint:需要被增强的方法
Pointcut :需要为哪些包中的哪些类做增强 就是JoinPoint 的一个集合
Advice : 当拦截到JoinPoint之后 在这个方法执行的哪个时机做哪些增强
Aspect : 等于 Pointcut+Advice 就是说在哪些包中的哪些类中的哪些方法在执行的什么时机做什么样的增强
这几个术语大多是包含关系,其实你不理解也无所谓,等学完了回头再看
Pointcut 语法 : 要表示哪些包中的哪些类中的哪些方法肯定是要有一个表达式规范的
execution(<修饰符>? <返回类型> <声明类型>? <方法名> (<参数>) <异常>?)
public static Class java.lang.Class.forName(String className) throws ClassNotFoundException;
public static(<修饰符>?) Class(<返回类型>) java.lang.Class(<声明类型>?).forName(<方法名>)(String className)(<参数>) throws ClassNotFoundException(<异常>?);
后面有 ? 的表示可有可无 也就是说如果我懒可以写成execution( <返回类型> <方法名> (<参数>) )
表示com.xyz.service下的任何方法和service子包下的任何方法
通配符: * 表示所有匹配任意字符 ..(两个点)在方法参数中表示任意参数,在包名后面表示包括子包
比如我把上面的换成 execution(* com..service..*.*(..)) 那就变成了 com.abc.service包中的方法也会被切
com.def.service包中的方法也会被切 所有在com.任意包.service中的都会切
SpringAOP
既然有了动态代理而且AOP底层也是动态代理为什么还要学习AOP(AOP可以让你不用每个代理都手动创建对象)
jar包 spring-aop-5.0.4.RELEASE.jar aopalliance-1.0.jar(Spring5以上版本不需要,已经集成在spring-aop-5.0.4.RELEASE.jar)
spring-aspects-5.0.4.RELEASE.jar 3个jar包
<!-- 扫描注解包-->
<context:component-scan base-package="com.xiaowww.aop" />
<!-- 注解解析器 -->
<context:annotation-config />
<bean id="txManager" class="com.xiaowww.aop.TransactionManager"></bean>
<!-- AOP配置 -->
<aop:config>
<aop:aspect ref="txManager">
<aop:pointcut id="servicePoint" expression="execution(*
com.xiaowww.aop.*Service.*(..))"/>
<aop:before method="begin" pointcut-ref="servicePoint"/>
<aop:after-returning method="commit" pointcut-ref="servicePoint"/>
<aop:after-throwing method="rollback" pointcut-ref="servicePoint"/>
</aop:aspect>
</aop:config>
public class TransactionManager {
public void begin(){
System.out.println("事务开启");
}
public void commit(){
System.out.println("事务提交");
}
public void rollback(){
System.out.println("事务回滚");
}
}
public interface DoWorkService {
public void doWork();
public void doWorkExcption();
}
@Service
public class DoWorkServiceImpl implements DoWorkService{
public void doWork() {
System.out.println("doWork.....");
}
public void doWorkExcption() {
System.out.println("doWorkExcption.....");
int i=5/0;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class DoWorkTest {
@Autowired
DoWorkService doWorkService;
@Test
public void test(){
doWorkService.doWorkExcption();
}
}
这样你就会发现跟我们解释的一样,在某些方法上面加上某些增强,不过这些增强指向的是另外一个类的方法
before:前置增强 在被执行方法之前增强 权限控制/日志记录
after-returning:后置增强 在被执行方法之后增强(无异常) 提交事务/统计分析数据
after-throwing:异常增强 在被执行方法抛出异常之后增强 回滚事务/记录异常信息
after:最终增强 在return之前执行无论是否异常(finally块) 资源关闭
around:环绕增强 上面四种都包含在内,任意时机增强 ...都可以
AOP中的一些细节
1)、在异常增强中怎么来获取异常信息:
一般在出现异常时我们都会打印异常对象的异常信息 只需要在
<aop:after-throwing method="rollback" pointcut-ref="servicePoint"/> 上加一个属性
<aop:after-throwing method="rollback" pointcut-ref="servicePoint" throwing="ex"/> 这个ex就是参数名 必须一致
看到这里也差不多明白了,就是在增强的方法里面添加一个参数 而参数类型必须为Throwable (因为你抛出的异常一定是实现这个接口的)
2)、如何获取被增强方法的一些属性
其实在切面类的方法中可以传入一个参数 JoinPoint 也就是上面我们所说的JoinPoint (需要被增强的方法信息)
这里面涵盖的一些方法在记录日志( jp.getSignature() 、Arrays.toString(jp.getArgs()) )有很好的作用
该参数不需要配置
3)、如何使用aop:around
<aop:around method="around" pointcut-ref="servicePoint"/>
public Object around(ProceedingJoinPoint pj){
Object obj = null;
try {
System.out.println("事务开启");
obj = pj.proceed();
System.out.println("事务提交");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("事务回滚");
}finally{
System.out.println("关闭资源");
}
return obj;
}
ProceedingJoinPoint 是 JoinPoint的一个子接口,直接传入就可以使用,ProceedingJoinPoint 有个方法 proceed() 可以执行真实对象的方法 这里像极了 继承父类 重写父类方法 然后调用super.around(); 当然有返回值可以自行return;
使用注解来配置AOP 注解配置AOP是真的简单
XML中先配置一个AOP注解解析器
首先找到切面类 , 在类上面 贴上注解 @Component @Aspect 然后
@Component
@Aspect
public class TransactionManager {
@Before(value="execution(* com.xiaowww.aop.*Service.*(..))")
public void begin(JoinPoint jp){
System.out.println("代理对象类型:"+jp.getThis().getClass());
System.out.println("真实对象类型:"+jp.getTarget().getClass());
System.out.println("方法参数:"+Arrays.toString(jp.getArgs()));
System.out.println("签名:"+jp.getSignature());
System.out.println("事务开启");
}
@AfterReturning(value="execution(* com.xiaowww.aop.*Service.*(..))")
public void commit(){
System.out.println("事务提交");
}
@AfterThrowing(value="execution(* com.xiaowww.aop.*Service.*(..))",throwing="ex")
public void rollback(Throwable ex){
ex.printStackTrace();
System.out.println("事务回滚");
}
@Around(value="execution(* com.xiaowww.aop.*Service.*(..))")
public Object around(ProceedingJoinPoint pj){
Object obj = null;
try {
System.out.println("事务开启");
obj = pj.proceed();
System.out.println("事务提交");
} catch (Throwable e) {
e.printStackTrace();
System.out.println("事务回滚");
}finally{
System.out.println("关闭资源");
}
return obj;
}
}
JdbcTemplete
jdbc的操作流程 加载驱动 ---> 获取连接 ---> 创建预编译语句 --->执行sql --->提交事务
冷静想一下 哪些过程是不变的 ?
Spring JDBC的操作流程 创建预编译语句 ---> 执行sql
那么中间省略掉的东西肯定要我们配置了
JdbcTemplete 需要一个连接池对象(获取连接) 连接池会指定DriverClass(加载驱动) 事务需要单独配置
这样也就完成了之前的一个jdbc过程 (所以说基础还是最重要的)
导包 spring-jdbc-5.0.4.RELEASE.jar spring-tx-5.0.4.RELEASE.jar 然后导入驱动包 和 连接池包
<!-- 扫描注解包-->
<context:component-scan base-package="com.xiaowww" />
<!-- DI注解解析器 -->
<context:annotation-config />
<!-- AOP注解解析器 -->
<aop:aspectj-autoproxy />
<context:property-placeholder location="classpath:app.properties" system-properties-mode="NEVER"/>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="maxActive" value="${maxActive}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
@Repository
public class StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(Student s){
jdbcTemplate.update("insert into student (name,age) values(?,?)",new Object[]{s.getName(),s.getAge()});
}
public void update(Student s){
jdbcTemplate.update("update student set name=?,age=? where id = ?",new Object[]{s.getName(),s.getAge(),s.getId()});
}
public void delete(int id){
jdbcTemplate.update("delete from student where id = ?",new Object[]{id});
}
}
public class Student {
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdbcTest {
@Autowired
StudentDao dao;
@Test
public void te(){
Student s = new Student();
dao.delete(1);
}
}
原理已经讲的很清楚了,上一下增删改的代码 下面是查询 配置文件依然不变
//无参数查询基本类型
public <T> T queryObject(String sql,Class<T> clazz){
T queryForObject = jdbcTemplate.queryForObject(sql, clazz);
return (T) queryForObject;
}
//有参数查询基本类型
public <T> T queryObject(String sql,Class<T> clazz,Object[] args){
T queryForObject = jdbcTemplate.queryForObject(sql, clazz, args);
return (T) queryForObject;
}
//查询封装成我们自己的对象
public <T> T queryForObject(String sql,Class<T> clazz,Object[] args) throws
IntrospectionException{
BeanInfo beanInfo = Introspector.getBeanInfo(clazz,Object.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
T queryForObject = jdbcTemplate.queryForObject(sql, args, new RowMapper<T>(){
@Override
public T mapRow(ResultSet arg0, int arg1) throws SQLException {
Object newInstance = null;
try {
newInstance = clazz.newInstance();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Object object =
arg0.getObject(propertyDescriptor.getName().toLowerCase());
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(newInstance, object);
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return (T) newInstance;
}
});
return (T) queryForObject;
}
//自己的对象
public <T> List<T> queryForList(String sql,Class<T> clazz,Object[] args) throws
IntrospectionException{
BeanInfo beanInfo = Introspector.getBeanInfo(clazz,Object.class);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
List<T> query = jdbcTemplate.query(sql, args, new RowMapper<T>(){
@Override
public T mapRow(ResultSet arg0, int arg1) throws SQLException {
Object newInstance = null;
try {
newInstance = clazz.newInstance();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
Object object =
arg0.getObject(propertyDescriptor.getName().toLowerCase());
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(newInstance, object);
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return (T) newInstance;
}
});
return query;
}
重点说一下我们自己的对象是如何构建的
queryList跟这个一模一样 只不过多了个for循环
Spring事务 TransctionManager
为什么需要事务 : 诸多案例中最经典的银行转账, 小A给小B 转 1000块钱 ,那么在程序中需要两步操作
update MoneyTable set money = money - 1000 where name = '小A';
update MoneyTable set money = money + 1000 where name = '小B';
这样执行之后就可以完成一个转账功能 , 但是如果在 小A 这句转出sql 执行完之后断电了
那么小A少了1000 而小B 则没收到转账 ,毋庸置疑是个很严重的问题
而事务就是用来解决这个问题的 ;
Spring的默认事务是什么样的 : 我们使用的JDBCTemplete默认情况下 是自动提交事务的 ,也就是说上面的过程可以理解为:
getConnection(获取连接) --> 开启事务 -->执行update MoneyTable set money = money - 1000 where name = '小A'; -->
提交事务;
再次getConnection(获取连接) --> 开启事务 -->执行update MoneyTable set money = money + 1000 where name = '小B';-->
提交事务;
事务提交之后无法再次回滚;
怎么解决:
getConnection(获取连接) --> 开启事务 -->执行update MoneyTable set money = money - 1000 where name = '小A';
-->执行update MoneyTable set money = money + 1000 where name = '小B'; --> 提交事务;
抑或是
getConnection(获取连接) --> 开启事务 -->执行update MoneyTable set money = money - 1000 where name = '小A';
--> 抛出异常 --> 回滚事务;
让同一个service中使用同一个连接,这样的话就可以控制连接的提交和回滚 ;
在这之前需要设置事务的自动提交为false;
Connection conn = DriverManager.getConnection("");
try {
conn.setAutoCommit(false);
PreparedStatement prepareStatement = conn.prepareStatement("");
prepareStatement.executeUpdate();
prepareStatement.executeUpdate();
conn.commit();
} catch (Exception e) {
// TODO: handle exception
conn.rollback();
}
这样就可以自己控制事务的提交和回滚,而不至于损坏数据
Spring事务包含三个接口
PlatformTransactionManage : 根据TransactionDefinition提供的事务配置属性来创建事务
getTransaction():根据事务定义的环境返回一个已经存在的事务,如果不存在则新建一个;
commit():提交事务;
rollback():回滚事务:
TransactionDefinition :封装事务的配置属性如隔离级别、超时时间、传播规则等;
TransactionStatus:当前事务处于什么样的状态
对于jdbc/myBatis使用的是dataSourceTransctionManager 而hibernate使用的是hibernateTransctionManager
事务的传播规则:
在开发中一般情况下每个service类都会配置事务,那么在AaaService中的a方法中调用BbbService中的b方法
由于AaaService中的a方法有事务 BbbService中的b方法也有事务,到底在AaaService中的a方法中使用谁的事务呢?
简单分为三类
第一类:需要遵从当前的事务
REQUIRED:必须存在一个事务如果当前a方法存在事务 则b方法加入该事务 否则a方法则新建一个事务(常用)
SUPPORTS:如果当前存在事务则使用该事务,否则以非事务方式运行
MANDATORY:必须存在一个事务,如果当前存在事务则使用该事务 否则直接抛异常
第二类:不遵从当前的事务
REQUIRES_NEW:不管当前是否存在事务,每次都新开启一个事务
NOT_SUPPORTED:一定以非事务方式进行,如果当前存在事务,则挂起该事务(在a方法中调用b方法,在执行到b方 法的时候就把a的事务挂起,b方法不使用这个事务,等b方法执行完了,再继续用事务执行a剩 下的方法,如果a方法有异常 b方法也无法回滚)
NEVER:不支持事务,如果当前存在事务则抛出异常
第三类:寄生事务
NESTED:如果当前存在事务则在事务内部执行,如果当前不存在事务则新建事务
寄生事务可以通过数据的savePointer(保存点)来实现 寄生事务可以回滚,但是回滚不影响外部事务
外部事务回滚会影响寄生事务一起回滚
不是所有的事务管理器都默认支持寄生事务有些需要手动设置才能支持 如:hibernateTransctionManager
<!-- 扫描注解包-->
<context:component-scan base-package="com.xiaowww.trans" />
<!-- DI注解解析器 -->
<context:annotation-config />
<!-- AOP注解解析器 -->
<aop:aspectj-autoproxy />
<context:property-placeholder location="classpath:app.properties" system-
properties-mode="NEVER"/>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="maxActive" value="${maxActive}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池 -->
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 配置事务管理器增强 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="trans"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务管理切面-->
<aop:config>
<aop:pointcut expression="execution(* com.xiaowww.trans.service.*Service.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountDao accountDao;
@Override
public void trans(int inId, int outId, Long money) {
accountDao.transOut(outId, money);
int i=5/0;
accountDao.transIn(inId, money);
}
}
@Repository
public class AccountDaoImpl implements AccountDao{
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public void transIn(int id, Long money) {
// TODO Auto-generated method stub
jdbcTemplate.update(" update account set money = money + ? where id = ?"
,new Object[]{money,id});
}
@Override
public void transOut(int id, Long money) {
// TODO Auto-generated method stub
jdbcTemplate.update(" update account set money = money - ? where id = ?"
,new Object[]{money,id});
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class transTest {
@Autowired
AccountService accountService;
@Test
public void te() throws SQLException{
accountService.trans(1, 2, 1000L);
}
}
整体就是配置了一个事务切面Aop来管理事务,
为什么需要txAdvice?
上面讲了事务的隔离级别、传播行为等;就是需要配置在txAdivce的属性上面
<tx:method name="trans" read-only="true" isolation="DEFAULT" no-rollback-for="" propagation="REQUIRED" rollback-for="" timeout="-1"/>
这几个属性都是事务的属性配置 意思就是trans方法使用的是(read-only="true")只读事务 (timeout="-1")用数据库的默认超时时间 等 其中除了name属性是必填以外 其他都是有默认值的,一般我们用到最多的就是 (read-only="true")设置为只读事务 用来提高查询效率 注意: name属性可以使用通配符,我如果写<tx:method name="trans*"/>就代表所有以trans开头的方法名 transX 、transY等都会使用这一套属性
在开发中一般会这样<!-- 配置事务管理器增强 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="list*" read-only="true" />
<tx:method name="query*" read-only="true" />
<tx:method name="get*" read-only="true" />
<tx:method name="*" />
</tx:attributes>
</tx:advice>
表示所有查询方法都用只读事务 , 其他就是默认事务
使用注解来配置事务
把刚才配置的<tx:Advice/>和<aop:config/>都删掉 只保留一个 txManager
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入连接池 -->
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<!-- 事务注解解析器 -->
<tx:annotation-driven transaction-manager="txManager"/>
在service类的实现类上面贴上一个注解@Transactional 就可以实现事务管理 当然如果你想使用只读事务
那就是@Transactional(readOnly=true)
@Transactional贴在类上面表示整个类都使用事务,也可以单独贴方法上标明某个方法使用事务