文章目录
spring和Mybatis使用起来有区别,Mybatis纯xml配置文件开发是至少两个配置文件,一个主配置文件,一个dao配置文件。spring的纯xml开发只有一个配置文件,使用注解和xml混合开发和纯xm配置开发要实现的功能是一样的,只是配置的地方不一样。
1、IOC配置
1.1 创建maven工程
projectName
|-src
|-main
|-java
|-cn.klb
|-dao
|-AccountDao
|-impl
|-AccountDaoImpl
|-service
|-AccountService
|-impl
|-AccountServiceImpl
|-resource
|-bean.xml
1.2 导入jar包坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
1.3 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 告知 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="cn.klb"></context:component-scan>
</beans>
1.4 编写实体类
持久层
package cn.klb.dao;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/8 22:28
* @Modified By:
*/
public interface AccountDao {
public void saveAccount();
}
package cn.klb.dao.impl;
import cn.klb.dao.AccountDao;
import org.springframework.stereotype.Repository;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/8 22:28
* @Modified By:
*/
@Repository("accountDaoImpl")
public class AccountDaoImpl implements AccountDao {
public void saveAccount() {
System.out.println("AccountDaoImpl的saveAccount执行了...");
}
}
业务层:
package cn.klb.service;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/8 22:29
* @Modified By:
*/
public interface AccountService {
public void saveAccount();
}
package cn.klb.service.impl;
import cn.klb.dao.AccountDao;
import cn.klb.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/8 22:29
* @Modified By:
*/
@Service("accountServiceImpl")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void saveAccount() {
System.out.println("AccountServiceImpl的saveAccount执行了...");
accountDao.saveAccount();
}
}
1.4 测试代码
package cn.klb;
import cn.klb.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/10 13:28
* @Modified By:
*/
public class AnnotationTest {
@Test
public void testSpringIOC(){
//1.使用 ApplicationContext 接口,就是在获取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据 bean 的 id 获取对象
AccountService aService = (AccountService) ac.getBean("accountServiceImpl");
aService.saveAccount();
}
}
输出结果:
1.5 小结
IOC注解开发其实就是把<bean>
标签的内容移到各个类上面,持久层用@Repository
,业务层用@Service
,其实这两个标签等同于@Component
,源码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
/**
* The value may indicate a suggestion for a logical component name,
* to be turned into a Spring bean in case of an autodetected component.
* @return the suggested component name, if any (or empty String otherwise)
*/
@AliasFor(annotation = Component.class)
String value() default "";
}
可以看出,这两个注解都是@Component
的别名,为了好看而已。
使用的时候,肯定是先扫描配置文件,配置文件的内容就很简洁了,首先就是告诉程序要去扫描哪些包的注解,<context:component-scan base-package="cn.klb"></context:component-scan>
就告诉程序:我的bean注解都在这些包里面。
2、AOP配置
xml配置文件的AOP配置是在xml文件中定义<aop:config>
标签,标签体定义通知类的方法和需要增强的表达式。注解配置的实现功能是一样的,只是换个地方配置,<aop:config>
标签换成在通知类上,表达式和通知类型都定义在这个类的注解上。
2.1 创建maven工程
projectName
|-src
|-main
|-java
|-cn.klb
|-dao
|-AccountDao
|-impl
|-AccountDaoImpl
|-service
|-AccountService
|-impl
|-AccountServiceImpl
|-utils
|-Logger
|-resource
|-bean.xml
2.2 导入jar坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
同理,maven会自动导入间接依赖
2.3 实体类
实体类内容和第一节一样。
2.4 配置文件
<?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="cn.klb"></context:component-scan>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
当大部分的IOC交给注解了,那么配置文件只剩一个作用:开启xx。
2.5 AOP注解配置
原来的AOP的xml配置文件流程大体如下:
<!-- 配置Logger类 -->
<bean id="logger" class="cn.klb.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* cn.klb.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
<!-- <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
<!-- <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行-->
<!-- <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
</aop:aspect>
</aop:config>
可见,AOP至少两个部分:通知类+被增强的表达式。
Logger类:
package cn.klb.utils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("txManager")
@Aspect
public class Logger {
// 定义切入点表达式的引用
@Pointcut("execution(* cn.klb.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
}
2.6 测试类
package cn.klb;
import cn.klb.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @Author: Konglibin
* @Description:
* @Date: Create in 2020/3/10 13:28
* @Modified By:
*/
public class AnnotationTest {
@Test
public void testSpringIOC(){
//1.使用 ApplicationContext 接口,就是在获取 spring 容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据 bean 的 id 获取对象
AccountService aService = (AccountService) ac.getBean("accountServiceImpl");
aService.saveAccount();
}
}
测试结果:
3、事务控制
事务控制其实本质也是AOP。上面的注解配置AOP写道,把AOP的注解配置都写在通知类当中,而spring的事务是框架做好了的,所以我们是把事务的AOP配置在待增强的方法、类或接口当中。
准备一个数据库表:
3.1 创建maven工程
projectName
|-src
|-main
|-java
|-cn.klb
|-dao
|-AccountDao
|-impl
|-AccountDaoImpl
|-service
|-AccountService
|-impl
|-AccountServiceImpl
|-resource
|-bean.xml
3.2 导入jar包坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
这里涉及到junit整合spring的内容,自行百度。
3.3 配置文件
<?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:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="cn.klb"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/ssm"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- spring中基于注解 的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
3.4 程序源码
持久层
package cn.klb.dao;
import cn.klb.domain.Account;
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 根据Id查询账户
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 根据名称查询账户
* @param accountName
* @return
*/
Account findAccountByName(String accountName);
/**
* 更新账户
* @param account
*/
void updateAccount(Account account);
}
package cn.klb.dao.impl;
import cn.klb.dao.IAccountDao;
import cn.klb.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 账户的持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
业务层
package cn.klb.service;
import cn.klb.domain.Account;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 根据id查询账户信息
* @param accountId
* @return
*/
Account findAccountById(Integer accountId);
/**
* 转账
* @param sourceName 转成账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
void transfer(String sourceName,String targetName,Float money);
}
package cn.klb.service.impl;
import cn.klb.dao.IAccountDao;
import cn.klb.domain.Account;
import cn.klb.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 账户的业务层实现类
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
//需要的是读写型事务配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
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);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
Account实体类
package cn.klb.domain;
import java.io.Serializable;
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
//省略set get的书写
}
3.5 测试类
package cn.klb.test;
import cn.klb.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
业务层人为加一个异常,运行结果:
数据表无改变。可以,事务管理生效了。
4、纯注解开发
经过那么多注解,spring的配置文件只剩下:告诉spring要扫描的包、开启AOP、开启事务等一些开关性的工作和配置数据源等。如果全使用注解配置,意味着这些公共配置要交给一个类来代替。
@Configuration
@EnableTransactionManagement
public class SpringTxConfiguration {
//里面配置数据源,配置 JdbcTemplate,配置事务管理器。
}
定义了这个类后,要调用spring管理的所有东西,就不需要加载bean.xml配置文件,转为加载这个类即可。
其实spring的核心作用就是解耦,可以把程序分成若干“组件”,通过配置文件进行组合更替,如果全都是注解,一旦要改点什么,还不是要来改源码,违背了解耦的初衷。其实纯注解开发用的较少,用最多的就是配置文件和注解混合开发,一些固定的配置用注解,灵活的配置写在配置文件中。