Spring

参考视频:B站狂神,写这个只是方便个人复习,怎么写是我自己的事,我能看懂就行,没要求非要让你看!白嫖还挑刺,是很没有风度的事情。希望做个有风度的“五好青年”!


10、代理模式

为什么要学习代理模式?因为这就是Spring AOP的底层!

  • SpringAOp和SpringMVc】代理模式的分类:
    • 静态代理
    • 动态代理

10.1 静态代理

角色分析:

  • 抽象角色:一般会使用接口或者抽象类来解决;
  • 真实角色:被代理的角色;
  • 代理角色:代理真实角色,代理真实角色后,我们一般会做一些附属操作;
  • 客户:访问代理角色的人!

1.接口

package com.github.subei.demo;

// 租房
public interface Rent {
    public void rent();
}
  1. 真实角色
package com.github.subei.demo;

public class Host implements Rent{
    public void rent(){
        System.out.println("房东要出租房子?");
    }
}
  1. 代理角色
package com.github.subei.demo;

public class Proxy {
    private Host 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("和你签合同!");
    }
}
  1. 客户端访问代理角色
package com.github.subei.demo;

public class Client {
    public static void main(String[] args) {
        // 房东要租房子
        Host host = new Host();
// host.rent();
        // 代理,中介帮房东租房子,但是呢?代理角一般会有一些附属操作!
        Proxy proxy = new Proxy(host);
        // 直接找中介租房
        proxy.rent();
    }
}

代理模式的好处:

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色;代码量会翻倍,开发效率会变低。

10.2 静态代理再理解

  1. 创建一个抽象角色,比如平时开发做的用户业务,抽象起来就是增删改查!
package com.github.subei.demo2;

// 实现增删改查业务
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}
  1. 需要一个真实对象来完成这些增删改查操作。
package com.github.subei.demo2;

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("查询用户");
    }
}
  1. 设置一个代理类来处理日志!代理角色
package com.github.subei.demo2;

public class UserServiceProxy implements UserService{
    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        login("add");
        userService.add();
    }

    public void delete() {
        login("delete");
        userService.delete();
    }

    public void update() {
        login("update");
        userService.update();
    }

    public void query() {
        login("query");
        userService.query();
    }

    // 日志方法
    public void login(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}
  1. 测试类
package com.github.subei.demo2;

public class Client {
    public static void main(String[] args) {
        // 真实业务
        UserServiceImpl userService = new UserServiceImpl();
        // 代理类
        UserServiceProxy proxy = new UserServiceProxy();
        // 代理类实现
        proxy.setUserService(userService);

        proxy.add();
        proxy.query();
    }
}

AOP:纵向开发,横向开发。

我们想要静态代理的好处,又不想要静态代理的缺点,所以 , 就有了动态代理 !

10.3 动态代理

  • 动态代理和静态代理角色一样;

  • 动态代理的代理类是动态生成的,不是我们直接写好的;

  • 动态代理分为两大类:基于接口的动态代理,基于类的动态代理。

    • 基于接口——JDK动态代理【我们在这里使用】
    • 基于类——cglib
    • Java字节码实现:javasist
  • 需要了解两个类: InvocationHandler、Proxy,打开JDK帮助文档


案例实现:

  1. 抽象角色
package com.github.subei.demo3;

// 租房
public interface Rent {
    public void rent();
}
  1. 真实角色
package com.github.subei.demo3;
// 房东
public class Host implements Rent {
    public void rent(){
        System.out.println("房东要出租房子?");
    }
}
  1. 代理角色
package com.github.subei.demo3;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Rent rent;

    public void setRent(Rent rent){
        this.rent = rent;
    }

// Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
// new Class<?>[] { Foo.class },
// handler);

    // 生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

    // 处理代理实例,并返回代理结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 动态代理本质:利用反射机制实现!
        Object result = method.invoke(rent, args);
        seeHouse();
        fare();
        return result;
    }

    // 看房
    public void seeHouse(){
        System.out.println("中介带你看房!");
    }

    // 收中介费
    public void fare(){
        System.out.println("收中介费!");
    }

    // 签合同
    public void contract(){
        System.out.println("和你签合同!");
    }

}
  1. 测试类
package com.github.subei.demo3;

public class Client {
    public static void main(String[] args) {
        // 真实角色
        Host host = new Host();

        // 代理角色
        ProxyInvocationHandler handler = new ProxyInvocationHandler();

        // 通过调用程序来处理角色,来处理我们要调用的接口对象!
        handler.setRent(host);

        Rent proxy = (Rent) handler.getProxy(); // 动态生成对应的代理类!

        proxy.rent();
    }
}

核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!

10.4 动态代理再理解

觉得上面比较麻烦,可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

package com.github.subei.Demo4;

