一、AOP编程
1.1 什么是代理技术?
在实际开发中,客户的需求可能会随着软件的开发而发生变化。如果在项目前期的时候,业务需求的变化对项目的影响可能不是很大。但是,如果到了项目后期阶段,这时候软件的功能已经基本完成。那么如果客户这时候提出要修改需求,这样可能会导致项目的影响是很大的,严重的话可能会导致项目推动重来。
使用代理技术可以解决由于需求变化对业务代码的影响。Java动态代理的优势是实现无侵入式的代码扩展,使得用户可以在不修改原有代码的基础上额外增加新的功能。
1.1.1 静态代理
静态代理是通过创建一个代理类,对已有类的方法进行了功能的扩展。
静态代理的优点:
1)代码直观,易于阅读;
2)代理类在程序编译之前就已经创建出来,执行效率高;
静态代理的缺点:
1)当存在多个目标需要代理的时候,必须要为每个目标类创建代理类,比较繁琐;
2)每个代理类中可能会存在相同的代码,程序的维护性较差;
3)由于代理类在程序编译前就定义好了,灵活性较差;
案例:银行和追数公司。
// 机构
interface Agent {
void taozhai();
}
// 工商银行
class IcbcBank implements Agent {
public void taozhai() {
System.out.println("把钱打入工商银行帐号...");
System.out.println("还清...");
}
}
// 中国银行
class ChinaBank implements Agent {
public void taozhai() {
System.out.println("把钱打入中国银行帐号...");
System.out.println("还清...");
}
}
// 银行代理(追债公司)
class BankProxy implements Agent {
int num;
public BankProxy(int num) {
this.num = num;
}
@Override
public void taozhai() {
System.out.println("打电话...");
System.out.println("派人上门...");
System.out.println("恐吓...");
if (num == 1) {
IcbcBank icbc = new IcbcBank();
icbc.taozhai();
} else if (num == 2) {
ChinaBank cb = new ChinaBank();
cb.taozhai();
}
}
}
// 测试
public class Demo01 {
public static void main(String[] args) {
BankProxy proxy = new BankProxy(1);
proxy.taozhai();
}
}
1.1.2 动态代理
与静态代理不同,动态代理的特点:
- 静态代理是在编译期之前就加入,而动态代理是在程序运行的时候才加入;
- 静态代理会生成代理类的Class文件,而动态代理不会生成Class文件;
- 动态代理比静态代理的灵活性更好;
实现动态代理功能,需要使用到JDK中的java.lang.reflect.Proxy类。该类提供了用于创建动态代理对象的静态方法。
参数说明:
- loader:代理类的类加载器;
- interfaces:代理类要实现的接口列表;
- h:调用处理程序,通过该对象可以调用被代理类中的方法;
案例:使用动态代理改造上面例子。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Agent {
void taozhai();
}
class IcbcBank implements Agent {
public void taozhai() {
System.out.println("把钱打入工商银行帐号...");
System.out.println("还清...");
}
}
class ChinaBank implements Agent {
public void taozhai() {
System.out.println("把钱打入中国银行帐号...");
System.out.println("还清...");
}
}
public class Demo02 {
public static void main(String[] args) {
IcbcBank bank = new IcbcBank();
Agent bankProxy = (Agent) Proxy.newProxyInstance(
bank.getClass().getClassLoader(), //与被代理类具有相同的类加载器
bank.getClass().getInterfaces(), //与被代理类实现的接口一样
new InvocationHandler() { //调用处理器
/**
* 功能:执行被代理对象的任何方法,都会经过该方法。
*
* 参数说明:
* proxy: 动态代理对象的引用
* method: 当前执行的方法
* args: 方法参数列表
*
* 返回值:
* 当前方法的返回值。
*/
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
System.out.println("打电话...");
System.out.println("派人上门...");
System.out.println("恐吓...");
method.invoke(bank);
return null;
}
});
bankProxy.taozhai();
}
}
1.2 AOP概述
AOP(Aspect Oriented Programming):面向切面编程。它把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
AOP的相关术语:
名称 | 功能 |
---|---|
连接点(Joint Point) | 连接点是指那些被拦截的方法 |
切入点(Pointcut) | 切入点是指对Jointpoint进行拦截的定义 |
通知(Advice) | 通知是指拦截到Joinpoint之后所要做的事情 |
目标对象(Target) | 被代理的对象 |
代理(Proxy) | 增强后的对象 |
切面(Aspect) | 切入点和通知的结合 |
1.3 Spring整合AOP
第一步:引入aop相关的jar包;
aopalliance-1.0.jar
aspectjweaver-1.9.4.jar
spring-aop-5.1.9.RELEASE.jar
spring-aspects-5.1.9.RELEASE.jar
第二步:修改spring配置文件,开启aop功能;
<aop:aspectj-autoproxy/>
第三步:定义切面类;
@Component
@Aspect
public class Logger {
}
第四步:定义增强方法,然后在方法上定义切入点;
@Component
@Aspect
public class Logger {
@Before("execution(* com.xjy.service.impl.*.*(..))")
public void before() {
System.out.println("前置通知...");
}
}
第五步:测试;
@Test
public void testFindAll() {
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:beans.xml");
ICustomerService customerService = (ICustomerService) ac.getBean("customerServiceImpl");
customerService.findAllCustomer();
}
1.4 AOP注解介绍
注解名 | 功能 |
---|---|
@Aspect | 把当前类声明为切面 |
@Before(value=“切入表达式”) | 把当前方法设置为前置通知 |
@AfterReturning(value=“切入表达式”) | 把当前方法设置为后置通知 |
@After(value=“切入表达式”) | 把当前方法设置为最终通知 |
@AfterThrowing(value=“切入表达式”) | 把当前方法设置为异常通知 |
@Around | 把当前方法设置为环绕通知,即@Before和@After的结合 |
@Pointcut(value=“切入表达式”) | 指定切入表达式 |
@After和@AfterReturning的区别?
- 它们都是在切入方法执行完成后执行,但是@AfterReturning在@After之后执行;
- 如果方法出现异常,那么@AfterReturning将不会执行;而@After无论方法是否正常结束,它都会被执行;
- @AfterReturning可以访问目标方法的返回值,而@After就无法做到;
@AfterReturning(value="execution(* com.xjy.service.impl.*.*(..))", returning="rt")
public void afterReturn(Object rt) throws Throwable {
System.out.println("切入方法的返回值:" + rt);
System.out.println("方法返回通知...");
}
注意:returning属性值要与增强方法的参数名相同。
1.5 AOP零配置
使用@EnableAspectJAutoProxy注解启用aop功能。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={UserServiceTest.class})
@ComponentScan(basePackages={"com.xjy"})
@EnableAspectJAutoProxy
public class UserServiceTest {
@Autowired
private IUserService userService;
@Test
public void testSaveUser() {
userService.saveUser();
}
}
二、Spring和MyBatis整合
2.1 整合步骤
第一步:导入mybatis-spring-1.3.2.jar文件。
下载地址:https://mvnrepository.com/artifact/org.mybatis/mybatis-spring
第二步:在映射接口上使用@Repository注解。
@Repository
public interface UserMapper {
void addUser(User user);
}
第三步:把Mapper接口注入到业务层。
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
}
第四步:编写测试类。
package springqs.test;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import springqs.beans.User;
import springqs.service.IUserService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={UserServiceTest.class})
@Configuration
@ComponentScan(basePackages={"springqs"})
@MapperScan("springqs.mapper")
public class UserServiceTest {
@Autowired
private IUserService userService;
@Bean(name="dataSource")
public DataSource getDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource")DataSource ds) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(ds);
return sessionFactory.getObject();
}
@Test
public void testAddUser() {
User user = new User();
user.setName("ddee");
user.setAge(12);
userService.addUser(user);
}
}
2.2 配置事务
第一步:配置事务管理器。
@Bean
public DataSourceTransactionManager txManager(@Qualifier("dataSource")DataSource ds) {
return new DataSourceTransactionManager(ds);
}
第二步:开启事务功能。
@EnableTransactionManagement
public class UserServiceTest {
}
第三步:在业务类上或业务方法上使用@Transactional注解。
@Service
@Transactional
public class UserServiceImpl implements IUserService {
}
如果在类上使用@Transactional注解,那么该类下的所有方法都在事务中运行。
如果在方法上使用@Transactional注解,那么只有该方法在事务中运行。
属性说明:
- readOnly:该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写。
- rollbackFor:该属性用于设置需要进行回滚的异常类数组。只有当方法抛出异常类数组中的异常才会回滚事务。(比如:@Transactional(rollbackFor={RuntimeException.class, Exception.class}))
- rollbackForClassName:该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。(比如:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”}))
- noRollbackFor:该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。具体用法与rollbackFor类似。
- noRollbackForClassName:该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。具体用法与rollbackForClassName类似。
- propagation:该属性用于设置事务的传播行为。
- isolation:该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置。(比如:@Transactional(isolation = Isolation.REPEATABLE_READ))
- timeout:该属性用于设置事务的超时秒数,默认值为-1表示永不超时。
2.3 MySQL的事务隔离级别
数据库事务的四大特性:
- 原子性:在一个事务中的所有操作,要么全部成功,要么全部失败。
- 持久性:如果一个事务结束,那么事务中的数据必须要保存到数据库中。
- 隔离性:如果存在多个事务,事务之间是互不干扰。
- 一致性:在一个事务执行完之后,数据的状态是保持不变的。
数据库事务隔离一共有四个级别,从低级到高级分别为:
- read uncommitted:允许一个事务可以读取另外一个未提交事务的数据,引起脏读问题;
- read committed:只允许一个事务只能够读取到另外一个事务已提交的数据,可以解决脏读问题,但是会不可重复读;
- repeatable read:只有一个事务结束之后,它才可以读取其他已提交事务的数据。MySQL默认的事务级别,解决不可重复读的问题;
- serializable:在同一个时间内,只允许有一个事务执行;
MySQL数据库默认事务隔离级别为repeatable read级别。事务隔离级别越高,数据就越安全,但是数据库的执行效率就越慢。
修改事务隔离级别:
mysql > set global transaction isolation level 级别名;
查看数据库的事务隔离级别:
mysql > select @@global.tx_isolation;
在实际开发中除非有特殊要求,一般情况下不会修改数据库的事务隔离级别。
附录:事务传播行为
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.