Spring面试题(下)
文章目录
1.什么是面向切面编程(AOP)
- AOP:全称是Aspect Oriented Programming 即:面向切面编程
- 简单的说就是我们把重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的原有代码进行增强。
2.面向切面编程(AOP)的作用
- 在程序运行期间,不修改源代码对已有方法进行增强
- 减少重复代码
- 提高效率
- 维护方便
- 可用来做:日志记录、性能统计、安全控制、事务处理、异常处理
3.AOP的实现方式
- 使用动态代理技术
4.事务控制的场景
- 使用自动提交的方式来控制事务,在每次都只执行一条sql语句的时候,没问题,但是如果一个业务要执行多条sql这种方式就无法正确的实现事务控制。
- 例如经典的转账问题,使用自动事务控制就会产生A的账户已经扣钱了,B的账户却没加钱的情况。
- 我们可以自定义一个事务控制类,实现事务的开启、提交、回滚等操作。
- 但是使用这种办法业务层的代码会很臃肿。
5.如何实现AOP
-
使用JDK官方的Proxy类(基于接口的动态代理,要求被代理类至少实现一个接口)
-
使用第三方cglib的Enhancer类(基于子类的动态代理)
-
public class Client { public static void main(String[] args) { final Producer producer = new Producer(); IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),producer.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object ret = null; Float money = (Float) args[0]; if ("saleProduct".equals(method.getName())){ ret = method.invoke(producer,money*0.8f); } return ret; } }); proxyProducer.saleProduct(10000f); Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() { public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object ret = null; Float money = (Float) args[0]; if ("saleProduct".equals(method.getName())) { ret = method.invoke(producer, money * 0.8f); } return ret; } }); cglibProducer.saleProduct(10000f); } }
6.spring中实现通知
-
第一步:用bean标签注入通知类
-
第二步:使用aop:config 声明aop配置
-
第三步:使用aop:aspect 配置切面
-
第四步:使用aop:pointcut配置切入点表达式
-
第五步:使用aop:xxx 配置对应的通知类型(前置通知、后置通知、异常通知、最终通知)
-
<?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/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"> <!-- 配置spring的ioc,把service对象配置进来 --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!-- 配置AOP --> <aop:config> <!-- 配置切入点表达式 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.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:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>--> </aop:aspect> </aop:config> </beans>
-
也可以用注解的方法配置
-
<?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="com.itheima"></context:component-scan> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
package com.itheima.utils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.context.annotation.ComponentScan; import org.springframework.stereotype.Component; /** * @author chx * @version 1.0 * @description: TODO * * @date 2021/1/25 0025 9:59 */ @Component("logger") @Aspect public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){ } /** * 前置通知 */ @Before("pt1()") public void beforePrintLog(){ System.out.println("Logger类中的pringLog方法开始记录日志了。。。"); } /** * 后置通知 */ @AfterReturning("pt1()") public void afterReturningPrintLog(){ System.out.println("Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ @AfterThrowing("pt1()") public void afterThrowingPrintLog(){ System.out.println("Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ @After("pt1()") public void afterPrintLog(){ System.out.println("Logger类中的afterPrintLog方法开始记录日志了。。。"); } /** * 环绕通知 */ //@Around("pt1()") public Object aroundPrintLog(ProceedingJoinPoint pjp){ Object rtValue = null; try { Object[] args = pjp.getArgs(); System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed();//明确调用业务层方法 System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置"); return rtValue; } catch (Throwable throwable) { System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常"); throw new RuntimeException(throwable); }finally { System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终"); } } }
7.spring中基于AOP实现事务
-
编写获取连接的工具类,用于从数据源中获取一个连接并且实现和线程绑定
-
package com.itheima.utils; import javax.sql.DataSource; import java.sql.Connection; /** * @author chx * @version 1.0 * @description: TODO * 连接的工具类,它用于从数据源中获取一个连接并且实现和线程绑定 * @date 2021/1/24 0024 14:32 */ public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 */ public Connection getThreadConnection(){ //1.先从ThreadLocal上获取 Connection conn = tl.get(); try { //2.判断当前线程上是否有连接 if(conn==null){ //3.从数据源获取连接,并且和存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } } catch (Exception e) { e.printStackTrace(); } return conn; } /** * 解绑 */ public void removeConnection(){ tl.remove(); } }
-
编写事务管理相关的工具类
-
package com.itheima.utils; import java.sql.SQLException; /** * @author chx * @version 1.0 * @description: TODO * 事务管理相关的工具类 * @date 2021/1/24 0024 14:41 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); } catch (Exception e) { e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (Exception e) { e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); } catch (Exception e) { e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close(); connectionUtils.removeConnection(); } catch (Exception e) { e.printStackTrace(); } } }
-
修改spring配置文件
-
<?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/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"> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <bean id="accountDao" class="com.itheima.dao.impl.AccountDao"> <property name="runner" ref="runner"></property> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> </bean> <!-- 配置数据库 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property> <property name="user" value="root"></property> <property name="password" value="xxxx"></property> </bean> <!--配置Connection的工具类--> <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="txManageer" class="com.itheima.utils.TransactionManager"> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置aop--> <aop:config> <!--配置通用切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="txAdvice" ref="txManageer"> <!--配置前置通知:开启事务--> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <!--配置后置通知:提交事务--> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知:回滚事务--> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知:释放连接--> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
8.spring中实现声明式事务
-
第一步:配置事务管理器
-
第二步:配置事务通知(引用事务管理器)
-
第三步:配置事务的属性
-
第四步:配置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" xmlns:tx="http://www.springframework.org/schema/tx" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置业务层 --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></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/eesy"></property> <property name="username" value="root"></property> <property name="password" value="0000"></property> </bean> <!-- 配置账户的持久层 --> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- spring中基于xml的声明式事务控制配置步骤 1.配置事务管理器 2.配置事务的通知 3.配置AOP中的通用表达式切入点表达式 4.建立事务通知和切入点表达式的对应关系 5.配置事务的属性 --> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 指定方法名称:是业务核心方法 read-only:是否是只读事务。默认 false,不只读。 isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。 propagation:指定事务的传播行为。 timeout:指定超时时间。默认值为:-1。永不超时。 rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。 没有默认值,任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回 滚。没有默认值,任何异常都回滚。 --> <!-- 配置事务的通知 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <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="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/> <!-- 建立切入点表达式和事务通知的关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config> </beans>