AOP
aop:面向切面编程,通过预编译和运行期动态代理,实现在不修改程序源代码的基础上统一动态和给程序添加某些功能的技术。
相关术语
通知(advice):代理对象的方法。分为前置通知,最终通知,返回通知,异常通知,环绕通知五类。以反射执行目标对象方法为基准:
- 前置通知:在目标对象方法执行之前执行;
- 最终通知:在目标对象方法执行完成后,无论在执行过程中是否出现异常,都会执行;
- 异常通知:目标对象方法执行过程中产生异常后执行;
- 返回通知:目标对象方法执行过程中不产生异常时执行;
环绕通知:特殊的通知,不以目标对象方法为基准,可以同时实现前置和后置通知。环绕通知保留了调度目标对象原有方法的功能,也就是说,使用环绕通知甚至可以不必要调用目标对象方法,可以取代目标对象的方法。因此环绕通知非常强大,而且灵活,但可控性差,一般不需要大量改变业务逻辑的情况下,不会使用环绕通知。
**织入(weaving)**:将通知组合到目标的过程。<br /> **连接点(join point)**:能被代理对象增强的方法。<br /> **切入点(pointcut)**:真正被增强的方法。<br /> 注意:只有被切入点表达式匹配的连接点,才能成为切入点。切入点一定是连接点,而连接点不一定是切入点,因为连接点有可能不需要进行增强。<br /> **切面(aspect)**:通知与切点的结合。
XML实现AOP
1.导入依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.7</version></dependency>
2.编写通知类
package com.woniuxy.interceptor;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;/*** 进行事务管理的类*/public class TransactionManager {public void beginTransaction(){System.out.println("开启事务");}public void commit(){System.out.println("提交事务");}public void rollback(){System.out.println("回滚事务");}public void close() {System.out.println("释放资源");}}
3.编写核心配置文件(引入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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!----><bean id="userService" class="com.woniuxy.service.impl.UserServiceImpl"/><!--配置通知--><bean id="transactionManager" class="com.woniuxy.interceptor.TransactionManager"></bean><!--aop相关的配置--><aop:config><!--配置切入点访问修饰符 方法返回值 方法名(参数列表)--><aop:pointcut id="pointCut" expression="execution(* com.woniuxy.service.impl.*.*(..))"/><!--配置切面--><aop:aspect ref="transactionManager"><aop:before method="beginTransaction" pointcut="execution(* com.woniuxy.service.impl.*.*(..))"/><aop:after-returning method="commit" pointcut-ref="pointCut"/><aop:after-throwing method="rollback" pointcut-ref="pointCut"/><aop:after method="close" pointcut-ref="pointCut"/></aop:aspect></aop:config></beans>
注解实现AOP
1.导入依赖
2.创建配置类
@Configuration@EnableAspectJAutoProxy@ComponentScan(basePackages = {"com.woniuxy"})public class ApplicationConfig {}
3.修改通知类
/*** 进行事务管理的类*/@Component@Aspectpublic class TransactionManager {@Pointcut("execution(* com.woniuxy.service.impl.*.*(..))")public void pointCut(){}@Before("pointCut()")//前置通知public void beginTransaction(){System.out.println("开启事务");}@AfterReturning("pointCut()")//返回通知public void commit(){System.out.println("提交事务");}@AfterThrowing("pointCut()")//异常通知public void rollback(){System.out.println("回滚事务");}@After("pointCut()")//最终通知public void close() {System.out.println("释放资源");}}
QueryRunner
使用queryRunner解决对象引用的问题
1、导入依赖 commons-dbutils
<dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version></dependency>
2、将queryrunner对象交由容器管理
@Bean//通过Bean注解将QueryRunner添加进spring容器public QueryRunner queryRunner(){return new QueryRunner();}
3、dao层注入queryrunner,并使用该对象进行增删改查
@Repository("accountDao")//控制层注解public class AccountDaoImpl implements AccountDao {//使用一个工具QueryRunner commons-dbutils//JdbcTemplate 也可以操作数据库@Resource//属性注入private ConnectionUtil connectionUtil;@Resourceprivate QueryRunner queryRunner;@Overridepublic Account findByUsername(String username) throws SQLException {//参数1:数据库连接 参数2:要执行的sql语句 参数3:查询结果使用哪种类型进行封装 参数4:sql语句执行时,需要填充的参数//query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)Account account = queryRunner.query(connectionUtil.getConnection(),"select * from account where username=?",new BeanHandler<Account>(Account.class), username);return account;}@Overridepublic int updateAccount(Account account) throws SQLException {int i = queryRunner.update(connectionUtil.getConnection(),"update account set balance=? where username=?",account.getBalance(), account.getUsername());return i;}}
环绕通知
通过环绕通知可以解决spring5.2.6及以下版本中,使用注解实现aop时出现通知执行顺序问题(前置->最终->返回或异常),5.2.7版本及以上,这个bug已被修复。
@Component@Aspectpublic class TransactionManager {//真正的去完成事务控制,不能再使用伪代码@Resourceprivate ConnectionUtil connectionUtil;@Pointcut("execution(* com.woniuxy.service.impl.*.*(..))")public void pointCut(){}// @Before("pointCut()")public void beginTransaction(){try {connectionUtil.getConnection().setAutoCommit(false);System.out.println("事务开启");} catch (SQLException e) {e.printStackTrace();}}// @AfterReturning("pointCut()")public void commit(){try {connectionUtil.getConnection().commit();System.out.println("事务提交");} catch (SQLException e) {e.printStackTrace();}}// @AfterThrowing("pointCut()")public void rollback(){try {connectionUtil.getConnection().rollback();System.out.println("事务回滚");} catch (SQLException e) {e.printStackTrace();}}// @After("pointCut()")public void close(){try {connectionUtil.close();} catch (Exception e) {e.printStackTrace();}}//环绕通知和四大通知的方式任选其一@Around("pointCut()")//环绕通知的注解public Object around(ProceedingJoinPoint pjp){Object[] args = pjp.getArgs();//获取真实对象方法的参数列表Object obj = null;try {//开启事务beginTransaction();obj = pjp.proceed(args);//类似于动态代理中的method.invoke(),是在调用真实对象的方法//提交事务commit();} catch (Throwable throwable) {throwable.printStackTrace();//回滚事务rollback();} finally {//释放资源close();}return obj;}}
声明式事务
spring提供了声明式事务的接口PlatformTransactionManager接口,使用其实现类DatasourceTransactionManager即可实现声明式事务。
声明式事务中不关心事务开启和资源释放,只关心commit和rollback。
xml实现声明式事务
使用XML实现spring声明式事务的步骤:
0.导入依赖
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.8.RELEASE</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.22</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.43</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.18</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</version></dependency></dependencies>
- 配置事务管理器;
- 配置事务通知:为业务层方法配置事务,并设置事务属性;
- 配置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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><!--配置数据访问层实现类--><bean id="accountDao" class="com.woniuxy.dao.impl.AccountDaoImpl"><property name="jdbcTemplate" ref="jdbcTemplate"/></bean><!--配置业务层实现类--><bean id="accountService" class="com.woniuxy.service.impl.AccountServiceImpl"><property name="accountDao" ref="accountDao"/></bean><!--配置数据库连接池--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///bank?characterEncoding=UTF-8&useSSL=false"/><property name="username" value="root"/><property name="password" value="root"/></bean><!--配置jdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><property name="dataSource" ref="dataSource"/></bean><!-- 配置事务管理器 --><bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--配置事务通知--><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!--isolation:设定事务隔离级别,用于保证数据的完整性。DEFAULT,默认使用数据库的事务隔离级别READ_UNCOMMITTED,可读取未提交数据,有可能出现脏读、不可重复读、幻读READ_COMMITTED,可读取已提交数据,有可能出现不可重复读、幻读REPEATABLE_READ,读取的数据表被加行锁,有可能出现幻读SERIALIZABLE,读取的数据表加表锁,安全程度最高propagation:当被事务控制的业务方法进行相互调用时,设定事务的传播行为。REQUIRED,默认,之前的操作中有事务时加入该事务,没有事务时创建一个事务(增删改);SUPPORTS,之前的操作中有事务时加入该事务,没有事务时不使用事务(查询)MANDATORY,必须在事务内部执行,没有事务就报异常REQUIRES_NEW,将原有事务挂起,新建一个事务执行自己的操作,两个事务之间没有关联NOT_SUPPORTED,必须在非事务内部执行,如果有事务存在,将事务挂起,执行自己的操作NEVER,不能在事务内部执行,有事务就报异常NESTED,之前的操作有事务时,创建一个嵌套事务,两个事务之间会产生关联read-only:设定事务是否只读。默认false,读写(增删改),true,只读(查询)timeout:设定事务的超时时间,默认-1,永不超时,设定数值时,以秒为单位计算no-rollback-for:设定一个异常,事务执行过程中出现该异常时不回滚,其它异常会回滚,不设默认全回滚rollback-for:设定一个异常,事务执行过程中出现该异常时回滚,其它异常不会回滚,不设默认全回滚建议手动抛异常时设定该属性--><tx:method name="*" propagation="REQUIRED" read-only="false"/><tx:method name="find*" propagation="SUPPORTS" read-only="true"/></tx:attributes></tx:advice><!-- 配置aop --><aop:config><aop:pointcut id="pt" expression="execution(* com.woniuxy.service.impl.*.*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/></aop:config><!--声明式事务的实现步骤:1、导入依赖 spring-tx(必须) spring-context(核心容器+aop) spring-jdbc mysql驱动 druid2、编写核心配置文件2.1配置事务管理器:datasourcetransactionmanager2.2配置事务通知:tx:advice transaction-manager属性引用配置好的事务管理器tx:attributestx:method name="*" propagation="REQUIRED" read-only="false" 给增删改的业务层方法使用tx:method name="find*" propagation="SUPPORTS" read-only="true" 给业务层的查询方法使用2.3配置aop:配置切入点配置tx:advisor 引用事务通知 引用切入点--></beans>
常用注解
| 注解名 | 作用 |
|---|---|
| @Configuration | 用在配置类上 |
| @Component | 无语义的类上 |
| @Service | 用在业务层实现类上 |
| @Repository | 用在数据访问层实现类上 |
| @Controller | 用在控制层的类上 |
| @Value | 用于给类的属性赋值 |
| @Autowired | 根据类型自动注入,用于进行自动注入的成员变量上 |
| @Resource | 先byName,再byType。用于进行自动注入的成员变量上 |
| @Primary | 用于在要优先注入的类上 |
| @Qualifier | 用于在成员变量上,用于指定注入一个容器中的bean |
| @Bean | 用于将第三方的类注册到spring容器中,用在某个方法上,将方法返回值注册到容器 |
| @ComponentScan | 用在配置类上,用于指定扫描的包 |
| @PropertySource | 用于在配置类上,用于加载类路径下的properties文件 |
| @Scope | 用在类上,用于指定当前类的作用范围,一般用于配置多例或单例 |
| @Aspect | 用在切面类上, |
| @PointCut | 用在方法上,用于配置切入点表达式 |
| @Before@After@AfterReturning@AfterThrowing@Around | 用在切面类的方法上,五大通知 |
| @EnableAspectJAutoProxy | 用在配置类上,用于开启aspectj的自动代理(识别aspectj的注解) |
| @EnableTransactionManagement | 用在配置类上,用于开启注解事务 |
| @Transactional | 用在业务层实现类上或实现类的方法上,用于指定事务策略 |
| @Import | 用在配置类上,用于导入另外一个配置类 |
注解实现声明式事务
1、编写配置类
@Configuration@EnableTransactionManagement//开启注解事务管理@ComponentScan(basePackages = {"com.woniuxy"})public class AppConfig {@Bean("dataSource")public DataSource dataSource(){DruidDataSource dataSource = new DruidDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql:///bank?characterEncoding=UTF-8&useSSL=false");dataSource.setUsername("root");dataSource.setPassword("root");return dataSource;}@Beanpublic JdbcTemplate jdbcTemplate(DataSource dataSource){JdbcTemplate jdbcTemplate = new JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}@Beanpublic DataSourceTransactionManager dataSourceTransactionManager(){DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();dataSourceTransactionManager.setDataSource(dataSource());return dataSourceTransactionManager;}}
2、使用@Transactional注解进行事务控制
@Transactional注解用在类上,表示类中所有方法使用同一种事务策略
也可以将其放在方法上,表示当前被注解的方法使用特定的事务策略。
@Service("accountService")@Transactionalpublic class AccountServiceImpl implements AccountService {@Resourceprivate AccountDao accountDao;@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)public Account findByUserName(String username){return null;}@Overridepublic void transfer(String source, String target, Double money) throws SQLException {//查询要进行转帐操作的两个人的帐户信息Account sourceAccount = accountDao.findByUsername(source);Account targetAccount = accountDao.findByUsername(target);//判断被转出人的帐户余额是否足够if (sourceAccount.getBalance()>money) {//真正执行转账操作sourceAccount.setBalance(sourceAccount.getBalance()-money);targetAccount.setBalance(targetAccount.getBalance()+money);accountDao.updateAccount(sourceAccount);// System.out.println(1/0);accountDao.updateAccount(targetAccount);}else{throw new RuntimeException("余额不足");}}}
spring整合mybatis
对于spring项目中整合mybatsi前置条件:导入spring本身的依赖,以及mybatis的依赖,还需要多导入一个整合mybatis与spring的依赖(mybatis提供的)。
1、导入依赖:spring-context spring-jdbc mybatis-spring mybatis mysql-connector-java druid lombok
2、编写spring的配置文件
a)扫描包下的注解(spring本身的注解)
b)配置数据库连接池(druid)
c)SqlSessionFactoryBean(注入一个datasource)
d)MapperScannerConfigurer 指定扫描的包(basepackage:扫描创建mapper接口的代理对象)
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"><!--扫包--><context:component-scan base-package="com.woniuxy"/><!--配置数据库连接池--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql:///bank?characterEncoding=utf-8&useSSL=false"/><property name="username" value="root"/><property name="password" value="root"/></bean><!--配置sqlSessionFactoryBean作用:产生一个sqlsessionfactory 只要ioc容器中有任何一个bean需要使用sqlsession,那么就会由sqlsessionfactory自动创建一个给其使用。--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><!-- <property name="configLocation" value="classpath:sqlConfig.xml"/>--></bean><!--配置MapperScannerConfigurer:作用:用于扫描指定的包,创建对应接口的代理对象--><bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="basePackage" value="com.woniuxy.mapper"/></bean></beans>