import com.github.subei.demo3.Rent;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 会用这个类,自动生成代理类
public class ProxyInvocationHandler 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);
    }

    // 处理代理实例,并返回代理结果
    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+"方法");
    }
}
  • 测试类
package com.github.subei.Demo4;

import com.github.subei.demo2.UserService;
import com.github.subei.demo2.UserServiceImpl;

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.add();
    }

动态代理的好处

  • 可以使得我们的真实角色更加纯粹,不再去关注一些公共的事情。
  • 公共的业务由代理来完成,实现了业务的分工。
  • 公共业务发生扩展时,方便集中管理。
  • 一个动态代理,一般代理某一类业务。
  • 一个动态代理可以代理多个类,代理的是接口!

11、AOP

11.1 什么是AOP

  • AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

  • AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

  • 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

11.2 Aop在Spring中的作用

<mark>提供声明式事务;允许用户自定义切面</mark>

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
  • 目标(Target):被通知对象。
  • 代理(Proxy):向目标对象应用通知之后创建的对象。
  • 切入点(PointCut):切面通知 执行的 “地点”的定义。
  • 连接点(JointPoint):与切入点匹配的执行点。

  • SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:

  • 即 Aop 在 不改变原有代码的情况下 , 去增加新的功能。

11.3 使用Spring实现Aop

使用AOP织入,需要导入一个依赖包!

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>

方式一:使用Spring接口

  • 编写业务接口和实现类
package com.github.subei.service;

public interface UserService {
    public void add();
    public void delete();
    public void select();
    public void update();
}
package com.github.subei.service;

public class UserServiceImpl implements UserService{

    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void select() {
        System.out.println("查询了一个用户");
    }

    public void update() {
        System.out.println("更新了一个用户");
    }
}
  • 前置增强类,后置增强类
package com.github.subei.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {
    // method:要执行的目标对象的方法
    // args:参数
    // target:目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行了!");
    }
}
package com.github.subei.log;

import org.springframework.aop.AfterReturningAdvice;

import java.lang.reflect.Method;

public class AfterLog implements AfterReturningAdvice {
    // returnValue:返回值
    // method:被调用的方法
    // args:被调用的方法的对象的参数
    // target:被调用的目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + target.getClass().getName()
                +"的"+ "返回结果为:" + returnValue);
    }
}
  • 在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.github.subei.service.UserServiceImpl"/>
    <bean id="log" class="com.github.subei.log.Log"/>
    <bean id="afterLog" class="com.github.subei.log.AfterLog"/>

    <!-- 方式一:使用原生Spring API接口 -->
    <!-- 配置AOP:需要导入AOP的约束 -->
    <aop:config>
        <!-- 切入点:expression:表达式 , execution(要执行的位置!* * * *) -->
        <aop:pointcut id="pointcut" expression="execution(* com.github.subei.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>
  • 测试类
import com.github.subei.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.select();
    }
}

Spring的Aop就是将公共的业务 (日志,安全等) 和领域业务结合起来,当执行领域业务时,将会把公共业务加进来。实现公共业务的重复利用,其本质还是动态代理。


第二种方式:自定义类来实现Aop

主要是切面定义。

  • 第一步 : 写一个切入类
package com.github.subei.diy;

public class DiyPointCut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 在spring中配置
    <!-- 第二种:自定义类 -->
    <bean id="diy" class="com.github.subei.diy.DiyPointCut"/>

    <aop:config>
        <!-- 自定义切面,ref要引用的类 -->
        <aop:aspect ref="diy">
            <!-- 切入点 -->
            <aop:pointcut id="point" expression="execution(* com.github.subei.service.UserServiceImpl.*(..))"/>
            <!-- 通知 -->
            <aop:before method="before" pointcut-ref="point" />
            <aop:after method="after" pointcut-ref="point"/>
        </aop:aspect>
    </aop:config>
  • 测试类
import com.github.subei.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 动态代理代理的是接口
        UserService userService = context.getBean("userService", UserService.class);
        userService.select();
    }
}


第三种方式:使用注解实现(未实现!!!)

  • 第一步:编写一个注解实现的增强类
package com.github.subei.diy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

// 使用注解方式实现AOP
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.github.subei.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }

    @After("execution(* com.github.subei.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }

    // 在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点
    @Around("execution(* com.github.subei.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        Signature signature = jp.getSignature();
        System.out.println("签名:"+ signature); // 获得签名

        // 执行目标方法:proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");

        System.out.println(proceed);
    }
}
  • 第二步:在Spring配置文件中,注册bean,并增加支持注解的配置。
    <!-- 第三种方法:使用注解方式实现 -->
    <bean id="annotationPointCut" class="com.github.subei.diy.DiyPointCut"/>
    <!-- 开启注解支持! -->
    <aop:aspectj-autoproxy/>
  • 这里不知怎么回事,出现了如下情况,未解决……

  • aop:aspectj-autoproxy——说明

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。