Spring框架详解(二)

一、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没有声明事务,那就不用事务.

发布了111 篇原创文章 · 获赞 41 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhongliwen1981/article/details/99599317