AOP英文名为Aspect Oriented Programming,意为面向切面编程,通过预编译方式和运行期间动态代理实现程序功能统一维护的一种技术。AOP是OOP的延续,是Spring框架中的一个重要内容,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:
AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
应用场景:
事务管理
记录日志
监测性能(统计方法运行时间)
权限控制
缓存
主要目的:
将日志记录,性能统计,安全控制,事务处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
**Joinpoint(连接点)😗*所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点.
**Pointcut(切入点)😗*所谓的切入点是指我们要对哪些Joinpoint进行拦截的定义
**Advice(通知/增强)😗*所谓通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的事情)
**Introduction(引介)😗*引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态的添加一些方法或Field
Target代理的目标对象
**Weaving(织rget(目标):入)😗*是指把增强应用到目标对象来创建的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
**Proxy(代理)😗*一个类被AOP织入增强后,就产生一个结果代理类
**Aspect(切面)😗*是切入点和通知(引介)的结合
建议采用图解方式理解:
Spring AOP通知分类:
通知类型 | 说明 |
---|---|
before(前置通知) | 通知方法在目标方法调用之前执行 |
after(后置通知) | 通知方法在目标方法返回或异常后调用 |
after-returning(返回后通知) | 通知方法会在目标方法正常执行,返回后调用 |
after-throwing(抛出异常通知) | 通知方法会在目标方法抛出异常后调用 |
around(环绕通知) | 通知方法会将目标方法包装起来 |
org.springframework spring-aop 5.2.15.RELEASE org.springframework spring-aspects 5.2.15.RELEASE org.aspectj aspectjweaver 1.9.6
编码
编写各种通知的日志记录类
在所有增强方法中,都可以接受JoinPoint类型连接点参数对象,可以获取当前增强是哪个类的哪个方法,还可以获取调用目标方法传递参数
package org.aop.aspectJ.logger;import java.sql.SQLException;
import java.util.Arrays;import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
@Slf4j
public class UserBizLogger {/*** 前置增强* @param jp*/public void before(JoinPoint jp){log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));}/*** 后置增强* @param jp* @param result 返回值*/public void afterReturning(JoinPoint jp,Object result){log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,方法的返回值:"+result);}/*** 异常增强* @param jp* @param e 增强的异常类型*/public void afterThrowing(JoinPoint jp,SQLException e){log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法发生异常"+e);}/*** 最终增强* @param jp*/public void after(JoinPoint jp){log.info(jp.getSignature().getName()+"方法执行结束");}/*** 环绕增强* @param jp* @throws Throwable*/public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{log.info(jp.getSignature().getName()+"方法开始执行");try {//执行目标方法Object rs = jp.proceed();log.info(jp.getSignature().getName()+"方法正常执行完");return rs;} catch (SQLException e) {log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法发生异常"+e);throw e;}}
}
配置切入点和切面
引入aop命名空间:
配置:
切入点说明
execution(修饰符? 返回值 方法名(参数) 异常?)
execution(* *(…)) 匹配所有spring管理对象所有方法, 第一个*任意返回值 ,第二个*任意方法, … 任意参数
execution(* org.suke.spring…*.*(…) ):匹配org.suke.spring包及其子包所有类的所有方法
execution(* org.suke.spring.*.*(…)) 匹配org.suke.spring包中所有对象所有方法
execution(* org.suke.spring.UserService.s*(…)) 匹配org.suke.spring包UserService中s开头方法
execute( public * addUser(entity.User)) 匹配addUser方法,返回值任意,参数为entity包User对象
execute( public void *(entity.User)) 匹配返回值为void,参数为entity包User的所有方法
execute( public void addUser(…)) 匹配返回值为void,参数任意的addUser方法
测试效果:
编写增强类:
package org.aop.aspectJ.logger;import java.sql.SQLException;
import java.util.Arrays;import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;@Aspect //把该类定义为切面类
public class UserBizLogger {private static final Logger log =Logger.getLogger(UserBizLogger.class);/*** 前置增强* @param jp*/@Before("execution( * org.aop.service..*.*(..))") //使用@Before 定义该方法为前置增强public void before(JoinPoint jp){log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,传递的参数:"+Arrays.toString(jp.getArgs()));}/*** 后置增强* @param jp* @param result 返回值*///使用@AfterReturning 定义该方法为后置增强方法@AfterReturning(pointcut="execution( * org.aop.service..*.*(..))",returning="result")public void afterReturning(JoinPoint jp,Object result){log.info("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法,方法的返回值:"+result);}/*** 异常增强* @param jp* @param e 增强的异常类型*///使用@AfterThrowing 定义该方法为异常增强@AfterThrowing(pointcut="execution( * org.aop.service..*.*(..))",throwing="e")public void afterThrowing(JoinPoint jp,SQLException e){log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法发生异常"+e);}/*** 最终增强* @param jp*///使用@After 定义该方法为最终增强@After("execution( * org.aop.service..*.*(..))")public void after(JoinPoint jp){log.info(jp.getSignature().getName()+"方法执行结束");}/*** 环绕增强* @param jp* @throws Throwable*///使用@Around 定义该方法为环绕增强@Around("execution( * org.aop.service..*.*(..))")public Object aroundLogger(ProceedingJoinPoint jp) throws Throwable{log.info(jp.getSignature().getName()+"方法开始执行");try {//执行目标方法Object rs = jp.proceed();log.info(jp.getSignature().getName()+"方法正常执行完");return rs;} catch (SQLException e) {log.error("调用"+jp.getTarget()+"的"+jp.getSignature().getName()+"方法发生异常"+e);throw e;}}
}
配置:
由于Spring框架提供了对象的管理,切面编程等非常实用的功能,如果把mybatis的对象交给Spring容器进行解偶合管理,不仅能大大增强系统的灵活性,便于功能扩展,还能通过Spring提供的服务简化编码,减小开发工作量,提高开发效率.实现整合的主要工作就是把Mybatis中的对象配置到Spring容器中,交给Spring来管理.比如说Mapper对象,以及业务层的事物管理。
导入相关依赖
4.0.0 org.example spring-webdemo 1.0-SNAPSHOT war spring-webdemo Maven Webapp http://www.example.com UTF-8 1.8 1.8 5.2.15.RELEASE org.springframework spring-context ${spring.version} org.springframework spring-test ${spring.version} org.springframework spring-web ${spring.version} javax.servlet javax.servlet-api 3.1.0 provided javax.servlet.jsp jsp-api 2.1 provided junit junit 4.12 test org.slf4j slf4j-log4j12 1.7.36 org.projectlombok lombok 1.18.24 javax.inject javax.inject 1 org.springframework spring-aop 5.3.21 org.springframework spring-aspects 5.3.21 org.springframework spring-jdbc 5.3.21 org.springframework spring-tx 5.3.21 org.mybatis mybatis 3.5.7 org.mybatis mybatis-spring 2.0.6 mysql mysql-connector-java 8.0.29 com.mchange c3p0 0.9.5.2 org.aspectj aspectjweaver 1.9.6 com.alibaba fastjson 1.2.75 spring-webdemo maven-clean-plugin 3.1.0 maven-resources-plugin 3.0.2 maven-compiler-plugin 3.8.0 maven-surefire-plugin 2.22.1 maven-war-plugin 3.2.2 maven-install-plugin 2.5.2 maven-deploy-plugin 2.8.2
编写User实体类和UserMapper接口,以及UserService,UserServlet
package com.suke.pojo;
import lombok.Data;
@Data
public class User {private Integer id;private String name;private String gender;private Integer age;private String address;private String email;private String qq;private String photo;}
package com.suke.mapper;import com.suke.pojo.User;public interface UserMapper {public void insert(User user);public void delete(int id);public User selectById(int id);
}
package com.suke.service;import com.suke.pojo.User;public interface UserService {public void addUser(User user);public void deleteUser(Integer uid);public User queryById(Integer uid);
}
package com.suke.service.impl;import com.suke.mapper.UserMapper;
import com.suke.pojo.User;
import com.suke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;@Service("userService")
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic void addUser(User user) {userMapper.insert(user);}@Overridepublic void deleteUser(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}userMapper.delete(uid);}@Overridepublic User queryById(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}return userMapper.selectById(uid);}
}
package com.suke.web;import com.alibaba.fastjson.JSON;
import com.suke.pojo.User;
import com.suke.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;@WebServlet(name = "UserServlet",value = "/userServlet")
public class UserServlet extends HttpServlet {protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {}protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());UserService userService = applicationContext.getBean("userService", UserService.class);int id = Integer.parseInt(request.getParameter("id"));User user = userService.queryById(id);response.setContentType("application/json;charset=UTF-8");PrintWriter out = response.getWriter();String json = JSON.toJSONString(user);out.print(json);out.flush();out.close();}
}
log4j.properties
log4j.rootCategory=DEBUG, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n
配置SqlSessionFactory
使用Spring整合Mybatis,首先应将诸如JDBC DataSource 或者MyBatis的SqlSessionFactory等数据访问资源以Bean的形式定义在Spring容器中,交由Spring容器进行管理.
1)把jdbc的参数配置到一个properties的文件,便于修改:
db.properties
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.user=root
jdbc.password=123#当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3
jdbc.acquireIncrement=5
#初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3
jdbc.initialPoolSize=5
#连接池中保留的最小连接数。
jdbc.minPoolSize=3
#连接池中保留的最大连接数。Default: 15
jdbc.maxPoolSize=10
因为我们在spring的配置文件需要配置大量的bean,为了方便管理,我们把spring的配置文件拆分成几个文件: applicationnContext-mapper.xml和applicationnContext-tx.xml, 然后在applicationnContext.xml使用import导入上面两个配置文件:
然后在 applicationnContext-mapper.xml配置与mybatis相关的配置:
我们导入mybatis的主配置文件,虽然我们不会在该配置文件添加配置,但是还是推荐保留:
mybatis-config.xml
编写UserMapper.xml的sql映射文件
SELECT LAST_INSERT_ID() insert into tb_userinfo(name,gender,age,address,email,qq,photo)values(#{name,jdbcType=VARCHAR},#{gender,jdbcType=VARCHAR},#{age,jdbcType=INTEGER},#{address,jdbcType=VARCHAR},#{email,jdbcType=VARCHAR},#{qq,jdbcType=VARCHAR},#{photo,jdbcType=VARCHAR})delete from tb_userinfo where id = #{id}
在applicationContext-tx.xml配置声明式事务管理
- PlatformTransactionManager 接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。
注意:
PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc
或 mybatis 时:org.springframework.jdbc.datasource.DataSourceTransactionManager
Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager
- TransactionDefinition
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
- TransactionStatus
TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。
Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。
声明式事务处理的作用
事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
声明式事务底层就是AOP
注意:我们可以通过tx:attributes子标签定制事务属性.事务属性通过tx:method标签进行设置,Spring支持对不同的方法设置不同的事务属性, tx:method标签中的name是必须的用于指定匹配的方法,这里需要对方法名进行约定,可以使用通配符 ( * ).其他属性均可选,用于指定具体的事务机制,这些属性解释如下:
注意:在实际开发中, REQUIRED能够满足大多数的事务需求,可以作为首选的事务传播行为.
编写测试类
package com.suke.service;import com.suke.pojo.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import static org.junit.Assert.*;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class UserServiceTest {@Autowiredprivate UserService userService;@Testpublic void addUser() {User user = new User();user.setName("李琦");user.setGender("男");user.setAge(21);user.setAddress("北京");user.setEmail("liqi@163.com");user.setQq("2312311");userService.addUser(user);System.out.println(user.getId());}@Testpublic void deleteUser() {userService.deleteUser(6);}
}
在业务实现类的方法上添加@Transactional
注解进行事务控制
package com.suke.service.impl;import com.suke.mapper.UserMapper;
import com.suke.pojo.User;
import com.suke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Override@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) {userMapper.insert(user);}@Override@Transactional(propagation = Propagation.REQUIRED)public void deleteUser(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}userMapper.delete(uid);}@Override@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)public User queryById(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}return userMapper.selectById(uid);}
}
编写 applicationContext.xml 配置文件
e(“李琦”);
user.setGender(“男”);
user.setAge(21);
user.setAddress(“北京”);
user.setEmail(“liqi@163.com”);
user.setQq(“2312311”);
userService.addUser(user);
System.out.println(user.getId());
}@Testpublic void deleteUser() {userService.deleteUser(6);}}```
在业务实现类的方法上添加@Transactional
注解进行事务控制
package com.suke.service.impl;import com.suke.mapper.UserMapper;
import com.suke.pojo.User;
import com.suke.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;@Service("userService")
@Transactional
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Override@Transactional(propagation = Propagation.REQUIRED)public void addUser(User user) {userMapper.insert(user);}@Override@Transactional(propagation = Propagation.REQUIRED)public void deleteUser(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}userMapper.delete(uid);}@Override@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)public User queryById(Integer uid) {if(uid == null){throw new RuntimeException("id不能为空!");}return userMapper.selectById(uid);}
}
编写 applicationContext.xml 配置文件