AOP目标
- AOP核心概念
- Spring - ProxyFactory
- 目标对象- 基于XML配置实现AOP
- 基于注解配置实现AOP
1.注解
- Spring IOC基础注解
- @Component - 类 --创建该类的对象存入到Spring容器中,默认使用类名(首字母小写)
- @Controller
- @Service
- @Repository
- 属性-@Resource(name=""),@Autowired(required=true|false),@Qualifier("")
- 为属性注入值的@Value注解
- -String或数值 [基本简单类型的值]
- Properties属性文件中的值
- 整合MyBatis-基于 XML配置
- DataSource
- SqlSessionFactoryBean
- 造Dao接口代理子类对象的配置
- Spring新注解
- @Configuration - 配置类[使用java类来取代xml配置]
- @ComponentScan({"包名"})
- @Bean -<bean>
2.Spring注解
## 2-1 @PropertySource注解
- 用于加载.properties 文件中的配置
- 我们配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置
属性:value[]:用于指定 properties 文件位置
- 示例:
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/smbms?serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
jdbc.username=root jdbc.password=root
#Druid连接池的配置
druid.initalSize=4
druid.maxActive=20
druid.minIdel=5
创建配置类:JdbcConfig.java
@Configuration
@PropertySource({"classpath:db.properties"}) //ctrl+p 读取属性配置文件*.properties
public class JdbcConfig {
@Value("{{jdbc.driverClass}")
private String driverClass;
@Value("{{jdbc.url}")
private String url;
@Value("{{jdbc.username}")
private String username;
@Value("{{jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
System.out.println("driverClass:"+driverClass);
dataSource.setDriverClassName(driverClass);
System.out.println("url:"+url);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setInitialSize(4);
dataSource.setMaxActive(20);
dataSource.setMinIdle(20);
return dataSource;
}
}
## 2-2 @Import注解
- 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解
- 将 @Value 属性 (甚至包括数据库的配置) 拆分出去,再通过 @Import 引入回来,变相强迫 Spring 在
MapperScannerConfigurer 进行包扫描前先处理它们
@Configuration
@ComponentScan(basePackages = "com.woniu")
@Import(value = {JdbcConfig.class})
public class SpringConfig {
}
## 2-3 Spring整合JUnit
- 如果没有整合JUnit,则每次在测试方法中都需要手动创建Spring工厂对象[扫描包,创建相应对象放入容器中]
- 整合的目的就是当我们启动JUnit运行器方法时,自动的创建Spring容器
- 具体的实现步骤:
step1: 添加整合Junit的依赖包
<!--整合JUnit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
step2: 在JUnit 测试类上添加注解@RunWith
使用Spring框架提供的运行器替代JUnit运行器,就会在启动时自动创建spring工厂
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfig.class}) //指定运行器需要读取的 Java配置类
//@ContextConfiguration(locations={"classpath:applicationContext.xml"}) //运行器创建工厂需要读取的XML配置文件
public class UserServiceTest {
...
}
step3: 在Test类中为UserService属性注入@Autowired
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={SpringConfig.class})
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testFindUserList(){ //main方法启动器
List<User> userList = userService.findUserList();
for (User user : userList) {
System.out.println(user);
}
}
}
3 使用Spring+Mybatis整合实现转账操作
步骤1:添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
</dependencies>
步骤2: 准备相关的配置文件
applicationContext.xml
db.properties
logback.xml
步骤 3: 创建包dao,entity,添加实体类Account
public class Account {
private Integer id;
private String userName;
private Integer money;
...
}
步骤4:编写AccountService,AccountDao,JUnit整合后的测试类
/**
* 业务测试类-模拟表示层
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) //启动时自动基于XML配置文件创建 Spring工厂
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
Integer result = accountService.transfer("jack", "tom", 100);
System.out.println(result==1 ? "OK" :"Error");
}
}
---------------------------------------------------------------------------------
/**
* 业务实现类
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账业务方法实现
* @param sourceName
* @param targetName
* @param money
* @return
*/
public Integer transfer(String sourceName, String targetName, Integer money) {
try {
//根据帐户名查询sourceAccount和targetAccount对象
Account sourceAccount = accountDao.getAccountByName(sourceName);
Account targetAccount = accountDao.getAccountByName(targetName);
//根据转账金额修改余额
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新两个账户
accountDao.updateAccount(sourceAccount);
int i=3/0;
accountDao.updateAccount(targetAccount);
return 1;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
---------------------------------------------------------------------------------
import com.woniu.entity.Account;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface AccountDao {
@Select("select * from account where userName=#{userName}")
Account getAccountByName(String userName);
@Update("update account set money=#{money} where userName=#{userName}")
void updateAccount(Account account);
}
结论:使用Spring整合mybatis之后,默认是sql语句自动提交,也就没有开启事务处理的,会导致一个转账内部出现数据不一致.
4 转账案例-问题解决
- 让业务层来控制事务的提交和回滚
- 先使用手动控制事务
- 手动控制连接对象
- 添加Dao实现类
步骤1:将applicationContext.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">
<context:component-scan base-package="com.woniu" />
<!--加载db.properties属性文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!--数据源配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="{{jdbc.driverClass}"/>
<property name="url" value="{{jdbc.url}"/>
<property name="username" value="{{jdbc.username}"/>
<property name="password" value="{{jdbc.password}"/>
<!--关于连接池相关配置-->
<property name="initialSize" value="{{druid.initalSize}"/>
<property name="maxActive" value="{{druid.maxActive}" />
<property name="minIdle" value="{{druid.minIdel}" />
</bean>
</beans>
步骤2: 添加AccountDao接口手动实现类
/**
* 持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private ConnectionUtil connectionUtil;
public Account getAccountByName(String userName) {
Account account = null;
try {
String sql = "select * from account where userName=?";
Connection conn = connectionUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userName);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
account = new Account();
account.setId(rs.getInt(1));
account.setUserName(rs.getString(2));
account.setMoney(rs.getInt(3));
}
return account;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public int updateAccount(Account account) {
try {
String sql = "update account set money=? where userName=?";
Connection conn = connectionUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, account.getMoney());
pstmt.setString(2, account.getUserName());
int rows = pstmt.executeUpdate();
return rows;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return 0;
}
}
步骤3:定制ConnectionUtil工具类,并定义ThreadLocal本地线程变量,确保同一个业务操作获取Connection是同一个
/**
* 管理Connection连接对象
*/
@Component
public class ConnectionUtil {
@Autowired
private DataSource dataSource;
private ThreadLocal<Connection> conns = new ThreadLocal<>();
/**
* 获取连接对象
* @return
*/
public Connection getConnection() {
Connection conn = conns.get();
if (conn == null) {
try {
conn = dataSource.getConnection();
conns.set(conn); //设置到ThreadLocal本地变量中的Map中
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return conn;
}
/**
* 关闭连接
* @param conn
*/
public void closeConnection(Connection conn){
if(conn!=null){
try {
conn.close();
conns.remove(); //移除Map中的变量conn
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
步骤4: 添加事务管理器TransactionManager类
/**
* 事务管理器
*/
@Component
public class TransactionManager {
@Autowired
private ConnectionUtil connectionUtil;
//开启事务
public void beginTran(){
try {
System.out.println("begin Trans...");
connectionUtil.getConnection().setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//提交事务
public void commit(){
try {
System.out.println("commit Trans...");
connectionUtil.getConnection().commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//回滚事务
public void rollback(){
try {
System.out.println("rollback Trans...");
connectionUtil.getConnection().rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//释放资源
public void release(){
System.out.println("release...");
connectionUtil.closeConnection(connectionUtil.getConnection());
}
}
步骤5:更新业务层代码,添加事务管理
/**
* 业务实现类
*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager transactionManager;
/**
* 转账业务方法实现
* @param sourceName
* @param targetName
* @param money
* @return
*/
public Integer transfer(String sourceName, String targetName, Integer money) {
try {
//开启事务
transactionManager.beginTran();
//根据帐户名查询sourceAccount和targetAccount对象
Account sourceAccount = accountDao.getAccountByName(sourceName);
Account targetAccount = accountDao.getAccountByName(targetName);
//根据转账金额修改余额
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新两个账户
accountDao.updateAccount(sourceAccount);
int i=3/0; //发生异常
accountDao.updateAccount(targetAccount);
//提交
transactionManager.commit();
return 1;
} catch (Exception e) {
e.printStackTrace();
//回滚事务
transactionManager.rollback();
return 0;
}finally {
//释放资源
transactionManager.release();
}
}
}
5 新的问题解决--动态代理
- 事务处理问题 解决了,但是业务层代码过于臃肿,每个业务方法需要去处理事务[开启,提交,回滚,释放]
- 动态代理常用的有两种方式:
- JDK
- 被代理类必须是基于接口设计的目标类
- CGLIB - cglib.jar
- 没有实现接口的目标类
- 这里我们采用JDK动态代理
ServiceProxy:
@Component
public class ServiceProxy {
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
/**
* 获取AccountService代理对象
* @return
*/
@Bean("accountServiceProxy")
public AccountService getJdkProxy(){
return (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 代理对象调用业务方法时的统一处理
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnVal = null; //用于接收目标对象真实方法的返回值
try{
System.out.println("代理对象的开启...");
transactionManager.beginTran(); //开启
returnVal = method.invoke(accountService,args);
transactionManager.commit(); //提交
}catch(Exception e){
e.printStackTrace();
transactionManager.rollback();
}finally {
transactionManager.release();
}
return returnVal;
}
});
}
}
- 业务层去掉事务管理代码
public Integer transfer(String sourceName, String targetName, Integer money) {
//根据帐户名查询sourceAccount和targetAccount对象
Account sourceAccount = accountDao.getAccountByName(sourceName);
Account targetAccount = accountDao.getAccountByName(targetName);
//根据转账金额修改余额
sourceAccount.setMoney(sourceAccount.getMoney()-money);
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新两个账户
accountDao.updateAccount(sourceAccount);
int i=3/0; //发生异常
accountDao.updateAccount(targetAccount);
return 1;
}
- 测试类使用业务代理对象执行transfer()
/**
* 业务测试类-模拟表示层
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"}) //启动时自动基于XML配置文件创建 Spring工厂
public class AccountServiceTest {
@Autowired
@Qualifier("accountServiceProxy")
private AccountService accountService;
@Test
public void testTransfer(){
Integer result = accountService.transfer("jack", "tom", 100);
System.out.println(result==1 ? "OK" :"Error");
}
}