Spring,春天~
开玩笑,怎么可能是春天啊喂,那是弹簧?
别逗了
那是什么?
Spring,是一个企业级框架,它的出现是为了简化操作,使开发更容易,最关键的是,它是开源的。
Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
大家都喜欢使用这张图,那我也摆一下吧
上图是Spring七大模块明确定义,虽然我还没有完全使用过,鉴于作者能力有限,这篇权当做科普文章,简单介绍Spring最重要的两个概念——IOC和AOP。
IOC(控制反转)
使用IOC容器来管理对象的生命周期、依赖关系等,从而使应用程序的配置和依赖性规范与实际的应用代码分开。简单来说,使用IOC可以让我们解耦我们的程序代码。
何为解耦呢?
耦合,是两个事物之间存在一种相互作用和相互影响的关系,解耦就是解除这种关系。
我们举个例子,在java的代码中,当我们使用new新建对象的时候,我们就不知不觉中产生了一种耦合关系,是在当前使用模块和对象所在类的一种耦合。我们消除这个耦合的过程(或者说降低耦合)就是解耦。
IOC的实现是基于java技术的反射机制,通俗的说,反射就是根据给出的类名(字符串)来生成对象。
这里我么不管java的反射机制如何实现,那要另开一篇专题。我们只关心Spring如何实现IOC。
用过Spring的朋友都知道,我们通过配置xml文件中的beans来管理我们的Spring容器中的内容。我们使用Spring实现IOC也是在配置beans中体现的。
啰里啰嗦一大堆,可能会晕晕的,直接实操,配合讲解,我们剥茧抽丝,巴拉巴拉~
先看个例子
限于篇幅,这里只讲解通过beans配置文件来获取对象。
我们先看一下使用Spring时需要使用的jar包,这里我们使用maven依赖,所以直接给出的是配置文件中的依赖关系。
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
</dependencies>
这里共使用了四个Spring依赖包,具体什么作用不再详述,建议百度或者参考官方文档。
然后看一下我的项目结构,以作参考
package entity;
/**
* Created by yubotao on 2017/09/24.
*/
public class User {
private String firstName;
private String lastName;
//getter , setter and toString.
}
package entity;
/**
* Created by yubotao on 2017/09/24.
*/
public class AnotherUser {
private String firstName;
private String lastName;
//getter , setter and toString.
}
package entity;
/**
* Created by yubotao on 2017/09/24.
*/
public class FatherUser {
private BabyUser babyUser;
private String power;
private Integer age;
//getter , setter and toString.
}
package entity;
/**
* Created by yubotao on 2017/09/24.
*/
public class BabyUser {
private String firstName;
private String lastName;
//getter , setter and toString.
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="user" class="entity.User"/>
<bean name="anotherUser" class="entity.AnotherUser"/>
<bean name="baby" class="entity.BabyUser">
<property name="firstName" value="YU"/>
<property name="lastName" value="Botao"/>
</bean>
<bean name="father" class="entity.FatherUser">
<property name="babyUser" ref="baby"/>
</bean>
</beans>
import entity.AnotherUser;
import entity.FatherUser;
import entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Created by yubotao on 2017/09/24.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:beans.xml"})
public class BeansTest {
@Test
public void method1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user1 =(User) applicationContext.getBean("user");
user1.setFirstName("YU");
user1.setLastName("Botao");
System.out.println(user1);
}
@Autowired
AnotherUser anotherUser;
@Test
public void method2(){
anotherUser.setFirstName("YU");
anotherUser.setLastName("Botao");
System.out.println(anotherUser);
}
@Autowired
FatherUser fatherUser;
@Test
public void ref(){
fatherUser.setAge(18);
fatherUser.setPower("*");
System.out.println(fatherUser);
}
}
为什么把代码全贴出来了?
先去跑起来吧,这样你才会有一个直观的感受,这样做是行得通的,如果跑不通,请耐心一点,排除错误,这是必经之路。
代码剖析
接下来我们就聊聊吧,这些代码是什么意思,为什么这么写啊?
我们先看配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
文件头:简单的理解为规定,就像法律一样,你不可以越线。这里没有声明的一些标签是无法开启使用的。
第一种方式
<bean name="user" class="entity.User"/>
Spring的beans管理,这里就是Spring实现IOC的具体体现,我们将这些实体类交由Spring容器统一管理,需要取用对象时,由Spring在相应的工厂类中为我们生成相关对象,我们只需通过使用配置文件的方式,即可达到解耦的效果。
可以看到我们在测试类中的相关代码
@Test
public void method1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user1 =(User) applicationContext.getBean("user");
user1.setFirstName("YU");
user1.setLastName("Botao");
System.out.println(user1);
}
这里我们使用ClassPathXmlApplicationContext("beans.xml")
来读取配置文件,并返回一个ApplicationContext
对象;这个对象继承BeanFactory
工厂类,这也是我们看到的它拥有getBean()
方法。
此时我们成功从工厂中获取了一个User
对象,然后使用它的方法,最后打印,成功实现对象的创建。
这就是我们说的不使用new新建对象,通过SpringIOC的注入方式进行创建,实现解耦。
第二种方式
接下来我们看下一个,另外一种方式来新建对象,通过注解的方式注入,也是我们最常用的方式。
@Autowired
AnotherUser anotherUser;
@Test
public void method2(){
anotherUser.setFirstName("YU");
anotherUser.setLastName("Botao");
System.out.println(anotherUser);
}
这里需要注意的是,在使用这个注解的时候,我们需要对我们的测试类添加一些额外的注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:beans.xml"})
否则会报空指针异常。至于为什么,这里不表,感兴趣的朋友可以自己去查。
这里我们相当于使用
@Autowired
AnotherUser anotherUser;
来代替
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
AnotherUser anotherUser=(AnotherUser) applicationContext.getBean("anotherUser");
当然那些额外的注解也要算上。
通常在使用注解的时候,我们会开启一个自动扫描包注解的标签<context:component-scan base-package="packagePath"/>
,这里不做讲解。可以关注后续文章~
bean的依赖关系ref
我们看一下我们剩下的两个类,FatherUser
和BabyUser
,这个取名我们就能看出端倪。FatherUser
中包含了一个BabyUser
的私有对象变量;这时,我们看一下我们在配置文件中的相关配置:
<bean name="baby" class="entity.BabyUser">
<property name="firstName" value="YU"/>
<property name="lastName" value="Botao"/>
</bean>
<bean name="father" class="entity.FatherUser">
<property name="babyUser" ref="baby"/>
</bean>
我们看到,我们首先注册了一个BabyUser
的bean,并且通过<property>
标签将它的相关变量进行了定义,感兴趣的朋友可以去查一下<property>
标签,还有另外一个标签<constructor-arg>
,也很有趣。
<property>
标签有三个属性,分别为必须的(required)name
,以及对应的value
或者ref
,且二者只能选择其一。value
正如它的字面意义,赋值;ref
是”reference”的简写,“参考”意,即该属性参考其他的bean,正如我们看到的,FatherUser
的babyUser
属性参考了BabyUser
,而我又对BabyUser
进行了赋值,所以,当我们运行相应的测试代码时
@Autowired
FatherUser fatherUser;
@Test
public void ref(){
fatherUser.setAge(18);
fatherUser.setPower("*");
System.out.println(fatherUser);
}
不要忘记之前说的特别注解哦~
可发现结果如下:
我们看到,在配置文件中对于BabyUser
的赋值,同样应用到FatherUser
中,因为我们配置了一个参考属性。这种参考属性最常用在我们使用数据库连接时参考dataSource
。
同时我们看到我们在测试的时候打印出了很多日志信息,这得益于Spring的日志机制,通过这些日志信息,也让我们可以对IOC创建对象的过程有一个大致的了解。
到了这里,我就讲完Spring的IOC内容了,但是Spring的广阔绝不仅仅这些,希望你在“春天”的海洋里遨游的时候可以沐浴春风!
AOP
首先强调一下,如下这篇教程将绝大部分的aop内容讲解的差不多了,为了节省资源,建议首先移步该教程,然后在回来看我狗尾续貂。
就是这,点吧,不是什么奇怪的网站。
好,那我就开始了。
什么是AOP?
面向切面编程(Aspect Oriented Programing,AOP)采用横向抽取机制,是面向对象编程(Object Oriented Programing,OOP)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能、权限管理、异常处理等,该类功能往往横向地散布在核心代码当中,这种散布在各处的无关代码被称为横切。AOP恰是一种横切技术,解剖开封装对象的内部,将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为Aspect(切面),所谓切面,简单的说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP最常用的几种使用便是:声明式事务管理、权限校验和日志记录。本文放弃权限校验,仅介绍事务管理以及日志记录。
声明式事务管理
先看事务是什么?
事务是一系列动作,这一系列动作综合在一起组成一个完整的工作单元,如果有任何一个动作执行失败,那么事务就将回到最开始的状态,仿佛一切都没发生过。
接下来的介绍我就不赘述了,之前提到的教程中有详细讲解,其他想详细了解的地方,请移步:点吧,不会怀孕。
接下来就介绍如何使用SpringAOP来管理事务,当然,只有AOP是不够的,还需要其它的东东~
那我就先把需要的jar包提供一下吧。
<dependencies>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-pool/commons-pool -->
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.9.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
然后看一下我的项目结构图
那个羞羞的马赛克是一会要讲的东西,我们先放一下。
还是按惯例,先贴代码跑起来,然后在讲解。
package POJO;
/**
* Created by yubotao on 2017/09/25.
*/
public class User {
private int userID; //用户ID
private String userName; //用户名
private String password; //用户密码
//getter,setter and toString.
}
package POJO;
/**
* Created by yubotao on 2017/09/25.
*/
public class UserFactory {
public User createUser(String name, int id, String password){
User user = new User();
user.setUserName(name);
user.setUserID(id);
user.setPassword(password);
return user;
}
}
package Dao;
import POJO.User;
import java.util.List;
/**
* Created by yubotao on 2017/09/25.
*/
public interface UserDao {
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(User user);
public User findUserByID(int id);
public List<User> findAllUser();
}
package Daoimpl;
import Dao.UserDao;
import POJO.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.util.List;
/**
* Created by yubotao on 2017/09/25.
*/
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbc){
this.jdbcTemplate = jdbc;
}
public int addUser(User user) {
// TODO Auto-generated method stub
String sql = "insert into t_user(userid,username,password)values(?,?,?)";
Object[] obj = new Object[]{
user.getUserID(),
user.getUserName(),
user.getPassword()
};
return this.execute(sql, obj);
}
public int updateUser(User user) {
// TODO Auto-generated method stub
String sql = "update t_user set username=?,password=? where userid=?";
Object[] obj = new Object[]{
user.getUserName(),
user.getPassword(),
user.getUserID()
};
return this.execute(sql, obj);
}
public int deleteUser(User user) {
// TODO Auto-generated method stub
String sql = "delete from t_user where userid=?";
Object[] obj = new Object[]{
user.getUserID()
};
return this.execute(sql, obj);
}
private int execute(String sql, Object[] obj){
return this.jdbcTemplate.update(sql, obj);
}
public User findUserByID(int id) {
// TODO Auto-generated method stub
String sql = "select * from t_user where userid=?";
RowMapper<User> rowMapper = new BeanPropertyRowMapper(User.class);
return this.jdbcTemplate.queryForObject(sql, rowMapper, id);
}
public List<User> findAllUser() {
// TODO Auto-generated method stub
String sql = "select * from t_user";
RowMapper<User> rowMapper = new BeanPropertyRowMapper(User.class);
return this.jdbcTemplate.query(sql, rowMapper);
}
}
这里沿用了原教程里的JDBCTemplate,使用Mybatis也是可以的,这些在我之前的blog里都有相关讲解,这里不赘述。
package Dao;
/**
* Created by yubotao on 2017/09/25.
*/
public interface AccountDao {
public void addAccount(int id, double account);
public void inAccount(int id, double account);
public void outAccount(int id, double account);
}
package Daoimpl;
import Dao.AccountDao;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Created by yubotao on 2017/09/25.
*/
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbc){
this.jdbcTemplate = jdbc;
}
public void addAccount(int id, double account) {
// TODO Auto-generated method stub
String sql = "insert into account values(" + id + "," + account + ")";
this.jdbcTemplate.execute(sql);
}
public void inAccount(int id, double account) {
// TODO Auto-generated method stub
String sql = "update account set account=account+? where userid=?";
this.jdbcTemplate.update(sql, account,id);
}
public void outAccount(int id, double account) {
// TODO Auto-generated method stub
String sql = "update account set account=account-? where userid=?";
this.jdbcTemplate.update(sql, account,id);
}
}
package Service;
import POJO.User;
/**
* Created by yubotao on 2017/09/25.
*/
public interface AccountService {
/*
* 转账,实现从outUser转出account金额的钱到inUser
*/
public void transfer(User outUser, User inUser, double account);
}
package ServiceImpl;
import Dao.AccountDao;
import POJO.User;
import Service.AccountService;
/**
* Created by yubotao on 2017/09/25.
*/
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void transfer(User outUser, User inUser, double account){
// TODO Auto-generated method stub
this.accountDao.outAccount(outUser.getUserID(), account);
//模拟程序异常,无法执行inAccount方法
int i = 1 / 0;
this.accountDao.inAccount(inUser.getUserID(), account);
}
}
package util;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* Created by yubotao on 2017/09/25.
*/
public class CreateTables {
//通过JdbcTemplate对象创建表
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbc){
jdbcTemplate = jdbc;
}
public void createTable(String sql){
jdbcTemplate.execute(sql);
}
}
package Client;
import Dao.AccountDao;
import Dao.UserDao;
import POJO.User;
import POJO.UserFactory;
import Service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import util.CreateTables;
/**
* Created by yubotao on 2017/09/25.
*/
public class Client {
public static void main(String[] args) {
//定义配置文件路径
String path = "classpath:Transaction.xml";
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(path);
//获取CreateTables实例
CreateTables tables = (CreateTables) applicationContext.getBean("createTables");
//创建t_user表
String create_user = "create table t_user(userid int primary key auto_increment, username varchar(20), password varchar(32))";
tables.createTable(create_user);
//创建工资表,工资表的userid关联t_user表的userid
String create_account = "create table account(userid int primary key auto_increment, account double, foreign key(userid) references t_user(userid) on delete cascade on update cascade)";
tables.createTable(create_account);
//创建用户
User user1 = new UserFactory().createUser("张三", 1, "zhangsan");
User user2 = new UserFactory().createUser("李四", 2, "lisi");
User user3 = new UserFactory().createUser("王五", 3, "wangwu");
User user4 = new UserFactory().createUser("赵六", 4, "zhaoliu");
//获取用户数据访问对象
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
System.out.println(userDao.addUser(user1));
System.out.println(userDao.addUser(user2));
System.out.println(userDao.addUser(user3));
System.out.println(userDao.addUser(user4));
//获取存款数据访问对象
AccountDao account = (AccountDao) applicationContext.getBean("accountDao");
account.addAccount(1, 100);
account.addAccount(2, 290.5);
account.addAccount(3, 30.5);
account.addAccount(4, 50);
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.transfer(user1, user3, 10);
}
}
上面的所有代码都是没有难度的,所以就不浪费时间讲解了,如果有某些地方看不懂,就请花点时间找找资料或者再认真看看。
我们将重点放在最关键的配置文件的讲解上
不过我先把日志的配置文件贴出来,再讲重要的,hhhhhhh~
#Loggers(记录器)
#log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
#DEBUG < INFO < WARN < ERROR < FATAL
log4j.rootLogger = info ,stdout,FILE
#Appenders (输出源)
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
#Layouts(布局)
#org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出到日志文件 ###
#org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
log4j.appender.FILE = org.apache.log4j.DailyRollingFileAppender
#直接在项目路径下生成,也可写绝对路径,但是有中文乱码问题,设置utf-8并没有卵用
log4j.appender.FILE.File =logs/log.log
#Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
log4j.appender.FILE.Append = true
## 输出WARN级别以上的日志
log4j.appender.FILE.Threshold = WARN
log4j.appender.FILE.layout = org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
重要配置文件Transaction.xml
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 数据库驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
<!-- 连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 连接数据的密码 -->
<property name="password" value="yubotao9527"/>
</bean>
<!-- 配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 默认必须使用数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="createTables" class="util.CreateTables">
<!-- 通过setter方法实现JdbcTemplate对象的注入 -->
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="userDao" class="Daoimpl.UserDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="accountDao" class="Daoimpl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<bean id="accountService" class="ServiceImpl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!-- 事务管理器,依赖于数据源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 编写通知:对事务进行增强,需要对切入点和具体执行事务细节 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- <tx:method> 给切入点添加事务详情
name:方法名称, *表示任意方法, do* 表示以do开头的方法
propagation:设置传播行为
isolation:隔离级别
read-only:是否只读 -->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<!--ISOLATION_DEFAULT 默认级别(对大多数数据库来说就是ISOLATION_READ_COMMITTED)-->
<!--ISOLATION_READ_UNCOMMITTED 最低的隔离级别。事实上我们不应该隔离级别,因为在事务完成前,其他事务可以看到该事务所修改的数据。
而在其他事务提交前,该事务也可以看到其他事务所做的修改。-->
<!--ISOLATION_READ_COMMITTED 大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。
遗憾的是,在该事务提交后,你就可以查看其他事务插入活更新的数据。这意味着在事务的不同点上,如果其他事务修改数据,你会看到不同的数据。-->
<!--ISOLATION_REPEATABLE_READ 该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的数据集,即使其他事务修改了所查询的数据。
然而如果其他事务插入了新数据,你就可以查询到该新插入的数据。-->
<!--ISOLATION_SERIALIZABLE 代价最大、可靠性最高的隔离级别,所有的事务都是俺顺序一个接一个的执行。-->
</tx:attributes>
</tx:advice>
<!-- aop编写,让Spring自动对目标进行代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* ServiceImpl.AccountServiceImpl.*(..))" id="txPointCut"/>
<!-- 切面:将切入点和通知整合 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
前面的bean配置我们已经讲过了,这里我们只需看两个新的东西,这两个标签<tx:advice>
和<aop:config>
。
不过我感觉注解都写的这么详细了,还让我讲就有点过分了。不过我还是勉为其难的贴点东西,应付一下吧~
你需要在
<tx:advice>
标签内设置id和transaction-manager属性。 id是该advice bean的标识,而transaction-manager则必须引用一个PlatformTransactionManager bean。
除了这两个属性以外,你还可以通过<tx:attributes>
标签定制<tx:advice>
标签所创建的通知的行为。这可以让你对transactionAttributes属性表达式所支持的属性以更加结构化的方式进行配置。
详见这篇blog:我不想写了,点吧
而<aop:pointcut>
配置切点,其中expression定义了我们要将这个通知方法放到哪个类的哪个方法,有几种写法,这里支持正则表达式,可以看到我们就使用了正则表达式,如果你不知道正则表达式是什么,请移步:点吧,不会怀孕。
看下回滚效果
报错后,这个Service的方法被回滚了,数据库中的数据没有发生任何改变。
最后,我们捋一下逻辑,我们使用声明式事务管理的过程是怎样的?
首先,我们将我们的Service定义为一种事务过程,并且使用<tx:advice>
对事务进行装配,将这个事务通知通过切面的形式(<aop:config>
进行装配)连接到我们定义的Service上,这样就将我们的Service进行了事务管理。
日志记录
我们之前说到,日志管理有时候也是一种横向的切面流程,所以同样可以使用AOP进行日志记录;但是,这个记录的颗粒度就比较粗化了,所以,细颗粒度的日志记录还是要放到代码块中;而我们使用AOP管理的范围最小的颗粒度也是方法。
接下来我们就看一下日志记录。
揭开刚刚羞羞的马赛克面纱
package util;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by yubotao on 2017/09/25.
*/
public class LogHandler {
private static Logger loggerAdvice = Logger.getLogger(LogHandler.class);
//切入点执行之前需要执行的方法
public void LogBefore(){
System.out.println("转账开始时间:" + System.currentTimeMillis());
}
//切入点执行结束执行该方法
public void LogAfter(){
System.out.println("转账结束时间:" + System.currentTimeMillis());
}
//环绕方法使用
public Object around(ProceedingJoinPoint pip)throws Throwable{
//获取组件类名
String className = pip.getTarget().getClass().getName();
//获取调用方法名
String method = pip.getSignature().getName();
//取得数据库连接前时间
long begin = System.currentTimeMillis();
//当前系统时间
String date = new SimpleDateFormat("yyyy-MM-dd:mm:ss").format(new Date());
Object obj = pip.proceed();
//取得数据库连接后时间
long end = System.currentTimeMillis();
//数据库响应时间
int sqlTime = (int) (end-begin);
String msg = date + ",执行了" + className + "." + method + "()";
loggerAdvice.warn(msg + "\t数据库响应时间: " + sqlTime);
return obj;
}
}
还是在刚刚的Transaction.xml
配置文件中添加相关配置
<!-- 配置日志打印类 -->
<bean id="logHandler" class="util.LogHandler"/>
<aop:config>
<!-- order属性表示横切关注点的顺序,当有多个时,序号依次增加 -->
<aop:aspect id="log" ref="logHandler" order="1">
<!-- 切入点为AccountServiceImpl类下的transfer方法 -->
<aop:pointcut id="logTime" expression="execution(* ServiceImpl.AccountServiceImpl.transfer(..))"/>
<aop:before method="LogBefore" pointcut-ref="logTime"/>
<aop:after method="LogAfter" pointcut-ref="logTime"/>
<aop:around method="around" pointcut-ref="logTime"/>
</aop:aspect>
</aop:config>
<aop:before>
,<aop:after>
和<aop:around>
这几个标签就是为了日志管理而生的,也正如它们的字面意思,分别是方法执行前,执行后和环绕方法运行的,注解也比较详细了,这里就不详细解释了。
看下运行成果吧
当然,你可能觉得我讲的东西太少了。
“不过瘾啊,老师可不可再给点力啊“”。
那就看看这个吧:真的都是干货哦~
还有这个:两者可能会有重复,不过风格不同。
呼~终于讲完了,花了我这么多的时间和精力,希望这篇blog可以为你带来帮助,也可以关注一下我的其他blog哦~都超级用心的!