🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
AOP: aspect oriented programming 面向切面编程 分布于应用中多处的功能称为横切关注点,通过这些横切关注点在概念上是与应用的业务逻辑相分离的,但其代码往往直接嵌入在应用的业务逻辑之中。将这些横切关注点与业务逻辑相分离正是面向切面编程(AOP)所要解决的。切面实现了横切关注点的模块化 一句话概括:切面是跟具体业务无关的一类共同功能。 案例一、表演 1. 编写切面类 ~~~ @Component() public class Audience { // 表演之前 public void takeSeats() { System.out.println("The audience is taking their seats."); } // 表演之前 public void turnOffCellPhones() { System.out.println("The audience is turning off their cellphones"); } // 表演成功之后 public void applaud() { System.out.println("CLAP CLAP CLAP CLAP CLAP"); } // 表演失败之后 public void demandRefund() { System.out.println("Boo! We want our money back!"); } //表演后(finally) public void comment() { System.out.println("the audience is making comments"); } } ~~~ 2. 编写被代理类(方法一,被代理类必须实现接口) ~~~ public interface Performer { void perform(); } ~~~ ~~~ @Component() public class Instrumentalist implements Performer { private String song = "my love"; public void perform() { System.out.print("Playing " + song + " : "); } } ~~~ XML配置 aspect: 切面 pointcut:切入点(切面和被代理类的结合) advice:通知(前置通知,后置通知,例外通知,最终通知) ~~~ <aop:config> <aop:aspect ref="audience"><!-- 引用audience Bean --> <!-- 声明切入点 --> <aop:pointcut id="performance" expression="execution(* com.neuedu.model.aop.*.*(..))" /> <!-- 表演之前 --> <aop:before pointcut-ref="performance" method="takeSeats" /> <aop:before pointcut-ref="performance" method="turnOffCellPhones" /> <!-- 表演之后 --> <aop:after-returning pointcut-ref="performance" method="applaud" /> <!-- 表演失败之后 --> <aop:after-throwing pointcut-ref="performance" method="demandRefund" /> <aop:after pointcut-ref="performance" method="comment" /> </aop:aspect> </aop:config> ~~~ 编写测试类 ~~~ ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml"); Performer perfomer = (Performer)application.getBean("instrumentalist"); perfomer.perform(); ~~~ 编写被代理类(方法二:被代理类不实现接口) 1)这种方法需要额外引入cglib jar(spring4.x,自带cglib功能,不需要引入) 2)在xml中加入 <aop:aspectj-autoproxy proxy-target-class="true"/> 编写测试类 ~~~ ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml"); Instrumentalist perfomer = (Instrumentalist)application.getBean("instrumentalist"); perfomer.perform(); ~~~ 案例二、事务处理 使用切面之前 DBUtils类 ~~~ public class DBUtils { private static ThreadLocal<Connection> tl = new ThreadLocal<>(); static { //加载数据库驱动 try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void beginTransaction() { //1.得到数据库连接 Connection conn = getConnection(); //2.设置自动提交为false try { conn.setAutoCommit(false); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static Connection getConnection() { Connection conn = tl.get(); if(conn==null) { try { conn = DriverManager. getConnection("jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8", "root", "root"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } //放在本地线程 tl.set(conn); } return conn; } public static void commit() { //1.得到数据库连接 Connection conn = getConnection(); //2. 提交 try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void rollback() { //1.得到数据库连接 Connection conn = getConnection(); //2. 提交 try { conn.rollback(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void close() { //1.得到数据库连接 Connection conn = getConnection(); //2. 提交 try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } //3.把conn从tl中移除 tl.remove(); } } ~~~ Service类 ~~~ @Service public class AccountService { @Autowired AccountDAO accountDAO; public void transferMoney() { //1. 获得数据库连接,开启事务 DBUtils.beginTransaction(); try { accountDAO.deduct(); accountDAO.add(); DBUtils.commit(); } catch(Exception e) { DBUtils.rollback(); e.printStackTrace(); } finally { DBUtils.close(); } } public static void main(String[] args) { //1. 开启spring容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService)context.getBean("accountService"); accountService.transferMoney(); } } ~~~ DAO类 ~~~ @Repository public class AccountDAO { public void deduct() throws SQLException { //1. 获得连接 Connection conn = DBUtils.getConnection(); PreparedStatement ps = conn.prepareStatement("update account set balance = balance-10 where accountid =2"); ps.executeUpdate(); } public void add() throws SQLException { //1. 获得连接 Connection conn = DBUtils.getConnection(); PreparedStatement ps = conn.prepareStatement("update account set balance = balance+10 where accountid =1"); ps.executeUpdate(); } } ~~~ 使用切面之后: 切面类 ~~~ public TransactionManagerAspect { public void before() { //1. 获取数据库连接 Connection conn = DriverManager.getConnection(); //把conn放在本地线程中 } public void afterreturning() { //从本地线程获取当前connection //提交事务 conn.commit(); } public void afterthrowing() { //从本地线程获取当前connection conn.rollback(); } public void finally() { //从本地线程获取当前connection conn.close(); } } ~~~ Service类 ~~~ class MyService { public void test() { MyDAO myDAO = new MyDAO(); myDAO.deduct(); myDAO.add(); } } ~~~ DAO类实现不变 XML AOP配置 ~~~ <aop:config> <aop:aspect ref="transactionAspect"> <aop:pointcut expression="execution(* com.neuedu.model.service.AccountService2.*(..))" id="transactionpointcut"/> <aop:before method="before" pointcut-ref="transactionpointcut"/> <aop:after-returning method="afterReturning" pointcut-ref="transactionpointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="transactionpointcut"/> <aop:after method="after" pointcut-ref="transactionpointcut"/> </aop:aspect> </aop:config> ~~~ 环绕通知 案例一、表演 ~~~ @Component public class AroundAudience { public void watchPerformance(ProceedingJoinPoint joinpoint) { try { // 表演之前 System.out.println("The audience is taking their seats."); System.out.println("The audience is turning off their cellphones"); long start = System.currentTimeMillis(); // 执行被通知的方法 joinpoint.proceed(); // 表演之后 long end = System.currentTimeMillis(); System.out.println("CLAP CLAP CLAP CLAP CLAP"); System.out.println("The performance took " + (end - start) + " milliseconds."); } catch (Throwable t) { // 表演失败之后 System.out.println("Boo! We want our money back!"); } finally { System.out.println("leave.."); } } } ~~~ ~~~ <aop:config> <aop:aspect ref="aroundAudience"> <aop:pointcut id="performance" expression="execution(* com.neuedu.model.aop.*.*(..))" /> <aop:around method="watchPerformance" pointcut-ref="performance"/> </aop:aspect> </aop:config> ~~~ 案例二、日志 ~~~ @Component public class LogAspect { public void around(ProceedingJoinPoint joinpoint) { try { //前置通知 System.out.println(joinpoint.getTarget().getClass().getName()+" "+joinpoint.getSignature().getName()+"开始运行"); long start = System.currentTimeMillis(); // 执行被通知的方法 joinpoint.proceed(); // 后置通知 long end = System.currentTimeMillis(); System.out.println(joinpoint.getTarget().getClass().getName()+" "+joinpoint.getSignature().getName()+"方法运行了"+ (end - start) + " milliseconds."); } catch (Throwable t) { //例外通知 System.out.println(t.getMessage()); } finally { //最终通知 System.out.println("方法运行结束"); } } } ~~~ ~~~ @Service public class MyService { public void test1() { System.out.println("test1"); } public void test2() { System.out.println("test2"); } } ~~~ ~~~ <aop:config> <aop:aspect ref="logAspect"> <aop:pointcut expression="execution(* com.neuedu.model.service.*.*(..))" id="logpointcut"/> <aop:around method="around" pointcut-ref="logpointcut"/> </aop:aspect> </aop:config> ~~~ <aop:advisor> 顾问 Advisor表示只有一个通知和一个切入点的切面,由于Spring AOP都是基于AOP的拦截器模型的环绕通知的,所以引入Advisor来支持各种通知类型(如前置通知等5种),Advisor概念来自于Spring1.2对AOP的支持 ~~~ @Component public class MyAdvice implements MethodInterceptor{ @Override public void around(Method arg0, Object[] arg1, Object arg2) throws Throwable { System.out.println("hahaha before advice"); } } ~~~ AOP配置 ~~~ <aop:config> <aop:pointcut id="performance2" expression="execution(* com.neuedu.model.aop.*.*(..))" /> <aop:advisor advice-ref="myAdvice" pointcut-ref="performance2"/> </aop:config> ~~~ 以上配置可以进一步简化为: ~~~ <aop:config> <aop:advisor advice-ref="myAdvice" pointcut="execution(* com.neuedu.model.aop.*.*(..))"/> </aop:config> ~~~ 5种通知实现的接口类型: MethodBeforeAdvice(前置) AfterReturningAdvice(后置) MethodInterceptor(环绕) ThrowsAdvice(例外) AfterAdvice(最终) 除了在进行事务控制的情况下,其他情况一般不推荐使用该方式,该方式属于侵入式设计,必须实现通知API