文章目录
1 耦合
耦合,程序间的依赖关系,包括类之间的依赖和方法间的依赖
解耦,降低程序间的依赖关系
实际开发中,应该做到。编译期不依赖,运行时才依赖。
解耦的方法:
第一步,使用反射来创建对象,而避免使用new关键字
第二步,通过读取配置文件来获取要创建的对象的全限定类名
比如:在使用jdbc时,注册驱动不使用new来创建对象:
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
而是使用反射来创建对象:
Class.forName("com.mysql.jdbc.Driver");
2 BeanFactory工厂
BeanFactory是一个创建Bean对象的工厂,用于创建service和dao对象
Bean在计算机英语中,有可重用组件的含义
JavaBean就是用java语言编写的可重用组件,JavaBean > 实体类(实体类就是一种可重用组件)
方法:
-
步骤一:需要一个配置文件来配置我们的service和dao,配置文件可以是xml也可以是properties
配置的内容:唯一标识=全限定类名(key=value)
这里使用bean.properties作为配置文件
accountService = com.xiaoxi.service.impl.AccountServiceImpl accountDao = com.xiaoxi.dao.impl.AccountDaoImpl
-
步骤二:通过读取配置文件中配置的内容,反射创建对象
public class BeanFactory {
private static Properties properties;
static {
try {
properties = new Properties();
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
}catch (Exception e) {
throw new ExceptionInInitializerError("初始化错误");
}
}
public static Object getBean(String beanName) {
Object bean = null;
try {
String beanPath = properties.getProperty(beanName);
// System.out.println(beanPath);
bean = Class.forName(beanPath).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
2.1 存在的问题及改进
上面这种创建beanFactory工厂的方式,每次调用时都会创建一个newInstance()实例,这种使用的是多例模式,为了简化执行效率,采用单例模式
将上述代码放在静态代码块中,只创建一次newInstance()对象,每次使用时直接调用即可。
public class BeanFactory {
private static Properties properties;
private static Map<String, Object> beans;
static {
try {
//实例化对象
properties = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
properties.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//获取配置文件中所有的keys
Enumeration keys = properties.keys();
//遍历枚举
while(keys.hasMoreElements()) {
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = properties.getProperty(key);
//反射创建对象
Object bean = Class.forName(beanPath).newInstance();
//把key和value放入容器
beans.put(key, bean);
}
}catch (Exception e) {
throw new ExceptionInInitializerError("初始化错误");
}
}
public static Object getBean(String beanName) {
return beans.get(beanName);
}
}
3 IOC
3.1 IOC简介及作用
IOC(Inversion of control)控制反转,将创建对象的权力交给框架,包括依赖注入(Dependency Injection,DI)和依赖查找(Dependency Lookup)
作用:解耦合
原来使用new方法创建对象:
AccountService accountService = new AccountServiceImpl();
现在将创建对象的权利交给工厂:
AccountService accountService = (AccountService) BeanFactory.getBean("accountService");
这就是控制反转(IOC),它的作用是削减计算机的耦合,即解除代码中的依赖关系。
4 基于XML的IOC解决程序耦合
4.1 基于XML的IOC步骤
-
步骤一:编写bean.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="com.xiaoxi.dao.impl.AccountDaoImpl"></bean> <bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl"></bean> </beans>
-
步骤二:使用核心容器对象调用配置文件,读取并创建对象
public class Client { public static void main(String[] args) { //使用ClassPathXmlApplicationContext获取核心容器对象(常用) ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); //使用FileSystemXmlApplicationContext获取核心容器对象 //ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\javacode\\ss,_spring\\ssm_spring2_ioc\\src\\main\\resources\\bean.xml"); //根据id获取bean对象,两种写法 AccountDao accountDao = applicationContext.getBean("accountDao", AccountDao.class); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); System.out.println(accountDao); System.out.println(accountService); } }
ApplicationContext有三个常用实现类:
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,配置文件必须在类路径下。不在的话,加载不了。(比FileSystemXmlApplicationContext常用)
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:它是用于读取注解创建容器的
核心容器有两个接口:ApplicationContext和BeanFactory(顶层接口,现已不用),这两个接口有区别:
-
ApplicationContext:单例对象适用,常用
它在构建核心容器时,创建对象采用的是立即加载的方式,也就是说,只要一读完配置文件就立即创建配置文件中配置的对象
-
BeanFactory:多例对象适用
它在构建核心容器时,创建对象采用的是延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象
4.2 Bean的三种创建方式
-
第一种:使用默认构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountDao" class="com.xiaoxi.dao.impl.AccountDaoImpl"></bean>
-
第二种:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器),该方法适用于需要调用某个jar包中的函数时
<bean id="instanceFactory" class="com.xiaoxi.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
-
第三种:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.xiaoxi.factory.StaticFactory" factory-method="getAccountService"></bean>
4.3 Bean的作用范围调整
Spring中Bean创建的对象默认就是单例的,如果需要调整Bean的作用范围,使用bean标签中的scope属性
scope的属性:
- singleton:单例的(默认)
- prototype:多例的
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,就是session
4.4 Bean对象的生命周期
使用bean标签中的init-method和destory-method作为bean对象的初始化方法和销毁方法
-
单例对象
-
出生:当容器创建时对象出生
-
活着:只要容器还在,对象一直活着
-
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
-
-
多例对象
- 出生:当我们使用对象时spring框架为我们创建
- 活着:对象只要是在使用过程中就一直活着。
- 死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
4.5 Spring的依赖注入
依赖注入(Dependency Injection,DI),当当前类中需要用到其他类的对象时,交给spring来管理,我们只需要在配置文件中说明就行,这种依赖关系的管理就叫依赖注入
依赖注入能注入的对象有三类:(如果是经常变化的数据,不适合使用注入)
- 基本类型和String
- 其他bean类型(在配置文件或者注解中配置过的)
- 复杂类型/集合类型
注入的方法有三种:
- 使用构造函数提供
- 使用set方法提供
- 使用注解提供
4.5.1 构造函数注入
使用constructor-arg标签,标签放在bean标签的内部
标签中的属性:
-
name:用于指定给构造函数中指定名称的参数赋值(最常用)
-
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
-
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置是从0开始
以上三个用于指定给构造函数中哪个参数赋值
-
value:用于提供基本类型和String类型的数据
-
ref:用于指定其他的bean类型的数据,即在spring的IOC,即spring的核心容器中出现过的bean对象
在AccountServiceImpl实现类中定义三个属性:aa,bb,cc
public class AccountServiceImpl implements AccountService {
private String aa;
private Integer bb;
private Date cc;
public AccountServiceImpl(String aa, Integer bb, Date cc) {
this.aa = aa;
this.bb = bb;
this.cc = cc;
}
public void saveAccount() {
System.out.println("已经保存了账户," + aa + " " + bb + " " + cc);
}
}
在bean.xml中配置构造函数注入的标签:
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl">
<constructor-arg name="aa" value="晨曦"></constructor-arg>
<constructor-arg name="bb" value="20"></constructor-arg>
<constructor-arg name="cc" ref="date"></constructor-arg>
</bean>
<bean id="date" class="java.util.Date"></bean>
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须去注入数据
4.5.2 set方法注入(更常用)
set方法注入,使用property标签,标签放在bean标签的内部
标签中的属性:
-
name:用于指定注入时所调用的set方法名称
-
value:用于提供基本类型和String类型的数据
-
ref:用于指定其他的bean类型的数据,即在spring的IOC,即spring的核心容器中出现过的bean对象
在bean.xml中配置set方法注入的标签:
<bean id="accountService2" class="com.xiaoxi.service.impl.AccountServiceImpl2">
<property name="aa" value="好好"></property>
<property name="bb" value="11"></property>
<property name="cc" ref="date"></property>
</bean>
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行,即set方法无法保证一定注入
4.5.3 复杂类型的注入/集合类型的注入
复杂类型如:数组,list, set, map, properties等
用于给List结构集合注入的标签有:
list, array, set
用于给Map结构集合注入的标签有:
map, properties
结构相同,标签可以互换
<bean id="accountService3" class="com.xiaoxi.service.impl.AccountServiceImpl3">
<property name="myList">
<list>
<value>aaa</value>
<value>bbb</value>
</list>
</property>
<property name="mySet">
<set>
<value>ccc</value>
<value>ddd</value>
</set>
</property>
<property name="myStrings">
<array>
<value>eee</value>
<value>fff</value>
</array>
</property>
<property name="myMap">
<map>
<entry key="myG" value="ggg"></entry>
<entry key="myH">
<value>hhh</value>
</entry>
</map>
</property>
<property name="myProperties">
<props>
<prop key="myI">iii</prop>
<prop key="myJ">jjj</prop>
</props>
</property>
</bean>
或者可以直接写成:
<bean id="accountService3" class="com.xiaoxi.service.impl.AccountServiceImpl3">
<property name="myList">
<list>
<value>aaa</value>
<value>bbb</value>
</list>
</property>
<property name="mySet">
<list>
<value>ccc</value>
<value>ddd</value>
</list>
</property>
<property name="myStrings">
<list>
<value>eee</value>
<value>fff</value>
</list>
</property>
<property name="myMap">
<map>
<entry key="myG" value="ggg"></entry>
<entry key="myH">
<value>hhh</value>
</entry>
</map>
</property>
<property name="myProperties">
<map>
<entry key="myI" value="iii"></entry>
<entry key="myJ">
<value>jjj</value>
</entry>
</map>
</property>
</bean>
4.6 基于XMl的IOC总结
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl"
scope="" init-method="" destory-method="">
<property name="" value="" | ref="" ></property>
</bean>
-
< bean > 标签用于创建对象
-
scope 属性用于指定作用范围(是单例还是多例)
-
init-method 和 destory-method 属性用于指定 bean 对象的生命周期(初始化方法和销毁方法)
-
< property > 和 < constructor-arg > 标签用于依赖注入数据
4.7 基于XML的IOC案例CRUD操作
在bean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.xiaoxi.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"></property>
</bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm_spring?serverTimezone=UTC"></property>
<property name="user" value="root"></property>
<property name="password" value="xiaoxi123"></property>
</bean>
</beans>
注意:由于默认配置中queryRunner对象是单例的,可能有多个线程同时访问queryRunner对象,这样就可以产生问题,所以这里在bean标签中使用scope属性将queryRunner对象改成多例的
在AccountDaoImpl实现类中写入如下代码:
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public List<Account> findAllAccount() {
try {
return queryRunner.query("select * from account", new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return queryRunner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
queryRunner.update("insert into account (name, money) values (?, ?)", account.getName(), account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void updateAccountById(Account account) {
try {
queryRunner.update("update account set name=?, money=? where id = ?", account.getName(), account.getMoney(), account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccountById(Integer accountId) {
try {
queryRunner.update("delete from account where id = ?", accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
5 基于注解的IOC解决程序耦合
5.1 创建对象
@Component
作用:用于把当前类存入 spring 容器中
属性:value, 用于指定 bean 的 id,当不写时,默认为当前类名,且首字母小写
@Controller:一般用在表现层
@Service:一般用在业务层
@Repository:一般用在持久层
这三个注解的作用和属性和 Component 是一样的,spring 框架为我们提供的明确的三层使用的注解,使我们的三层对象更加清晰
步骤一:在 bean.xml 中配置 context:component-scan 标签,告知 spring 在创建容器时需要扫描的包,context 需配置约束
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xiaoxi"/>
</beans>
步骤二:在需要创建的类名上加上@Component注解,该注解有value属性,相当于xml中bean标签的id属性,给该对象其一个唯一的id值,若省略,则id相当于类名首字母改为小写。
@Component(value="accountService") //若省略,相当于value="accountServiceImpl"
public class AccountServiceImpl implements AccountService {
public void saveAccount() {
System.out.println("账户保存了");
}
}
5.2 注入数据
@Autowired
作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
如果IOC容器中没有任何bean的类型和要注入的变量类型匹配,则报错
出现位置:可以是变量上,也可以是方法上
细节:在使用注解注入时,set方法就不是必须的了
步骤一:在AccountServiceImpl实现类上加上@Component注解,在accountDao变量上加上@Autowired
@Component("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired()
private AccountDao accountDao = null;
public void saveAccount() {
accountDao.saveAccount();
}
}
步骤二:在AccoutDaoImpl实现类上加上@Component (这一步很重要,容易遗忘,因为两个类都需要在IOC容器中查找)
@Component
public class AccountDaoImpl implements AccountDao {
public void saveAccount() {
System.out.println("保存了账户");
}
}
@Qualifier
作用:在按照类中注入的基础上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以单独使用
属性:value,用于指定注入bean的id
举例:给方法参数注入的例子
@Bean(name = "queryRunner")
@Scope(value = "prototype")
public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Resource
作用:直接按照bean的id注入,可以单独使用哦
属性:name,用于指定注入bean的id
@Component("accountService")
public class AccountServiceImpl implements AccountService {
// @Autowired()
// @Qualifier(value = "accountDaoImpl1")
@Resource(name = "accountDaoImpl1")
private AccountDao accountDao = null;
public void saveAccount() {
accountDao.saveAccount();
}
}
@Autowired , @Qualifier, @Resource三个注入都只能注入其他bean类型的数据,基本类型和String类型无法使用上述注解实现
集合类型的注入只能通过XML来实现
@Value
作用:注入基本类型和String类型的值
属性:value,用于指定注入的值,也可以使用 ${} 的方式注入值
5.3 改变作用范围
@Scope
作用:用于改变bean的作用范围
属性:value, 指定范围的取值,常用取值 singleton(默认,单例), prototype(多例)
5.4 改变生命周期
@PostConstruct 用于指定初始化方法
@PreDestory 用于指定销毁方法
5.5 完全基于注解的IOC案例CRUD操作
这里完全抛弃了xml配置,改用注解配置。
新建一个配置类,作用和bean.xml配置是一样的
@Configuration
作用:指定当前类是一个配置类
细节:当当前类作为AnnotationConfigApplicationContext的参数字节码时,可以不写Configuration注解
@ComponentScan
作用:用于通过注解指定Spring在创建容器时需要扫描的包
属性:value, basePackages,两个作用是一样的,都是用于指定创建容器时需要扫描的包
使用这个注解就相当于在xml中配置了:<context:component-scan base-package="com.xiaoxi"></context:component-scan>
@Bean
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:name, value, 两个作用是一样的,用于指定bean对象的id,当不写时,默认是当前方法的名称
细节:当我们使用注解配置时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
查找的方式和@Autowired注解的作用是一样的
@Import
作用:用于导入其他的配置类
属性:value, 用于指定其他配置类的字节码
当我们使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类
新建一个SpringConfiguration.java类
@Configuration
@ComponentScan(basePackages = "com.xiaoxi")
public class SpringConfiguration {
@Bean(name = "queryRunner")
@Scope(value = "prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(value = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssm_spring?serverTimezone=UTC");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("xiaoxi123");
return comboPooledDataSource;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在AccountServiceImpl实现类和AccountDaoImpl实现类中加上@@Configuration注解,引用注入使用@Autowired注解
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
public void updateAccountById(Account account) {
accountDao.updateAccountById(account);
}
public void deleteAccountById(Integer accountId) {
accountDao.deleteAccountById(accountId);
}
}
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner queryRunner;
public List<Account> findAllAccount() {
try {
return queryRunner.query("select * from account", new BeanListHandler<Account>(Account.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try {
return queryRunner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try {
queryRunner.update("insert into account (name, money) values (?, ?)", account.getName(), account.getMoney());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void updateAccountById(Account account) {
try {
queryRunner.update("update account set name=?, money=? where id = ?", account.getName(), account.getMoney(), account.getId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void deleteAccountById(Integer accountId) {
try {
queryRunner.update("delete from account where id = ?", accountId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在测试类中使用AnnotationConfigApplicationContext类创建applicationContext对象
@Test
public void testfindAll() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
List<Account> accounts = accountService.findAllAccount();
for(Account account: accounts) {
System.out.println(account);
}
}
5.6 改进上述代码
上述代码在调用mysql数据库的配置时是在文件中直接调用的,这里可以进行优化,新建一个jdbcConfig.properties配置文件,然后使用@Value注解注解注入成员变量的值
@PropertySource
作用:用于指定properties配置文件的位置
属性:value, 指定文件的名称和路径
关键字:classpath: 表示在类路径下
@Configuration
@ComponentScan(basePackages = "com.xiaoxi")
@PropertySource(value = "classpath:jdbcConfig.properties")
public class SpringConfiguration {
@Value(value = "${jdbc.driver}")
private String driver;
@Value(value = "${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "queryRunner")
@Scope(value = "prototype")
public QueryRunner createQueryRunner(DataSource dataSource) {
return new QueryRunner(dataSource);
}
@Bean(value = "dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(username);
comboPooledDataSource.setPassword(password);
return comboPooledDataSource;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
6 总结
基于XML的IOC和基于注解的IOC都有其优缺点,在日常使用时需根据实际情况按需选择,
一般情况下,**自己写的注入使用注解,jar 包中的注入使用基于 XML 的 IOC **这样更方便。
7 Spring整合Junit的配置
在单纯的 junit 中并不能识别 Spring 的配置,这时候需要:
- 步骤一:导入 spring 整合 junit 的 jar 包
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- 导入 spring 整合 junit 的 jar 包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
</dependencies>
-
步骤二:使用 junit 提供的一个注解把原有的main方法给替换了,替换成spring提供的
@Runwith
-
步骤三:告知spring 的运行期,spring的ioc是基于xml还是基于注解的,并且说明位置
@ContextConfiguration
属性:locations, 指定xml配置文件的位置,加上classpath关键字,表示在类路径下
classes, 指定注解类所在的位置
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes = SpringConfiguration.class) //使用注解配置
@ContextConfiguration(locations = "classpath:bean.xml") //使用xml配置
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testfindAll() {
List<Account> accounts = accountService.findAllAccount();
for(Account account: accounts) {
System.out.println(account);
}
}
}
注意:当使用spring 5.x 版本时,要求junit的jar必须是4.12以上
8 Spring 中的AOP 面向切面编程(引出AOP)
8.1 案例中添加转账方法并演示事务问题
在转账的过程中可能会出现违反数据库“一致性” 的问题,比如如果在转账中间出现了异常,可能会导致转出账户的钱已经少了,而转入账户却没有收到钱,这就会出现事务的问题,如下:
在AccountServiceImpl中添加转账方法:
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
//其他方法省略
public void transfer(String sourceName, String targetName, Float money) {
// 1 得到转出钱的账户
Account source = accountDao.findAccountByName(sourceName);
// 2 得到转入钱的账户
Account target = accountDao.findAccountByName(targetName);
// 3 转账
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
// 4 更新转出钱账户
accountDao.updateAccountById(source);
int i = 1/0; //这里故意出现异常,导致数据库违反一致性问题
// 5 更新转入钱账户
accountDao.updateAccountById(target);
}
}
最后输出结果,导致转出账户的钱已经少了,而转入账户却没有收到钱。
解决方法:需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象
新建ConnectionUtils工具类
/**
* 连接的工具类,用于从数据源中获取连接,并绑定线程
*/
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try {
//先从当前线程上获取连接
Connection connection = threadLocal.get();
//如果当前线程上没有连接
if (connection == null) {
//从数据源中获取一个连接,并且和线程绑定(存入threadLocal中)
connection = dataSource.getConnection();
//和线程绑定
threadLocal.set(connection);
}
return connection;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection() {
threadLocal.remove();
}
}
然后新建一个和事务管理相关的工具类
/**
* 和事务管理相关的工具类,包含了开启事务,提交事务,回滚事务,释放连接操作
*/
public class TransactionManager {
//获取当前线程上的Connection
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 提交事务
*/
public void commitTransaction() {
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 事务回滚
*/
public void rollbackTransaction() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 释放连接
*/
public void release() {
try {
connectionUtils.getThreadConnection().close(); //不是真正的把连接关了,而是还回连接池中
connectionUtils.removeConnection(); //把连接和线程解绑
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
在AccountServiceImpl业务层加入TransactionManager对象
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
// 其他省略
/**
* 转钱的业务层实现类
* @param sourceName 转出钱的账户名称
* @param targetName 转入钱的账户名称
* @param money 转的钱多少
*/
public void transfer(String sourceName, String targetName, Float money) {
try{
// 1 开启事务
transactionManager.beginTransaction();
// 2 执行操作
// 2.1 得到转出钱的账户
Account source = accountDao.findAccountByName(sourceName);
// 2.2 得到转入钱的账户
Account target = accountDao.findAccountByName(targetName);
// 2.3 转账
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
// 2.4 更新转出钱账户
accountDao.updateAccountById(source);
int i = 1/0;
// 2.5 更新转入钱账户
accountDao.updateAccountById(target);
// 3 提交事务
transactionManager.commitTransaction();
// 4 返回结果
} catch (Exception e) {
// 5 回滚事务
transactionManager.rollbackTransaction();
e.printStackTrace();
} finally {
// 6 释放连接
transactionManager.release();
}
}
}
在 AccountDaoImpl 持久层加上 ConnectionUtils 对象,并使用queryRunner对象的含connection的方法,使用connectionUtils.getThreadConnection()得到connection对象
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner;
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
//其他省略
/**
* 根据账户名称查找账户,如果账户名称唯一,返回,如果没有账户,返回null,如果账户名称不唯一,报错
* @param accountName
* @return
*/
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = queryRunner.query(connectionUtils.getThreadConnection(), "select * from account where name = ?", new BeanListHandler<Account>(Account.class), accountName);
if(accounts == null || accounts.size() == 0) {
return null;
}
if(accounts.size() > 1) {
throw new RuntimeException("账户名称不唯一,失败");
}
return accounts.get(0);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
最后配置bean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!-- 注入transactionManager对象-->
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<bean id="accountDao" class="com.xiaoxi.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"></property>
<!-- 注入connectionUtils对象-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置QueryRunner对象-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!-- 配置DataSource数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm_spring?serverTimezone=UTC"></property>
<property name="user" value="root"></property>
<property name="password" value="xiaoxi123"></property>
</bean>
<!-- 配置ConnectionUtils工具类-->
<bean id="connectionUtils" class="com.xiaoxi.util.ConnectionUtils">
<!-- 注入dataSource对象-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置TransactionManager工具类-->
<bean id="transactionManager" class="com.xiaoxi.util.TransactionManager">
<!-- 注入connectionUtils对象-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
8.2 演示动态代理
动态代理的特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对源码增强
分类:1)基于接口的动态代理 2)基于子类的动态代理
8.2.1 基于接口的动态代理
基于接口的动态代理:
涉及的类:Proxy
提供者:jdk官方
如何创建动态代理:
使用Proxy类中的newProxyInstance方法
创建代理类的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
-
ClassLoader loader:类加载器,它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器(代理谁,就写谁的ClassLoader)
-
Class<?>[] interfaces:被代理对象实现的接口,让代理对象实现相同的接口
-
InvocationHandler h:用于提供增强的方法,让我们写如何代理,一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的,
此接口的实现类都是随用随写。执行被代理对象的任何方法都会经过invoke方法
举例:
新建一个Producer接口:
public interface Producer {
public void saleProduct(float money);
public void afterService(float money);
}
新建一个ProducerImpl的实现类:
public class ProducerImpl implements Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money) {
System.out.println("产品卖出去了,并拿到钱" + money);
}
/**
* 售后
* @param money
*/
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱" + money);
}
}
新建一个Client消费者的类:
public class Client {
public static void main(String[] args) {
final Producer producer = new ProducerImpl();
Producer proxyProducer = (Producer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用:执行被代理对象的任何方法都会经过invoke方法
* @param proxy 被代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象的方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
// 1 获取方法执行的参数
Float money = (Float)args[0];
// 2 判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
8.2.2 基于子类的动态代理
基于子类的动态代理
涉及的类:Enhancer
提供者:第三方cglib库
如何创建动态代理:
使用Enhancer类中的create方法
创建代理类的要求:
被代理类不能是最终类(被代理类没有用final修饰符修饰)
create方法的参数:
- Class type:字节码,用于指定被代理对象的字节码
- Callback callback:用于提供增强的方法,一般写的是该接口的子接口实现类,MethodIntercepter
示例:
首先要导入依赖包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
被代理类ProducerImpl不再是接口的实现类
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new ProducerImpl();
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何方法都会经过该方法
* @param proxy 被代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @param methodProxy 当前执行方法的代理对象
* @return 和被代理对象执行方法中的返回值一样
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
// 1 获取方法执行的参数
Float money = (Float)args[0];
// 2 判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
// System.out.println(returnValue);
return returnValue;
}
});
cglibProducer.saleProduct(10000f);
}
}
8.3 使用动态代理实现事务控制
将事务控制的方法交给代理类来实现,而不是在原来的AccountServiceImpl基础上添加事务控制的代码
新建BeanFactory工厂类专门使用动态代理实现事务控制,代码如下:
public class BeanFactory {
private AccountService accountService;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* 获取accountService的代理对象
* @return
*/
public AccountService getAccountService() {
AccountService as = (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try{
// 1 开启事务
transactionManager.beginTransaction();
// 2 执行操作
returnValue = method.invoke(accountService, args);
// 3 提交事务
transactionManager.commitTransaction();
// 4 返回结果
return returnValue;
} catch (Exception e) {
// 5 回滚事务
transactionManager.rollbackTransaction();
throw new RuntimeException(e);
} finally {
// 6 释放连接
transactionManager.release();
}
}
});
return as;
}
}
配置bean.xml文件创建bean对象并注入数据,需要配置的是beanFactory工厂,和新的proxyAccountService对象,这个对象是使用beanFactory类中的方法来创建对象的,所以使用factory-bean和factory-method属性。
<!-- 其他的配置省略-->
<!-- 配置beanFactory工厂-->
<bean id="beanFactory" class="com.xiaoxi.bean.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
需要注意的是,在配置文件中有两个accountService对象,在测试类中使用时,需要在对象上附加上@Qualifier注解以便spring容器可以识别需要创建的是哪一个对象
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")
private AccountService accountService;
@Test
public void testtransfer() {
accountService.transfer("aaa", "ccc", 200f);
}
}
9 Spring中基于XML的AOP配置
AOP(Aspect Orented Programming, 面向切面编程),通过预编译方式和运行期动态代理实现程序功能。
作用:降低耦合度,提高程序可重用性,提高开发效率
AOP的实现方式是使用动态代理的技术
Spring中基于XML的AOP配置步骤:
-
第一步:把通知Bean也交给spring来管理
-
第二步:使用 < aop: config > 标签进行AOP的配置
-
第三步:使用 < aop: aspect > 标签表示配置切面
id 属性:是给切面提供一个唯一标识
ref 属性:是指定通知类Bean的id
-
第四步:在 < aop: aspect > 标签的内部使用对应标签来配置通知的类型
< aop: before > 表示配置前通知
method 属性,用于指定配置通知的哪个方法
pointcut 属性,用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
< aop: after > 表示配置后通知
示例:记得约束要加上xmlns:aop
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置accountService对象-->
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl"></bean>
<!-- 配置日志对象-->
<bean id="logger" class="com.xiaoxi.utils.Logger"></bean>
<!-- 配置AOP-->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法之间的关联-->
<aop:before method="printLog" pointcut="execution(public void com.xiaoxi.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
9.1 切入点表达式的写法
切入点表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.xiaoxi.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略,省略后的写法:
void com.xiaoxi.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符 *
表示任意返回值
* com.xiaoxi.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符表示任意包,但是有几级包,但是有几级包就需要写几个 *.
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用 ..
表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用 *
来实现通配
* *..*.*()
参数列表可以直接写数据类型
- 基本类型直接写名称 int
- 引用类型写包名.类名的方式 java.lang.String
- 可以使用通配符 * 表示,但是必须表示任意有参数的方法
- 可以使用 … 表示任意类型,方法有参数无参数都可以
全通配写法(适配所有类中的所有方法,也就是给所有类都加上增强的方法):
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法,如下:
* com.xiaoxi.service.impl.*.*(..)
9.2 四种常用通知的类型
- < aop:before > 前置通知,在切入点方法执行之前执行
- < aop:after-returning > 后置通知,在切入点方法正常执行之后执行,与异常通知有且仅能有一个执行
- < aop:after-throwing > 异常执行,在切入点方法执行时产生异常之后执行,与后置通知有且仅能有一个执行
- < aop:after > 最终通知,无论切入点方法是否正常执行它都会在其后面执行
<!-- 配置AOP-->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pointcut1"></aop:before>
<!-- 配置后置通知,在切入点方法正常执行之后执行,与异常通知有且仅能有一个执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pointcut1"></aop:after-returning>
<!-- 配置异常执行,在切入点方法执行时产生异常之后执行,与后置通知有且仅能有一个执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pointcut1"></aop:after-throwing>
<!-- 配置最终通知,无论切入点方法是否正常执行它都会在其后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pointcut1"></aop:after>
<!-- 配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式的内容-->
<aop:pointcut id="pointcut1" expression="execution(* com.xiaoxi.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
9.3 环绕通知
< aop:around > 环绕通知,必须有明确的切入点方法调用,否则只会执行环绕通知的语句,不会执行切入点方法
- 解决:Spring 为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法 proceed(),此方法就相当于明确的调用切入点方法
该接口可以作为环绕通知的方法参数,在程序执行时,spring 框架会为我们提供该接口的实现类供我们使用
环绕通知是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
public class Logger {
/**
* 环绕通知
*/
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
try {
Object returnValue = null;
System.out.println("环绕通知Logger类中的方法开始记录日志了。。前置");
Object[] args = proceedingJoinPoint.getArgs();
returnValue = proceedingJoinPoint.proceed(args);
System.out.println("环绕通知Logger类中的方法开始记录日志了。。后置");
return returnValue;
} catch (Throwable t) {
System.out.println("环绕通知Logger类中的方法开始记录日志了。。异常");
throw new RuntimeException(t);
} finally {
System.out.println("环绕通知Logger类中的方法开始记录日志了。。最终");
}
// System.out.println("环绕通知Logger类中的方法开始记录日志了");
}
}
<!-- 配置AOP-->
<aop:config>
<!-- 配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<aop:around method="aroundPrintLog" pointcut-ref="pointcut1"></aop:around>
<!-- 配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式的内容-->
<aop:pointcut id="pointcut1" expression="execution(* com.xiaoxi.service.impl.*.*(..))"/>
</aop:aspect>
</aop:config>
10 Spring中基于注解的AOP配置
配置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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.xiaoxi"></context:component-scan>
<!-- 配置spring开启aop注解的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
使用注解配置Logger类,使用@Aspect注解
/**
* 用于记录日志的工具类,它里面提供了公共的方法
*/
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.xiaoxi.service.impl.*.*(..))")
public void pointcut1(){
}
/**
* 前置通知
*/
@Before("pointcut1()") //注意千万别忘了加括号
public void beforePrintLog() {
System.out.println("前置通知Logger类中的方法开始记录日志了");
}
/**
* 后置通知
*/
@AfterReturning(pointcut = "pointcut1()")
public void afterReturningPrintLog() {
System.out.println("后置通知Logger类中的方法开始记录日志了");
}
/**
* 异常通知
*/
@AfterThrowing("pointcut1()")
public void afterThrowingPrintLog() {
System.out.println("异常通知Logger类中的方法开始记录日志了");
}
/**
* 最终通知
*/
@After("pointcut1()")
public void afterPrintLog() {
System.out.println("最终通知Logger类中的方法开始记录日志了");
}
/**
* 环绕通知
*/
// @Around("pointcut1()")
public Object aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
try {
Object returnValue = null;
System.out.println("环绕通知Logger类中的方法开始记录日志了。。前置");
Object[] args = proceedingJoinPoint.getArgs();
returnValue = proceedingJoinPoint.proceed(args);
System.out.println("环绕通知Logger类中的方法开始记录日志了。。后置");
return returnValue;
} catch (Throwable t) {
System.out.println("环绕通知Logger类中的方法开始记录日志了。。异常");
throw new RuntimeException(t);
} finally {
System.out.println("环绕通知Logger类中的方法开始记录日志了。。最终");
}
// System.out.println("环绕通知Logger类中的方法开始记录日志了");
}
}
如果要使用纯注解的方式,完全不使用xml的配置方式:
@Configuration
@ComponentScan (basePackages="com.itheima" )
@EnableAspectJAutoProxy //加上这一行
public class SpringConfiguration {
}
11 JdbcTemplate的CRUD操作
Spring 提供了一个操作数据库的对象 JdbcTemplate
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//通过id查询
public Account findAccountById(Integer id) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), id);
return accounts.isEmpty()?null: accounts.get(0);
}
//通过name查询
public Account findAccountByName(String name) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), name);
if(accounts.isEmpty()){
return null;
}
if(accounts.size() > 1) {
throw new RuntimeException("出错,名字不唯一");
}
return accounts.get(0);
}
//返回一行一列,使用聚合函数,但不使用group by
public Integer findNumber(Float money) {
Integer count = jdbcTemplate.queryForObject("select count(*) from account where money > ?", Integer.class, money);
return count;
}
//增加账户
public void saveAccount(Account account) {
jdbcTemplate.update("insert into account (name, money) values (?, ?)", account.getName(), account.getMoney());
}
//删除账户
public void deleteAccount(Integer id) {
jdbcTemplate.update("delete from account where id = ?", id);
}
//更新账户
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
}
}
12 Spring 中事务控制
12.1 Spring中基于XML的声明式事务控制
基于XML的声明式事务控制的配置步骤:
-
1、配置事务管理器
-
2、 配置事务的通知
-
此时需要导入事务的约束,包括tx和aop的名称空间和约束
-
使用 < tx:advice > 标签配置事务通知
-
属性: id, 给事务通知起一个唯一的标识
transaction-manager, 给事务通知提供一个事务管理器引用
-
-
3、配置 AOP 中的通用切入点表达式
-
4、建立事务通知和切入点表达式之间的对应关系
- 使用 < aop: advisor >标签
-
5、配置事务的属性
- 在事务通知的内部 tx:advice 标签的内部配置
- isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
- propagation:用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务(增删改选择这个),查询方法可以选择SUPPORTS
- read-only:用于指定事务是否只读,只有查询方法才能设置为true,默认值是false,表示读写
- timeout:用于指定事务的超时时间,默认值是 -1,表示永不超时,如果指定了数值,以秒为单位
- rollback-for:用于指定一个异常,当产生该异常时回滚,产生其他异常时不回滚。没有默认值,表示所有异常都回滚
- no-rollback-for:用于指定一个异常,当产生该异常时不回滚,产生其他异常时回滚。没有默认值,表示所有异常都回滚
示例:
<?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: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.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.xsd">
<bean id="accountService" class="com.xiaoxi.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.xiaoxi.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置AOP中的通用切入点表达式-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.xiaoxi.service.impl.*.*(..))"/>
<!-- 建立事务通知和切入点表达式之间的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
<!-- 配置DataSource数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm_spring?serverTimezone=UTC"></property>
<property name="user" value="root"></property>
<property name="password" value="xiaoxi123"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
12.2 Spring中基于注解的声明式事务控制
基于注解的声明式事务控制的配置步骤:
-
1、配置事务管理器
-
2、开启spring对注解事务的支持
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
3、在需要事务支持的地方使用 @Transactional 注解
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
/**
* 转钱的业务层实现类
* @param sourceName 转出钱的账户名称
* @param targetName 转入钱的账户名称
* @param money 转的钱多少
*/
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer开始执行了");
// 2.1 得到转出钱的账户
Account source = accountDao.findAccountByName(sourceName);
// 2.2 得到转入钱的账户
Account target = accountDao.findAccountByName(targetName);
// 2.3 转账
source.setMoney(source.getMoney() - money);
target.setMoney(target.getMoney() + money);
// 2.4 更新转出钱账户
accountDao.updateAccountById(source);
// int i = 1/0;
// 2.5 更新转入钱账户
accountDao.updateAccountById(target);
}
}
12.3 Spring中完全基于注解的声明式事务控制
@EnableTransactionManager 开启Spring事务注解的支持
新建Spring的配置类,相当于bean.xml
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.xiaoxi")
@Import({JdbcConfig.class, TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class AnnotationConfig {
}
/**
* 和连接数据库相关的配置类
*/
@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean("dataSource")
public DataSource createDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
@Bean("transactionManager")
public TransactionManager createTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}