Spring AOP
AOP案例引入
数据库事务说明
- 案例分析:
- userMapper.insert(User对象)
- deptMapper.insert(Dept对象)
- 说明:由于业务需求,要求方法要么同时入库,要么同时回滚,所以必须通过事务进行控制。
Spring实现事务控制(demo)
代码结构如下
编辑UserMapper/UserMapperImpl
-
编辑UserMapper
public interface UserMapper { void addUser(User user); }
-
编辑UserMapperImpl
@Repository public class UserMapperImpl implements UserMapper { //事务的控制应该在哪一层完成? dao/mapper service public void addUser(User user) { System.out.println("用户入库:" + user); } }
编辑UserService/UserServiceImpl
-
编辑UserService
public interface UserService { void addUser(User user); }
-
编辑UserServiceImpl
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; //事务的控制应该放到Service层中进行控制 public void addUser(User user) { try { System.out.println("事务开始"); userMapper.addUser(user); System.out.println("事务结束"); }catch (Exception e){ e.printStackTrace(); System.out.println("事务回滚"); } } }
编辑测试类
public class TestUser {
/**
* Spring中规定:
* 如果传入的是接口的类型,则自动查找/注入该接口的实现类
* 前提:该接口只有一个实现类
* 注入接口的原则:
* if(getBean(isinterface)){
* Class targetClass = interface.getImpl();
* //根据类型,动态获取对象
* return 对象;
* }
*/
@Test
public void testTx(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
// 向上造型
UserService userService = context.getBean(UserService.class);
// UserService userService = (UserService) context.getBean("userServiceImpl");
User user = new User();
user.setId(1111);
user.setName("SpringAOP入门案例");
userService.addUser(user);
}
}
代码问题分析
问题1:Service层:本应该代码业务处理即可,但是由于控制紧紧的耦合在一起
问题2:代码冗余,不便于大批量开发
解决方案:采用代理模式进行编辑
代理模式
生活中代理的案例
-
房屋中介代理模式:
- 房东:自己手里有房子,需要出租换钱
- 中介机构:
- 本职工作 带客户看房/出租房屋
- 收取中介费(服务费)
- 租客:满足自身的需求 租房
-
代码思维建模:
- 对外暴露一个公共的接口(租房子)
- 客户与中介机构进行沟通,中介看起来和房东功能一致(代理看起来就是展示的对象)
- 完成用户额外的操作(收取服务费)
组成部分
- 要求代理者实现与被代理者相同的接口
- 在代理方法中实现功能的扩展
- 用户调用代理对象完成功能(用户认为代理就是目标对象)
调用流程
静态代理
通过代理模式实现事务控制
- 角色划分:
- 目标对象 target UserServiceImpl类
- 目标方法 method addUser方法
- 代理:实现事务的控制
- 代理对象与目标对象实现相同的接口
修改目标对象名称
编辑代理类
@Service("userService")
public class StaticProxy implements UserService {
//要求引入目标的对象
@Autowired
private UserService target;
//目的:对原有方法进行扩展
public void addUser(User user) {
try {
System.out.println("事务开启");
target.addUser(user);
System.out.println("事务结束");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
}
}
编辑UserServiceImpl
@Service("target")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
//事务的控制应该放到Service层中进行控制
public void addUser(User user) {
userMapper.addUser(user);
}
// public void addUser(User user) {
// try {
// System.out.println("事务开始");
// userMapper.addUser(user);
// System.out.println("事务结束");
// }catch (Exception e){
// e.printStackTrace();
// System.out.println("事务回滚");
// }
// }
}
编辑测试方法
@Test
public void testStaticTx(){
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setId(1112);
user.setName("测试代理机制");
// 执行用户调用
userService.addUser(user);
}
静态代理弊端
静态代理只针对于某个接口,不能实现所有接口的代理,实用性较差
静态代理中,所有的方法都需要手动添加事务的开始/事务提交代码,代码冗余/不够简洁
动态代理机制
动态代理分类
- JDK代理:
- 要求:目标对象必须实现接口
- 代理要求:代理对象也必须实现目标对象的接口
- 目标对象/代理的关系:目标与代理对象是兄弟关系
- CGlib代理
- 要求:不管目标对象是否有接口,都可以为其创建代理对象
- 代理要求:要求代理对象必须继承目标对象
- 目标对象/代理的关系:目标与代理对象是父子关系
编辑JDK动态代理
-
官网API:
-
背会知识点:
-
关于匿名内部类用法说明:匿名内部类引用外部参数,要求参数必须final
-
invoke():该方法标识当代理对象执行时,回调该方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {}
-
执行目标方法:
result = method.invoke(target, args);
-
目标方法执行:
public class JDKProxyFactory { // 要求用户传递目标对象 // 关于匿名内部类用法说明:匿名内部类引用外部参数,要求参数必须final public static Object getProxy(final Object target){ // 调用Java API实现动态代理 /** * 参数分析:3个参数 * 1. ClassLoader loader 类加载器(获取目标对象的Class(类型)) * 2. Class<?>[] interfaces JDK代理要求必须有接口(Java中可以多实现) * 3. InvocationHandler h 对目标方法进行扩展 */ // 获取类加载器 ClassLoader classLoader = target.getClass().getClassLoader(); // 获取接口数组 Class<?>[] interfaces = target.getClass().getInterfaces(); final Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() { // invoke():代理对象调用方法时invoke执行,扩展方法编辑的位置 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { // 添加事务的控制 System.out.println("事务开始"); // 执行目标方法 target代表真实的目标对象 method代表方法对象 args代表方法参数 // result标识目标方法执行的返回值 result = method.invoke(target, args); System.out.println("事务提交"); }catch (Exception e){ e.printStackTrace(); System.out.println("事务回滚"); } return result; } }); return proxyInstance; } }
-
JDK动态代理执行过程
动态代理优势
-
将公共的部分写到动态代理中,之后其他的业务类调用即可
-
编辑DeptService
public interface DeptService { void addDept(); }
-
编辑DeptServiceImpl
@Service("deptService") public class DeptServiceImpl implements DeptService { public void addDept() { System.out.println("调用DeptMapper,实现入库"); } }
-
编辑测试类
public class TestDept { @Test public void testTx(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 获取目标对象 DeptService deptService = (DeptService) context.getBean("deptService"); // 获取代理对象 DeptService proxy = (DeptService) JDKProxyFactory.getProxy(deptService); // 通过代理对象 调用方法 扩展方法 proxy.addDept(); // 执行invoke() } }
动态代理实现方案(二)
-
业务需求:
- 要求对Service层的方法记录其执行的时间
- 通过执行时间长短进行针对性优化
- 要求Service中有addUser()和delete()
- 要求代码结构扩展性好,耦合性低
-
编辑UserService
public interface UserService { void addUser(); void deleteUser(); }
-
编辑UserServiceImpl
@Service("target") public class UserServiceImpl implements UserService { public void addUser() { System.out.println("添加用户"); } public void deleteUser() { System.out.println("删除用户"); } }
-
编辑测试类
public class Test { @org.junit.Test public void testJDKProxy(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); UserService target = (UserService) context.getBean("target"); UserService proxy = (UserService) JDKProxyFactory.getProxy(target); proxy.addUser(); proxy.deleteUser(); } }
动态代理实现方案(三)
-
业务需求:
- 要求进行日志控制
- 要求方法执行时,获取方法的类名,方法民法 进行空服之台输出 以及方法执行时间
- 要求代码结构扩展性好,耦合性低
-
编辑DeptService
public interface DeptService { void addDept(); void searchDept(); void updateDept(); void deleteDept(); }
-
编辑DeptServiceImpl
@Service("target") public class DeptServiceImpl implements DeptService { public void addDept() { System.out.println("添加部门"); } public void searchDept() { System.out.println("查找部门"); } public void updateDept() { System.out.println("修改部门"); } public void deleteDept() { System.out.println("删除部门"); } }
-
编辑测试类
public class Test { @org.junit.Test public void testProxy(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); DeptService target = (DeptService) context.getBean("target"); DeptService proxy = (DeptService) JDKProxyFactory.getProxy(target); proxy.addDept(); proxy.deleteDept(); proxy.searchDept(); proxy.updateDept(); } }
ij
public class JDKProxyFactory {
// 要求用户传递目标对象
// 关于匿名内部类用法说明:匿名内部类引用外部参数,要求参数必须final
public static Object getProxy(final Object target){
// 调用Java API实现动态代理
/**
* 参数分析:3个参数
* 1. ClassLoader loader 类加载器(获取目标对象的Class(类型))
* 2. Class<?>[] interfaces JDK代理要求必须有接口(Java中可以多实现)
* 3. InvocationHandler h 对目标方法进行扩展
*/
// 获取类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
// 获取接口数组
Class<?>[] interfaces = target.getClass().getInterfaces();
final Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
// invoke():代理对象调用方法时invoke执行,扩展方法编辑的位置
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try {
// 添加事务的控制
System.out.println("事务开始");
// 执行目标方法 target代表真实的目标对象 method代表方法对象 args代表方法参数
// result标识目标方法执行的返回值
result = method.invoke(target, args);
System.out.println("事务提交");
}catch (Exception e){
e.printStackTrace();
System.out.println("事务回滚");
}
return result;
}
});
return proxyInstance;
}
}
附AOP案例:https://blog.csdn.net/weixin_47264624/article/details/118990367