一、概述
代理模式分为静态代理以及动态代理,属于23中设计模式之一。动态代理是SpringAop的底层。代理的概念如下图所示:
二、 静态代理
上图角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真是角色:被代理的角色
- 代理角色:代理真是角色,代理真是角色后,一般会做一些附属操作。
示例1:上图代码实现如下:
对出租房屋进行抽象(接口):
/**
* 抽象角色:租房
*/
public interface Rent {
public void rent();
}
Host.java 真实角色:
/**
* 真是角色:房东,房东要出租房屋
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房屋");
}
}
Proxy . java 即代理角色
/**
* 代理角色,中介
*/
public class Proxy implements Rent {
private Host host;
public Proxy(){
}
public Proxy(Host host){
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fare();
}
/**
* 看房,属于代理类增加的逻辑
*/
public void seeHouse(){
System.out.println("带客户看房");
}
/**
* 收费,属于代理类增加的逻辑
*/
public void fare(){
System.out.println("收费");
}
}
客户:
/**
* 租房:静态代理
* 客户会去找代理,由代理提供相应服务,其中的核心服务被代理类包裹
*/
@Test
public void TestDaili(){
//房东出租房
Host host = new Host();
//中介包裹房东
Proxy proxy = new Proxy(host);
//客户找代理(中介)租房
proxy.rent();;
}
代理模式的优点:
- 可以使真实角色的操作更加纯粹!不用关注一些公共的业务。
- 公共业务交给代理角色,实现了业务的分工
- 公共业务扩展的时候,方便集中管理。
静态代理的缺点:
- 一个真实角色就会产生一个代理角色,代码量翻倍,开发效率变低。
示例2:代理增加日志功能
1. 创建一个抽象接口,对用户业务进行抽象:
/**
* 抽象角色:增、删、改、查
*/
public interface UserService {
void add();
void delete();
void update();
void query();
}
2. 创建真实对象,实现以上业务
/**
* 真实角色:完成UserService
*/
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void query() {
System.out.println("查询一个用户");
}
}
此时,业务代码是可以正常运行的,现在,我们有一个新的需求:实现一个日志输出功能,以记录业务代码的调用情况。实现思路有两种,如下:
- 思路1:在业务实现类每个方法中增加相应操作(该思路是传统方法,缺点很多)。
- 思路2:使用代理,在不改变原有业务的情况下,实现此日志记录功能!
3. 创建代理类
/**
* 代理角色,在代理业务原有功能的同时,增加日志输出
*/
public class UserServiceProxy implements UserService{
private UserServiceImpl userService; //被代理的业务对象
public void setUserService(UserServiceImpl userService){
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
/**
* 日志输出功能
* @param msg
*/
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
4. 测试
public class client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//代理类注入被代理对象
proxy.setUserService(userService);
proxy.add();
}
}
以上示例1及示例2代码都是为了体现一个重要的思想:在不改变原有代码的情况下,实现对原有功能的增强。这是AOP中最核心的思想,如下图所示:
三、动态代理
动态代理和静态代理的角色是一样的,动态代理的代理类是动态生成的,不是像静态代理那样是直接写死的。动态代理可以分为两大类:一类是基于接口动态代理 , 一类是基于类的动态代理。
- 基于接口的动态代理,如JDK动态代理
- 基于类的动态代理,如cglib
JDK的动态代理需要了解两个类:
核心 : InvocationHandler (调用处理程序) 和 Proxy (代理)
InvocationHandler(调用处理程序)
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。Object invoke(Object proxy, 方法 method, Object[] args)
Proxy:代理
动态代理类 :是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。 每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler 。 通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例,
java.lang.reflect.Method
被调用方法的java.lang.reflect.Method
对象以及包含参数的类型Object
Object的数组。 调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。
生成代理类的方法(静态方法):
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
示例:
1. 通用的动态代理实现类:
public class ProxyInvocationHandler implements InvocationHandler {
private Object target; //被代理对象
public void setTarget(Object target) {
this.target = target;
}
/**
* 生成代理类
* @return
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
/**
*
* @param proxy 代理类
* @param method 代理类的调用处理程序的方法对象
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target,args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
2. 测试:
public class Client {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
/**
* 设置要代理的对象
*/
pih.setTarget(userService);
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.delete();
}
}
动态代理的优点:
-
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
-
公共的业务由代理来完成 . 实现了业务的分工 ,
-
公共业务发生扩展时变得更加集中和方便 .
-
一个动态代理 , 一般代理某一类业务
-
一个动态代理可以代理多个类,代理的是接口!
四、Spring AOP操作 示例
AOP操作概述:https://blog.csdn.net/sssxlxwbwz/article/details/122776028
示例:
业务抽象接口:
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
业务实现类:
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加用户");
}
@Override
public void delete() {
System.out.println("删除用户");
}
@Override
public void update() {
System.out.println("更新用户");
}
@Override
public void search() {
System.out.println("查询用户");
}
}
编写切入类:
public class DiyPointcut {
public void before(){
System.out.println("===方法执行前===");
}
public void after(){
System.out.println("===方法执行后===");
}
}
Spring配置文件中进行AOP配置:
<?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 -->
<bean id="userService" class="com.wyf.service.UserServiceImpl"/>
<!-- 配置AOP 需要导入AOP约束 -->
<!-- 自定义类注册 -->
<bean id="diy" class="com.wyf.pointcut.DiyPointcut"/>
<aop:config>
<!-- 将上面的自定义类定义为切面,切面包含切点和通知 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.wyf.service.UserServiceImpl.*(..))"/>
<!-- 通知,表明什么时候执行什么方法,以及作用到的切入点 -->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
</beans>
测试类:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}