Spring–AOP 面向切面编程
一.AOP介绍
为什么使用AOP?
-
java是oop面向对象的语言,OOP引入封装、继承和多态等概念来建立一种对象层次结构,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码一般都是水平地散布在所有对象层次中,但是它和对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理也是如此。这种代码就称为横切代码,在OOP设计中,它会导致了大量代码的重复,而不利于各个模块的重用。
-
AOP的核心就是代理,不用修改任何已经编写好的代码,只要使用代理就可以灵活的加入任何东西,将来不喜欢了,不用也不会影响原来的代码。
JDK动态代理
- 代理设计模式的原理: 使用一个代理将对象包装起来, 然后用该代理对象取代原始对象. 任何对原始对象的调用都要通过代理.代理对象决定是否以及何时将方法调用转到原始对象上。
- JDK动态代理开发
- 接口类
package com.wmy.spring.ao;
import java.math.BigDecimal;
public interface BankService {
public void transfer(String target, String source, BigDecimal money);
public String withdraw(String account, BigDecimal money);
public int saveMoney(String account,BigDecimal money);
}
- 接口实现类
package com.wmy.spring.ao;
import java.math.BigDecimal;
public class BankServiceImpl implements BankService{
public void transfer(String target, String source, BigDecimal money) {
}
public String withdraw(String account, BigDecimal money) {
return "AOP";
}
public int saveMoney(String account, BigDecimal money) {
return 100;
}
}
- 代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ServiceProxy implements InvocationHandler{
private Object target;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法:" + method.getName() + "被执行");
for(Object arg : args) {
System.out.println("参数:" + arg);
}
Object returnValue = method.invoke(target, args);
System.out.println("方法:" + method.getName() + "执行完毕,返回值:" + returnValue);
return returnValue;
}
public Object createProxy(Object target) {
this.target = target;
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
}
- 测试
package com.wmy.spring.ao;
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
BankService bs = new BankServiceImpl();
BankService bsProxy = (BankService) new ServiceProxy().createProxy(bs);
bsProxy.withdraw( "李四", new BigDecimal("500"));
bsProxy.saveMoney("张三", new BigDecimal("10000"));
}
}
AOP的优势
- 降低模块耦合度
- 使系统容易扩展
- 更好的代码复用性
AOP的术语
- 切面(Aspect)
一个横切关注点的模块化,这个关注点可能会横切多个对象。它是横切关注点的另一种表达方式。
- 连接点(Joinpoint)
在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。
- 切入点(Pointcut)
匹配连接点的断言。它通常是一个表达式,有专门的语法,用于指明在哪里(或者说在哪些方法调用上)嵌入横切逻辑
- 通知(Advice)
在切面的某个特定的连接点上执行的动作,也就是我们前面提到的横切逻辑,如日志处理逻辑,事务处理逻辑。
- 目标对象(Target Object)
被一个或者多个切面所通知的对象,也被称作被通知对象
- 代理对象(Proxy Object)
AOP框架创建的对象,它和目标对象遵循同样的接口,使用它的方式和使用目标对象的方式是一样的,但是它是目标对象的增强版,“通知”中的代码执行将会被代理对象的方法调用触发。在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
二.Spring AOP开发
方法一:利用xml
1. 相关jar包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2. xml配置文件
表达式:expression=“execution(* springboot.aop.xml. * .* (…))”
第一个表示不限制返回值类型
第二个 表示springboot.aop.xml包下所有的JAVA BEAN
第三个* javabean 所有的方法
(…) 表示对方法的参数没有限制
<aop:config>
<aop:pointcut expression="execution(* springboot.aop.xml.*.*(..))" id="loggerPointCut"/>
<aop:aspect ref="loggerAspect">
<aop:before method="logerBefore" pointcut-ref="loggerPointCut"/>
</aop:aspect>
</aop:config>
3. 业务代码
通知方法的编写
- 前置通知:在方法执行之前执行。
xml:
<aop:before method="logerBefore" pointcut-ref="loggerPointCut"/>
通知类java类:
public void logerBefore(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("method: " + methodName + "将要被执行!");
Object[] args = jp.getArgs();
for(Object arg : args) {
System.out.println("=============参数:>" + arg);
}
}
- 后置通知:在方法执行之后执行
xml:
<aop:after method="logerAfter" pointcut-ref="loggerPointCut"/>
通知类java类:
public void logerAfter(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("method: " + methodName + "已经被执行!");
Object[] args = jp.getArgs();
for(Object arg : args) {
System.out.println("=============参数:>" + arg);
}
}
- 返回通知:在连接点完成之后执行的, 即连接点返回结果或者抛出异常的时候。
属性 returning 的值必须与方法logerAfterReturning 返回值的返回值参数名保持一致
xml:
<aop:after-returning method="logerAfterReturning" pointcut-ref="loggerPointCut" returning="returnValue"/>
通知类java类:
public void logerAfterReturning(JoinPoint jp, Object returnValue) {
String methodName = jp.getSignature().getName();
System.out.println("后置返回通知 =========>method: " + methodName + "已经被执行!");
System.out.println("后置返回通知 返回值:" + returnValue);
}
- 异常通知, 在方法抛出异常之后
xml:
<aop:after-throwing method="loggerAfterThrowing" pointcut-ref="loggerPointCut" throwing="exception"/>
通知类java类:
public void loggerAfterThrowing(JoinPoint jp, Exception exception) {
System.out.println("后置异常通知:" + exception);
}
- 环绕通知
xml:
<aop:around method="logerAround" pointcut-ref="loggerPointCut"/>
通知类java类:
public Object logerAround(ProceedingJoinPoint pjp) {
String methodName = pjp.getSignature().getName();
System.out.println("环绕通知 =========>method: " + methodName + "将要被执行!");
Object[] args = pjp.getArgs();//獲取參數
args[0] = "小明";
try {
Object returnValue = pjp.proceed(args);
System.out.println("环绕通知 =========>method: " + methodName + "已经被执行返回值:! " + returnValue);
return new BigDecimal("999999999999999");
} catch (Throwable e) {
e.printStackTrace();
}
return null;
}
方法二:利用注解
1.spring配置文件
<context:component-scan base-package="com.zzxtit.spring.aop.anno"></context:component-scan>
<!-- 开启spring AOP 注解方式 自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.通知类java类编写
使用@Aspect 和@Component标记为切面的Spring Bean组件
- 前置通知
第一个 * 表示不限制返回值类型
第二个* 表示包下所有的JAVA BEAN
第三个* javabean 所有的方法
(…) 表示对方法的参数没有限制
@Before("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void beforeAdvice(JoinPoint jp) {
System.out.println("==================打印日志=====================");
Object[] args = jp.getArgs();
int index = 1;
for(Object arg : args) {
System.out.println("======第" + index +"个参数=======>" + arg);
index++;
}
System.out.println("方法:" + jp.getSignature().getDeclaringTypeName() + "." + jp.getSignature().getName());
}
- 后置通知
@After("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void afterAdvice(JoinPoint jp) {
System.out.println("-----------打印日志-----后置通知-----------------------------");
}
- 后置返回通知
@AfterReturning(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", returning="returnValue")
public void afterReturningAdvice(JoinPoint jp, Object returnValue) {
System.out.println("----------------后置返回通知-----------------------------" + returnValue);
}
- 环绕通知
@Around("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void aroundAdvice(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
returnValue = pjp.proceed();//执行目标方法
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("----------------环绕通知-----------------------------" + returnValue);
}
- 异常通知
@AfterThrowing(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", throwing="exception")
public void afterThrowing(JoinPoint jp, Exception exception) {
}
3.通过pointCut进行切入点的代码抽离
@Pointcut("execution(* com.zzxtit.spring.aop.anno.*.*(..))")
public void loggerPointCut() {
}
@After("loggerPointCut()")
public void afterAdvice(JoinPoint jp) {
System.out.println("-----------打印日志-----后置通知-----------------------------");
}
/**
* 如果指定 pointcut的值,将会覆盖value属性值
* @param jp
* @param returnValue
*/
@AfterReturning(value = "execution(* com.zzxtit.spring.aop.anno.*.*(..))", pointcut="loggerPointCut()", returning="returnValue")
public void afterReturningAdvice(JoinPoint jp, Object returnValue) {
System.out.println("----------------后置返回通知-----------------------------" + returnValue);
}
4. @Order指定切面的优先级
值从0开始,越小优先级越高。
@Order(1)
@Aspect
@Component
三.Spring JDBC开发
Jdbc的配置
<context:property-placeholder location="config/DB.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql_driver}"></property>
<property name="url" value="${mysql_url}"></property>
<property name="username" value="${mysql_username}"></property>
<property name="password" value="${mysql_passwd}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" value="#{dataSource}"></property>
</bean>
Java的Dao 层引用JdbcTemplate
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private JdbcTemplate jdbcTemplate;
}
- 增加
public void insertUserInfo(SysUserInfo su) {
String sql="insert into t_sys_user (user_name, passwd, salt, real_name, avatar, phone, email, gender, create_time) "
+ "values (?, ?, ?, ?, ?, ?, ?, ?, now())";
jdbcTemplate.update(sql, su.getUserName(), su.getPasswd(), su.getSalt(),
su.getRealName(), su.getAvatar(), su.getPhone(),
su.getEmail(), su.getGender());
}
- 删除
public void deleteUserInfo(SysUserInfo su) {
String sql = "delete from t_sys_user where passwd=?";
jdbcTemplate.update(sql, su.getPasswd());
}
- 修改
public void updateUserInfo(SysUserInfo su) {
String sql="update t_sys_user set user_name = ?, salt=?, real_name=?, avatar=?, phone=?, email=?, gender=?, create_time=? where passwd=?";
jdbcTemplate.update(sql, su.getUserName(), su.getSalt(),
su.getRealName(), su.getAvatar(), su.getPhone(),
su.getEmail(), su.getGender(),su.getPasswd());
}
- 查询
public SysUserInfo getSysUserById(int userId) {
String sql = "select * from t_sys_user where user_id = ?";
List<SysUserInfo> suList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<SysUserInfo>(SysUserInfo.class), userId);
if(suList != null && suList.size() > 0) {
return suList.get(0);
}else {
return null;
}
}
JDBC中具名参数的使用
1.xml配置
<bean id="namedJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg index="0" ref="dataSource"></constructor-arg>
</bean>
2.Dao层的使用
public void insertUserInfoByNJP(SysUser su) {
String sql = "insert into t_sys_user (user_name, passwd, salt, real_name, avatar, phone, "
+ "email, gender, create_time) values ({userName:}, {passwd:}, {salt:}, {realName:}, {avatar:}, {phone:}, {email:}, {gender:}, now())";
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("userName", su.getUserName());
paramMap.put("passwd", su.getPasswd());
paramMap.put("salt", su.getSalt());
paramMap.put("realName", su.getRealName());
paramMap.put("avatar", su.getAvatar());
paramMap.put("phone", su.getPhone());
paramMap.put("email", su.getEmail());
paramMap.put("gender", su.getGender());
npjTemplate.update(sql, paramMap);
}