10 代理模式
为什么要学习代理模式?因为这个就是SpringAOP的底层!【SpringAOP和SpringMVC】
代理模式分类:
- 静态代理
- 动态代理
例子:客户通过中介租房子
10.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人!
1.客户直接找房东租到房子:
//接口,出租房子的动作
public interface Rent {
public void rent();
}
//真实角色,房东出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房东出租房子!");
}
}
//客户,直接找房东租房子
public class Client {
public static void main(String[] args) {
Host host = new Host();
host.rent();
}
}
2.可是当客户不能直接找到房东的时候,就需要去找中介
//代理角色,中介
public class Proxy implements Rent{
Host host = new Host();
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
seeHouse();
host.rent();
contract();
fare();
}
public void seeHouse(){
System.out.println("中介带客户看房子!");
}
public void fare(){
System.out.println("中介收取客户中介费用!");
}
public void contract(){
System.out.println("中介与客户签合同!");
}
}
//真实客户,要租房子,此时与中介对接,访问代理角色。
public class Client {
public static void main(String[] args) {
//房东出租房子,不关心对象
Host host = new Host();
//代理,也就是中介帮房东出租房子,还可以有自己的一些行为
Proxy proxy = new Proxy(host);
//客户直接找中介,完成租房子的行为
proxy.rent();
}
}
静态代理模式的好处:
- 可以使真实角色的操作(只完成出租房子的动作)更加纯粹!不用去关注一些公共的业务
- 公共业务交给了代理角色!实现了业务的分工!
- 公共业务发生拓展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色
- 代码亮会翻倍,开发效率会变得更低
10.2 静态代理加深理解
数据库CURD操作,在操作时添加日志需求!
//业务接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
//业务实现
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户!");
}
public void delete() {
System.out.println("删除了一个用户!");
}
public void update() {
System.out.println("修改了一个用户!");
}
public void query() {
System.out.println("查询了一个用户!");
}
}
//客户操作
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
如果说要增加一个日志功能,如何实现?
1.直接在业务实现的地方添加代码,如下面添加代码,但是修改了原有的业务代码,在公司开发中是大忌!
public void add() {
System.out.println("使用了add方法!");
System.out.println("增加了一个用户!");
}
2.使用代理来做,在不改变原来的业务情况下,实现需求最好的方法!
设置一个代理类来完成需求添加日志打印
//代理角色,完成需求功能
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService){
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
//日志打印方法
private void log(String method){
System.out.println("使用了"+method+"方法!");
}
}
//客户端访问代理角色
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP最核心的思想!
10.3 动态代理
-
动态代理和静态代理角色一样
-
动态代理的代理类是动态生成的,不是我们直接写好的
-
动态代理分为两大类:基于接口的动态代理,基于类的动态代理
基于接口:JDK动态代理
基于类:cglib
java字节码实现:javasist
需要了解两个类:Proxy 代理;InvocationHandler 调用处理程序
Proxy
java.lang.reflect.Proxy
Proxy提供了创建动态代理类和实例的静态方法它也是由这些方法创建所有动态代理类的超类。(这是一个静态类,类里面有方法得到代理类)
动态代理类(下面简称代理类)是一个是现在类创建时在运行时指定的接口列表的类,具有下面所述的行为。代理接口是由代理类实现的接口。代理实例是代理类的一个实例。每个代理实例都有一个关联的调用处理程序对象,它实现了接口invocationHandler。通过其代理接口之一的代理实例上的方法调用将被分派到实例调用处理程序的invoke方法,传递代理实例,java.lang.reflect.Method被调用方法的java.lang.reflect.Method对象以及包含参数的类型Object的数组。调用处理程序适当地处理编码方法调用,并且返回的结果将作为方法在代理实例上调用的结果返回。
public static Object newProxyInstance(ClassLoader loader,
类<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
/*返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
Proxy.newProxyInstance因为与IllegalArgumentException相同的原因而Proxy.getProxyClass 。
参数
loader - 类加载器来定义代理类
interfaces - 代理类实现的接口列表
h - 调度方法调用的调用处理函数
结果
具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例 */
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
/*参数
loader - 类加载器来定义代理类
interfaces - 要实现的代理类的接口列表
结果
在指定的类加载器中定义并实现指定接口的代理类 */
InvocationHandler
InvocationHandler是由代理实例的调用处理程序实现的接口。
每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。
Object invoke(Object proxy,Method method,Object[] args);
/*处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。 */
//参数:
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。 方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
//结果: 从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是原始类型,则此方法返回的值必须是对应的基本包装类的实例; 否则,它必须是可声明返回类型的类型。 如果此方法返回的值是null和接口方法的返回类型是基本类型,那么NullPointerException将由代理实例的方法调用抛出。 如上所述,如果此方法返回的值,否则不会与接口方法的声明的返回类型兼容,一个ClassCastException将代理实例的方法调用将抛出。
代码实现
房东出租房子例子(10.1)更改:
//抽象角色,租房
public interface Rent {
public void rent();
}
//真实角色:房东,要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房东要出租房子!");
}
}
//代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色
//现在可以代理一类角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
//proxy:代理类; method:代理类的调用处理程序的方法对象
//处理代理实例上的方法并返回调用
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:本质是利用反射实现!
Object result = method.invoke(rent, args);
contract();
fare();
return null;
}
public void seeHouse(){
System.out.println("中介带客户看房子!");
}
public void fare(){
System.out.println("中介收客户中介费!");
}
public void contract(){
System.out.println("中介与客户签合同!");
}
}
//客户租房
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);//将真实角色放置进去!
Rent proxy = (Rent)pih.getProxy();//动态生成对应的代理类
proxy.rent();
}
}
核心:一个动态代理,一般代理某一业务,一个动态代理可以代理多个类,代理的是接口!
10.4 深化动态代理理解
10.2数据库操作添加打印例子,我们是用动态代理实现代理我们后面写的UserService!
编写一个通用的动态代理实现类!所有的对象设置为Object即可!
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口,编写为通用类Object
//private UserService userService;
private Object target;
public void setTarget(Object target){
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
//重写InvocationHandler中的invoke方法
//处理代理实例,并返回结果
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 msg){
System.out.println("执行了"+msg+"方法!");
}
}
测试代码
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-08-proxy
11 AOP
11.1 什么是AOP?
AOP(Aspect Oriented Programming)意为:面向切面编程!通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架的一个重要内容,是函数式编程的一种衍生范型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同事提高了开发效率。
11.2 AOP在Spring中的作用
提供生命事务:允许用户自定义切面
以下名词需要了解下:
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等。
- 切面(Aspect):横切关注点被模块化的特殊对象。即它是一个类。Log
- 通知(Advice):切面必须要完成的工作。即它是一个类方法。Log方法
- 目标(Target):被通知的对象。接口
- 代理(Proxy):向目标应用通知之后创建的对象。代理类
- 切入点(PointCut):切面通知执行的"地点"的定义。method
- 连接点(JointPoint):与切入点匹配的执行点。invoke
SpringAOP中,通过Advice定义横切逻辑。Spring中支持五种类型的Advice:
即AOP在不改变原有代码的情况下,去增加新的功能!
11.3 使用Spring实现AOP
【重点】使用AOP,需要导入一个依赖包!
<dependencies>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
方式一:使用Spring的API接口【主要SpringAPI接口实现】
1.首先编写我们的业务接口和实现类
//业务接口
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
//实现类
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加用户!");
}
public void delete() {
System.out.println("删除用户!");
}
public void update() {
System.out.println("修改用户!");
}
public void select() {
System.out.println("查询用户!");
}
}
2.写我们的增强类,一个前置增强,一个后置增强
//before
public class log implements MethodBeforeAdvice {
//method:要执行的目标对象的方法
//objects:被调用的方法的参数
//object:目标对象
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"方法被执行了!");
}
}
//after
public class AfterLog implements AfterReturningAdvice {
//returnValue:返回值
//Method:被调用的方法
//Objects:被调用的方法的对象的参数
//target:被调用的目标对象
public void afterReturning(Object returnValue, Method method, Object[] objects, Object target) throws Throwable {
System.out.println("执行了"+target.getClass().getName()
+"的"+method.getName()+"方法,返回值:"+returnValue);
}
}
3.在Spring的文件(applicationContext.xml)中注册,并实现aop切入,注意导入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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册bean-->
<bean id="userService" class="com.serene.service.UserServiceImpl"/>
<bean id="log" class="com.serene.log.log"/>
<bean id="afterLog" class="com.serene.log.AfterLog"/>
<!--方式一:利用原生Spring API接口-->
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.serene.service.UserServiceImpl.*(..))"/>
<!--执行环绕,advice-ref执行方法,pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
4.测试:这里注意动态代理的是接口
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//注意,动态代理 代理的是接口
UserService us = context.getBean("userService", UserService.class);
us.select();
}
5.测试结果
AOP的重要性:很重要,主要是理解其中的思路!
Spring的AOP就是将公共的业务(日志,安全等)和领域业务结合起来,当执行领域业务时,将会把公共业务加进来,实现公共业务的重复利用,领域业务更加纯粹,程序员专业领域业务。其本质还是动态代理。
方式二:自定义类来实现【主要是切面定义】
目标业务同方式一
1.编写一个自定义的切入类
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前!======");
}
public void after(){
System.out.println("======方法执行后!======");
}
}
2.在Spring中配置
<!--方式二:自定义类-->
<bean id="diy" class="com.serene.diy.DiyPointCut"/>
<!--aop的配置-->
<aop:config>
<!--自定义切面,ref要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.serene.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
3.测试结果。(测试代码不变同方式一)
方式三:使用注解实现!
1.编写一个注解实现的增强类
//方式三:s使用注解方式实现AOP
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.serene.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("======方法执行前-anotation!======");
}
@After("execution(* com.serene.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("======方法执行后-anotation!======");
}
//在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的店
@Around("execution(* com.serene.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("======环绕执行前!======");
//执行方法
Object proceed = jp.proceed();
System.out.println("======环绕执行后!======");
}
}
2.在Spring配置文件中,注册bean,并增加支持注解的配置
<!--方式三:注解实现-->
<bean id="annotationPointCut" class="com.serene.diy.AnnotationPointCut"/>
<!--开启注解支持 JDK(默认proxy-target-class="false") cglib(true)-->
<aop:aspectj-autoproxy proxy-target-class="false"/>
3.测试结果。测试代码不变
这里可以看下环绕与方法打印顺序!
对应代码:spring-09-aop