一. 什么是代理?
代理其实就和生活中的中介是一样的,假如你要去租房,肯定一头雾水,这时候你想到了找租房的中介,此时你并不会与租房的人进行交涉,而是中介代理租房人和你去谈租房的事,比如签订合同,交租金等。Spring也采用了这样的代理模式,比如将对象交给Spring去进行管理,此时Spring会生成一个代理对象来引用原始的对象。
二. 静态代理
角色分析:
- 抽象角色:一般使用接口或者抽象类
- 代理角色:代理真实角色
- 真实角色:被代理的角色
就拿租房来说,中介代理房东去租房,客户询问中介,定义一个客户类Client.java
,代理租房接口Renter.java
,代理类Proxy.java
,房东类Host.java
来模拟代理模式。
Host.java: 提供房源
/**
* @author jektong
* @Date 2020/6/20-11:19
*/
//房东
public class Host implements Renter {
@Override
public void rent() {
System.out.println("房东出租房子给客户");
}
}
Renter.java:租房业务接口类
/**
* @author jektong
* @Date 2020/6/20-11:17
*/
public interface Renter {
//租房
void rent();
}
Proxy.java:中介代理房东租房处理业务
/**
* @author jektong
* @Date 2020/6/20-11:17
*/
//中介代理房东
public class Proxy implements Renter {
private Host host;
public Proxy(Host host){
this.host = host;
}
@Override
public void rent(){
System.out.println("通知房东出租房屋");
renter.rent();
System.out.println("客户来提房");
}
}
Client.java: 客户找中介租房
/**
* @author jektong
* @Date 2020/6/20-11:24
*/
public class Client {
public static void main(String[] args) {
//房东出租房子
Host host = new Host();
//中介代理出租即可
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
使用代理模式的好处:
- 纯粹的操作真实对象,公共业务不用去关注
- 公共业务交给代理处理,实现业务的分工
- 业务扩展时可以集中管理
当然此模式也有缺点
一个真实角色就会有一个代理角色,导致效率开发变低
下面引出动态代理:
三. 动态代理
- 动态代理的角色和静态代理角色一样
- 代理的类是自动生成,不是自己写的
- 基于接口类的代理和基于抽象类的代理
- 代理方式:JDK代理,类动态代理,JavaSist代理
JDK动态代理
使用动态代理只需要关注两个类
Proxy
: 生成代理类
InvocationHander
: 处理实现的接口
现在用动态代理的方式去实现以上的案例:创建生成动态代理的类ProxyInvocationHander.java
去代替上面静态的Proxy.java
.
/**
* @author jektong
* @Date 2020/6/25-20:19
*/
//动态代理,自动生成代理类
public class ProxyInvationHander implements InvocationHandler {
private Renter renter;
public void setRenter(Renter renter) {
this.renter = renter;
}
//生成代理类,固定代码
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), renter.getClass().getInterfaces(),this::invoke);
}
//处理代理的实例
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(renter, args);
return result ;
}
}
Client.java
这个类也需要进行修改
/**
* @author jektong
* @Date 2020/6/20-11:24
*/
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色,并没有写自动生成
ProxyInvationHander pih = new ProxyInvationHander();
//设置代理角色
pih.setRenter(host);
//生成代理对象
Renter proxy = (Renter)pih.getProxy();
//代理执行方法
proxy.rent();
}
}
这里我们可以不用改变原始的代码结构,自己去添加一些业务逻辑在上面,比如日志代码,还可加上交租金的业务啊之类的,这就是AOP(面向切面编程)的体现。
使用动态代理时,只需要使用一个动态代理类就可以代理多个类,而且代理的是接口。
可以将上面的类改成一个通用的代理类,这样就可以实现代理多个类了,我们只需要修改ProxyInvocationHander.java
这个类就可以,使它成为一个工具类,注意对比
/**
* @author jektong
* @Date 2020/6/25-20:19
*/
//动态代理,自动生成代理类
public class ProxyInvationHander implements InvocationHandler {
//代理接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this::invoke);
}
//处理代理的实例
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
动态代理模式说完之后,我们就可以引入AOP的概念了
四. 什么是AOP
AOP(面向切面编程)
采取的是横向的抽取机制,将分散在各个方法中的重复代码抽取出来。当程序运行的时候再将其提取出来运用到要执行的代码上,提高了代码的重用性。其中类与切面的关系如下:
这张图就很好的反映了切面编程的实现方式,每个方法中都可以添加其它的业务代码,比如事务,日志,权限异常等,并且不会改变原有的代码结构,这就是AOP主要的实现思想。
AOP的相关术语以及在Spring中的作用
Aspect(切面)
:是切入点Pointcut和通知的Advice的集合。(比如加入的Log日志)Joint point(连接点)
:表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。(与Pointcut一样表示在哪个地方执行)Pointcut(切入点)
:表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。Advice(通知)
:Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。(Log中的方法)Target(目标类)
:织入 Advice 的目标对象.。(通知的对象比如接口或者方法)Weaving(织入)
:将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
五. AOP实现方式一(原生态方式实现)
首先导入AOP依赖(还有一些Spring的基本依赖)
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
需求: 定义用户接口以及它的实现类,通过AOP将日志的功能添加进去
用户接口以及他的实现类(UserService和UserServiceImpl):
/**
* @author jektong
* @Date 2020/6/26-20:11
*/
public interface UserService {
void selUser();
void addUser();
void updUser();
void delUser();
}
/**
* @author jektong
* @Date 2020/6/26-20:05
*/
public class UserServiceImpl implements UserService {
@Override
public void selUser() {
System.out.println("查询用户信息");
}
@Override
public void addUser() {
System.out.println("添加用户信息");
}
@Override
public void updUser() {
System.out.println("修改用户信息");
}
@Override
public void delUser() {
System.out.println("删除用户信息");
}
}
定义一个日志通知: AfterLog.java
用于切入业务层的方法中
/**
* @author jektong
* @Date 2020/6/26-20:42
*/
//定义日志的后置通知
public class AfterLog implements AfterReturningAdvice {
/**
* @param returnValue 目标方法执行后的返回值
* @param method 目标方法
* @param objects 对象
* @param o1
* @throws Throwable
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了" + method.getName() + "方法返回的结果为:" + returnValue);
}
}
applicationContext.xml
的配置: 首先要引入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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--注册相关的bean-->
<bean id="userService" class="service.Impl.UserServiceImpl"/>
<bean id="afterLog" class="log.AfterLog"/>
<!--配置aop-->
<aop:config>
<!--切入点 expression写位置-->
<aop:pointcut id="pointcut" expression="execution(* service.Impl.UserServiceImpl.*(..))"/>
<!--执行日志通知-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
关于execution
中的表达式的写法解释:
execution
括号里面写的是要切入的方法,可以是一个,也可以是多个方法
第一个 * 号:表示返回类型,* 号表示所有的类型,比如返回的是方法,后面跟的是切入到哪个包下的方法。比如这里切入的是UserServiceImpl下的所有方法,就用 * 表示,(…)这个表示方法带有那些参数。
测试
/**
* @author jektong
* @Date 2020/6/26-21:11
*/
//测试
public class SpringTestDemo {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService)ac.getBean("userService");
userService.addUser();
}
}
结果:添加的日志功能就实现了
以上实现AOP的方式是用API接口的方式实现的,我们也可以自定义类去实现,发现会更加的简单
六. AOP实现方式二(自定义类的方式实现)
根据以上的案例,增加一个Log.java
类这个就是 我们要切入到业务方法的自定义的类
Log.java
/**
* @author jektong
* @Date 2020/6/27-13:43
*/
public class Log {
public void before(){
System.out.println("方法执行前");
}
public void after(){
System.out.println("方法执行后");
}
}
接下来修改一下xml的配置文件:
<!--注册bean-->
<bean id="userService" class="service.Impl.UserServiceImpl"/>
<bean id="log" class="log.Log"></bean>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="log">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* service.Impl.UserServiceImpl.*(..))"/>
<!--通知介入,此时我们可以自定义切入了,比如在切入点之前执行before()
,或者在 切入点之后执行after()-->
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
<aop:after method="after" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
执行:
建议使用第二种方式,灵活度比较高