1. 引言
我们先通过现有业务层存在的问题来引出代理
定义业务接口
public interface UserService {
void save(String name);
void delete(String id);
void update();
String findAll(String name);
String findOne(String id);
}
定义业务接口的实现类
public class UserServiceImpl implements UserService{
@Override
public void save(String name) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void delete(String id) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void update() {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public String findAll(String name) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return name;
}
@Override
public String findOne(String id) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return id;
}
}
我们发现业务接口的实现类中的每个方法都有着开启事务、提交事务、回滚事务,也就是存在着代码冗余,那么为了解决这个问题,使业务逻辑对象能够更加专注的处理业务逻辑,我们就需要使用代理这种设计模式。
2. 代理
什么是代理
为什么需要代理
- 很多时候除了当前类能够提供的功能外,我们还需要补充一些额外功能
代理的作用
- 代理对象可以在目标对象和客户对象之间起到中介的作用,从而为目标对象增添额外的功能
2.1 静态代理
目标类|对象(target) :被代理类称之为目标类|或者被代理的对象的称之为目标对象
开发代理的原则: 代理类和目标类功能一致且实现相同的接口,同时代理类中依赖于目标类对象(调用目标类对象的方法)
开发静态代理类
public class UserServiceStaticProxy implements UserService{
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public void save(String name) {
try {
System.out.println("开启事务");
userService.save(name);
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void delete(String id) {
try {
System.out.println("开启事务");
userService.delete(id);
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void update() {
try {
System.out.println("开启事务");
userService.update();
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public String findAll(String name) {
try {
System.out.println("开启事务");
String all = userService.findAll(name);
System.out.println("提交事务");
return all;
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return null;
}
@Override
public String findOne(String id) {
try {
System.out.println("开启事务");
String one = userService.findOne(id);
System.out.println("提交事务");
return one;
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return null;
}
}
更改目标实现类
public class UserServiceImpl implements UserService{
@Override
public void save(String name) {
System.out.println("处理事务逻辑,调用DAO~~~");
}
@Override
public void delete(String id) {
System.out.println("处理事务逻辑,调用DAO~~~");
}
@Override
public void update() {
System.out.println("处理事务逻辑,调用DAO~~~");
}
@Override
public String findAll(String name) {
System.out.println("处理事务逻辑,调用DAO~~~");
return name;
}
@Override
public String findOne(String id) {
System.out.println("处理事务逻辑,调用DAO~~~");
return id;
}
}
静态文件的编写
<bean class="staticproxy.UserServiceImpl" id="userService"></bean>
<bean class="staticproxy.UserServiceStaticProxy" id="userServiceStaticProxy">
<property name="userService" ref="userService"/>
</bean>
调用代理方法
ApplicationContext context = new ClassPathXmlApplicationContext("staticproxy/spring.xml");
UserServiceStaticProxy userServiceStaticProxy = (UserServiceStaticProxy)
context.getBean("userServiceStaticProxy");
userServiceStaticProxy.findAll("小陈");
总结
-
代理对象和原始业务逻辑对象要实现相同的接口,代理类和真正的业务逻辑对象实现的目标其实是一致的,代理可以在保证原始业务逻辑不变的情况下去额外增加一些操作,而为了达到相同的目标,代理对象一般都要调用原始业务逻辑对象的方法,因此可以在代理类中声明一个类型为原始业务逻辑对象的成员变量,使用SET方式注入,在代理类实现接口时调用原始业务逻辑对象的方法,并增加一些额外的操作(代理对象依赖原始业务逻辑对象)。 -
一旦我们开发了静态代理类,之后我们就不能使用业务逻辑对象去调用方法了,而要使用代理对象去调用。 -
使用代理的设计模式可以让原始业务逻辑对象更加专注于处理事务逻辑。
# 补充
新的问题:往往在开发我们书写的不仅仅是一个业务层,两个业务层,而我们的业务层会有很多,如果为每一个业务层开发一个静态代理类,不仅没有减轻工作量,甚至让我们的工作量多了一倍不止怎么解决以上这个问题呢?
解决方案: 为业务层在运行过程中动态创建代理类,通过动态代理类去解决我们现有业务层中业务代码冗余的问题 。
2.2 动态代理
我们知道静态代理的开发需要为每一个类都开发一个静态代理类,如果有很多个类,却仍然使用静态代理开发代理类,这样是非常麻烦的。这时候就需要用到动态代理类了,它是在程序运行的过程中通过代码的方式去创建代理对象的,非常方便。
什么是动态代理呢
动态代理就是在程序运行的过程中,动态的通过代码底层的方式去创建一些代理类,这些代理对象是通过代码通过jvm底层执行我们的代码去帮我们创建出来的代理类。我们可以通过动态代理对象去解决现有业务层的代码冗余问题,或者可以通过动态代理对象执行一些附加的操作。由于动态代理是在程序运行过程中动态生成的,它不需要我们为每一个业务层开发一个动态代理,只需要我们写一段固定的代码,日后它就不断地通过这个固定的代码在程序运行过程中生成动态代理对象就可以了。
下面我们通过一段代码来学习一下动态代理:
-
首先是我们需要用到的UserService接口 public interface UserService {
void save(String name);
void delete(String id);
void update();
String findAll(String name);
String findOne(String id);
}
-
之后是我们的原始逻辑对象
public class UserServiceImpl implements UserService{
@Override
public void save(String name) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void delete(String id) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public void update() {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
}
@Override
public String findAll(String name) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return name;
}
@Override
public String findOne(String id) {
try {
System.out.println("开启事务");
System.out.println("处理事务逻辑,调用DAO~~~");
System.out.println("提交事务");
} catch (Exception e) {
System.out.println("回滚事务");
e.printStackTrace();
}
return id;
}
}
-
上面的原始逻辑对象存在着冗余问题,下面我们通过动态代理来解决一下这个问题。 首先我们需要知道一个类,叫做Proxy,它有一个静态方法newProxyInstance是用来生成代理对象的,这个newProxyInstance有三个参数:
- 参数一:ClassLoader类型,第一个参数是类加载器,我们知道代理类和原始逻辑对象要有相同的接口,对于动态代理来说,因为读接口的信息,因为底层是要操作字节码,而ClassLoader就是用来读.class文件的,读类信息的。
- 参数二:Class[]类型,这个Class[]数组需要传原始逻辑对象实现的所有接口,因为代理对象要和原始逻辑对象实现相同的接口,传入这个Class[]数组是为了知道要实现的所有接口是什么。
- 参数三:InvocationHandler接口类型(在传参的时候直接new就可以),这个接口我们需要实现它的invoke()方法,invoke()方法里面用来添加额外功能并且调用原始逻辑对象的方法。
newProxyInstance方法返回类型是一个Object,如果我们知道返回的是原始逻辑的代理类,我们可以使用强制类型转换转换为原始逻辑对象实现的接口类型。 其次,InvocationHandler接口的invoke()方法也有三个参数:
- 参数1:是一个Object类型的对象,代表当前代理对象。
- 参数2:传入的是一个Method对象,之后在InvocationHandler接口的invoke()方法内部用Method对象的反射调用原始逻辑对象的对应方法。(Method方法也有一个invoke方法,它的invoke方法有两个参数,第一个参数传入原始逻辑对象的类对象(传参的时候可以直接new),因为我们要知道我们现在所创建的动态代理对象是为哪个原始逻辑类创建的,第二个参数代表原始逻辑对象的这个方法需要传入的参数)。
- 参数3:Method需要传入的参数
下面是我们使用动态代理创建代理的代码:
public class TestDynamicProxy {
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class[] classes = {UserService.class};
UserService userServiceDynamicProxy = (UserService) Proxy.newProxyInstance(classLoader, classes, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("当前执行的方法:" + method.getName());
System.out.println("当前执行的方法的参数:" + args[0]);
try {
System.out.println("开启事务");
Object invoke = method.invoke(new UserServiceImpl(), args);
System.out.println("提交事务");
return invoke;
} catch (Exception e) {
e.printStackTrace();
System.out.println("回滚事务");
}
return null;
}
});
String result = userServiceDynamicProxy.findAll("小陈");
}
}
之后调用动态代理对象的findAll方法的执行流程是这样的: 执行InvocationHandler类的invoke方法,内部是增加一些额外操作,之后通过method对象的方法去调用原始逻辑对象的方法。 通过使用动态代理,关于事务的开启、关闭、回滚直接在动态代理中就可以操作了,使原始逻辑对象可以更加专注于处理事务的逻辑。
结果:
3. AOP编程
# spring中AOP编程
AOP:Aspect(切面)Oriented(面向)Programing(编程)
底层原理:java代理设计模式 动态代理
好处 :在保证原始业务功能不变情况下,通过代理对象完成业务中附加操作(事务等),将业务中核心操作放在目标对象中执行,
实现了核心操作和附加操作的解耦
通知(Advice):在动态代理对象的invoke方法中除了目标对象的方法以外的操作都称之为通知(附加操作就是通知,事务通知、
日志通知、性能通知...)
环绕通知:围绕(前后都有的)着目标对象方法的附加操作称为环绕通知
前置通知:只想要让附加操作在目标对象方法之前执行
后置通知:附加操作只想在目标对象的方法之后去执行
异常通知:出现异常之后执行的附加操作称之为异常通知
切入点(Pointcut):指定开发好的通知应用于项目中哪些组件(类)哪些方法 UserService 一般通知多用于业务层
切面(Aspect) = 通知(Advice) + 切入点(Pointcut)
AOP 面向切面编程:1. 开发通知类(附加操作) 2. 配置切入点 3. 组装切面
# spring aop编程的编程步骤
1. 引入aop编程相关依赖
spring-aop spring-expression spring-aspect(这三个依赖在前面配置pom.xml时已经引入过了)
2. 项目开发额外功能通知
环绕通知 MethodIntercept
前置通知 MethodBeforeAdvice
后置通知 AfterReturningAdvice
异常通知 ThrowsAdvice
3. 配置切面 spring.xml文件中完成
a). 注册通知类
<bean class="xxxAdvice" id="">
b). 组装切面 aspect = advice + pointcut
<aop:config>
<!--切入点-->
<aop:pointcut>
<!--切面-->
<aop:advisort>
</aop:config>
3.1 开发前置通知
接下来我们开始aop的编程:
这是我们用到的EmpService接口
public interface EmpService {
void save(String name);
String find(String name);
}
这是EmpService接口的实现类EmpServiceImpl
public class EmpServiceImpl implements EmpService{
@Override
public void save(String name) {
System.out.println("处理业务逻辑调用save DAO~~" + name);
}
@Override
public String find(String name) {
System.out.println("处理业务逻辑调用find DAO~~" + name);
return name;
}
}
现在我们想要让目标对象每次在方法执行之前输出方法的名字,这是额外功能,又因为在每次方法执行之前输出,所以我们需要开发一个前置通知
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("当前执行方法:" + method.getName());
System.out.println("当前执行方法参数:" + objects[0]);
System.out.println("目标对象:" + o);
}
}
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="aop.EmpServiceImpl" id="empService"></bean>
<bean class="aop.MyBeforeAdvice" id="myBeforeAdvice"/>
<aop:config>
<aop:pointcut id="pc" expression="execution(* aop.EmpServiceImpl.*(..))"/>
<aop:advisor advice-ref="myBeforeAdvice" pointcut-ref="pc"/>
</aop:config>
</beans>
测试类+结果
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");
EmpService empService = (EmpService) context.getBean("empService");
System.out.println(empService.getClass());
}
}public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("aop/spring.xml");
EmpService empService = (EmpService) context.getBean("empService");
System.out.println(empService.getClass());
}
}
总结:
|