Java开发Spring之AOP详解(xml--注解->方法增强事务管理(声明事务的实现))
Posted ahcfl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发Spring之AOP详解(xml--注解->方法增强事务管理(声明事务的实现))相关的知识,希望对你有一定的参考价值。
引入
1、理解AOP的相关概念
2、理解动态代理的原理及使用
3、编写spring AOP中不同通知类型
4、应用spring AOP中的注解
5、熟练spring的事务管理的方式和常用接口
6、理解事务的隔离级别
7、掌握事务的传播行为
8、完成spring声明式事务案例
`xml的本质作用是什么?
做配置文件
动静分离:
动态代码:java代码,一有变动,就重新编译;
静态资源:配置文件(xml,properties文件)
好处:静态资源有修改,无需重新编译java代码,只需重启即可;
`xml文件对于java代码的意义?
编码时:
xml做配置文件,为的是将动态数据与java代码相分离.(松耦合)
执行时:
优先解析xml配置文件,将配置文件中的相关配置转存到java对象中,方便后续java代码直接调用
`直接的使用当前资源属于那一层,就使用那一层的注解
1.@Repository:数据访问层/持久层/数据层
2.@Service:业务逻辑层/服务层
3.@Controller:web层
4.@Configuration:给配置配的注解,等价于xml
5.@Component:组件层(不属于上述任何一层)
以上是我们自定义的资源,可以使用的注解;
如果使用了第三方的资源,建议在配置类中使用
@Bean注解,作用某个方法下,且方法必须有返回值(方法的返回值作为一个bean被ioc容器管理)
一、AOP概述
1、什么是AOP
AOP(Aspect Oriented Programming),即面向切面编程。
通过预编译方式(静态代理)或 运行期(动态代理)实现程序功能的统一维护(增强)
的一种技术。
AOP是OOP的延续,是软件系统开发中的一个热点,也是spring框架的一个重点。
利用AOP可以实现【业务逻辑】各个部分的隔离,从而使得业务逻辑各个部分的耦合性降低,
提高程序的可重用性,同时提高开发效率
简单理解:
AOP是【面向切面编程】,使用【动态代理】技术,实现在【不修改java源代码】的情况下,
运行时实现方法功能的【增强】
具象化理解:【具象化是抽象化的反义词】
理解切面编程,就需要先理解什么是切面。
生活中:
用刀把面包切成数片,切开的每一片面包的切口就是切面==》摸果酱、加蔬菜
菜,锅、炉子共同来完成炒菜,锅与炉子,菜与锅就是切面==》锅有没有热、菜有没有熟、 炉子火力够不够
编程中:
web层==>service层==>dao层,每一层之间也是一个切面,对象与对象之间,
方法与方法之间,模块与模块之间都是一个个切面。
2、AOP的优势及使用场景
我们一般做活动功能的时候,一般对每一个活动接口都会做 (活动是否有效)校验、
是不是需要用户登录校验。
这有个问题就是,有多少活动接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。那么,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。
同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点),而红框中就是面向切面编程。
在不修改业务代码的情况下,使用AOP进行切面编程,对原有功能进行【增强】
# 【1】优势
1、非侵入性:使用动态代理技术,不修改java源代码对已有方法功能进行增强
说明:
1、我们开发好了分享活动主业务代码
2、需求人员需要填活动校验的代码
3、我们有不想修改主业务代码掺杂活动校验的代码
4、此时我们可以用AOP的方式开发
2、高内聚:集中处理某一个关注点,方便维护
说明:
AOP的切面只关注活动校验的代码
3、易移植:可以方便的增加、删除、修改某一个关注点的切入业务
说明:
活动校验规则发生改变,我们可以及时修改,而不需要修改主业务代码
# 【2】使用场景
大多情况下,都是适用于非功能性需求中
- 权限控制
- 日志收集
- 缓存设置
- 分布式追踪
- 异常处理
- 事务控制
3、AOP实现原理
AOP的底层实现需要依赖于动态代理技术;
动态代理在程序运行期间,不修改源码对已有方法进行增强。
二、代理模式
1、代理模式概念
我很忙,忙的没空理你,那你要找我呢,就先找我的代理人吧,那代理人总要知道被代理人能做哪些事情不能做哪些事情吧,那就是两个人具备同一个接口,被代理人虽然不想干活,但是代理的人能干活呀。
生活中的房屋中介
2、代理模式分类
静态代理:
【定义】
在程序运行之前,代理类.class文件就已经被创建
【实现】
由程序员创建或特定工具自动生成源代码,在对其编译
动态代理:
【定义】
程序运行时通过反射机制动态创建的,对方法的【增强】,不需要修改源码
【实现】
基于接口:JDK动态代理
基于子类:CGLib动态代理
3、静态代理演示
静态代理:在程序运行前手动创建代理类,代理类和目标类需要实现相同接口
步骤:
1、创建项目;
2、定义接口:HouseAgencyCompany及接口中租房的方法:rentingHouse;
3、定义房主类:HouseOwner 中介类 HouseProxy均实现接口HouseAgencyCompany;
4、租客类Customer调用HouseProxy完成租房
定义HouseAgencyCompany
/**
* @Description:中介公司
*/
public interface HouseAgencyCompany {
/**
* @Description 租房子
*/
void rentingHouse();
}
定义HouseOwner、HouseProxy
/**
* @Description:被代理人(房东)
*/
public class HouseOwner implements HouseAgencyCompany {
@Override
public void rentingHouse() {
System.out.println("房东签合同");
}
}
/**
* @Description:中介(代理人)
*/
public class HouseProxy implements HouseAgencyCompany {
/**
* 被代理人
*/
private HouseOwner houseOwner;
public HouseProxy() {
this.houseOwner = new HouseOwner();
}
@Override
public void rentingHouse() {
System.out.println("中介带看房子");
System.out.println("中介约房东");
houseOwner.rentingHouse();
System.out.println("中介完成租房");
}
}
定义Customer
import org.junit.jupiter.api.Test;
/**
* @Description:租客
*/
public class Customer {
@Test
public void needHouse(){
HouseOwner houseOwner = new HouseOwner();
houseOwner.rentingHouse();
System.out.println("==================================");
HouseAgencyCompany houseAgencyCompany = new HouseProxy();
houseAgencyCompany.rentingHouse();
}
}
4、动态代理演示
# 【1】有什么问题?
如果目标类中有多个方法都需要增强,我们得为每一个服务都得创建代理类,工作量太大,
不易管理。同时接口一旦发生改变,代理类也得相应修改。
# 【2】动态代理概述
代理类在程序运行时创建的方式被成为动态代理。也就是说,代理类并不是在Java代码中定的,而是在运行时根据我们在Java代码中的动态生成的。
相比于静态代理,动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
【1】jdk动态代理
必须基于接口的动态代理
1、JDK动态代理:基于接口的;
2、JDK动态代理实现要点:
Proxy类
newProxyInstance静态方法
InvocationHandler增强方法
java.lang.reflect.Proxy:
Java动态代理机制的主类,提供了一组静态方法来为一组接口动态地生成代理类及其实例。
//方法1: 该方法用于获取指定动态代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)
//方法2:该方法用于获取关联于指定类装载器和一组接口的动态代理对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)
//方法3:该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)
//方法4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理对象:1、类加载器 2、接口数组、调用处理器(增强部分的业务代码)
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
java.lang.reflect.InvocationHandler:
调用处理器接口,它自定义了一个invoke方法,用于集中处理在动态代理对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理对象时都需要指定一个实现了该接口的调用处理器对象。
InvocationHandler的核心方法:
//该方法负责集中处理动态代理类上的所有方法调用。
//第一个参数是代理对象,第二个参数是被调用的方法对象,第三个方法是调用参数。
//调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行。
Object invoke(Object proxy, Method method, Object[] args)
需求:使用jdk动态代理的方式,增强租房方法,使得租房方法前后打印输入日志信息;
删除HouseProxy
修改Customer
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description:租客
*/
public class Customer {
HouseOwner houseOwner = new HouseOwner();
@Test
public void needHouse(){
HouseAgencyCompany houseProxy = (HouseAgencyCompany) Proxy.newProxyInstance(
houseOwner.getClass().getClassLoader(), //类加载器
houseOwner.getClass().getInterfaces(), //被代理类实现的接口字节码数组(代理类需要实现这些接口)
//new Class[]{HouseAgencyCompany.class},//被代理类实现的接口字节码数组
new InvocationHandler() { // 对需要增强的方法进行增强,对不需要增强的方法调用原来的逻辑
/**
* 每次调用代理类的方法时,此方法都会执行
* @param proxy : 生成的代理类对象(慎用)
* @param method : 当前所执行的方法的字节码对象
* @param args : 当前执行的方法所传递的参数
*/
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
System.out.println("中介公司让中介带客户看房");
Object object = method.invoke(houseOwner, args);
System.out.println("中介公司让中介完成租房业务");
return object;
}
});
houseProxy.rentingHoues();
}
}
【2】cglib动态代理
# 思考
如果目标类没有实现接口呢?
那么就无法使用JDK的动态代理,因此这种方式有其局限性,必须实现一个接口。
可以使用的方案:
使用CGLIB动态代理:基于子类(包含本类)
# Cglib动态代理
1、基于类,无需实现接口;
2、被代理的目标类不能被final修饰
net.sf.cglib.proxy.Enhancer
Enhancer类是CGLib中的一个字节码增强器,作用用于生成代理对象,跟上一章所学的Proxy类相似,常用方式为:
//方法1:该方法用于为指定目标类、回调对象 1、类的类型,2、调用处理器
public static Object create(Class type, Callback callback)
net.sf.cglib.proxy.MethodInterceptor
//方法1:
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
修改Customer
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Description:
*/
public class Customer {
@Test
public void needHouse(){
System.out.println("=====================");
//代理模式下的租房
HouseOwner houseOwnerProxy = (HouseOwner) Enhancer.create(
HouseOwner.class, // 参数1:被代理对象的字节码
new MethodInterceptor() { // 参数2: 接口,在原方法执行前对方法进行拦截处理
/**
* @param o : 生成的代理类对象(被代理类的子类)
* @param method : 当前执行的方法的字节码对象
* @param objects : 当前执行的方法携带的参数数组
* @param methodProxy : 当前执行的方法的代理对象(不用管)
*/
@Override
public Object intercept(Object o, Method method,
Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强
System.out.println("中介小王:带你看房子");
//返回结果
return methodProxy.invokeSuper(o, objects);
}
});
houseOwnerProxy.renttingHouse();
}
}
5、手写AOP演示
在手写的IOC的基础上手写自己的AOP
这里是手写IOC的文章
# 【1】思考
我们在自定义IOC中我们实例化对象采用反射机制,如果需要对bean增强应该怎么做?
实例化过程不是直接实例化目标类,而是通过代理工厂使用动态代理实例化代理类
# 【2】目标
手写aop
【1】使用动态代理实现增强打印信息(不修改saveAccount())
【2】理解什么时候被代理==>bean初始化的时候,就为其生成代理类
【代码演示】
步骤:
1、添加proxy层,添加CglibProxy实现MethodInterceptor、JdkProxy实现InvocationHandler
2、添加ProxyBeanFactory,根据目标类是否有接口判断选择jdk和cglib
3、修改BeanFactory
1、什么时候加入切面?
在初始化IOC的时候,我们使用动态代理的方式加入切面(AOP)
2、为什么使用AOP?
不需要修改业务,可以达到业务增强的目的
添加proxy层
jdk动态代理
package com.example.spring.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Description:jdk动态代理
*/
public class JdkProxy implements InvocationHandler {
private Object targClass;
private String joinPoint = "saveAccount";
public JdkProxy(Object targClass) {
this.targClass = targClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = null;
if (joinPoint.equals(method.getName())){
System.out.println("JDK增强:"+method.getName());
}
object = method.invoke(targClass, args);
return object;
}
}
cglib动态代理
package com.example.spring.proxy;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @Description:cglib动态代理
*/
public class CglibProxy implements MethodInterceptor {
private Object targClass;
private String joinPoint = "saveAccount";
public CglibProxy(Object targClass) {
this.targClass = targClass;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object object = null;
if (joinPoint.equals(method.getName())){
System.out.println("Cglib增强:"+method.getName());
}
object = method.invoke(targClass, objects);
return object;
}
}
添加ProxyBeanFactory
package com.example.spring.factory;
import com.example.spring.proxy.CglibProxy;
import com.example.spring.proxy.JdkProxy;
import net.sf.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
/**
* @Description:工厂bean的动态代理
*/
public class BeanProxyFactory {
//我为谁干活
private Object targClass;
//传入目标类
public BeanProxyFactory(Object targClass) {
this.targClass = targClass;
}
public Object getBean() {
//获得对应类的接口
Class<?>[] interfaces = targClass.getClass().getInterfaces();
Object object = null;
//接口数目大于0
if (interfaces.length>0){
//jdk代理
JdkProxy jdkProxy = new JdkProxy(targClass);
object = Proxy.newProxyInstance(targClass.getClass().getClassLoader(), interfaces, jdkProxy);
}else {
//cglib代理
CglibProxy cglibProxy = new CglibProxy(targClass);
object = Enhancer.create(targClass.getClass(), cglibProxy);
}
return object;
}
}
修改MyBeanFactory
package com.example.spring.factory;
import javafx.beans.binding.ObjectExpression;
import java.io.IOException;
import java.util.*;
/**
* @Description:bean工厂
*/
public class BeanFactory {
//1、事先存储容器
private static Map<String, Object> map = new HashMap<>();
//2、加载配置文件
static {
Properties properties = new Properties();
try {
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("db.properties"));
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String value = (String) properties.get(key);
//3、实例化bean
//Object beanObject 以上是关于Java开发Spring之AOP详解(xml--注解->方法增强事务管理(声明事务的实现))的主要内容,如果未能解决你的问题,请参考以下文章
JAVAWEB开发之Spring详解之——Spring的入门以及IOC容器装配Bean(xml和注解的方式)Spring整合web开发整合Junit4测试