Java代码审计前置知识——Spring框架AOP和IoC

目录

前言:     

0x01、IoC

解释

DI依赖注入形式

 2.1 setter方法注入

2.2 构造方法注入

IoC的装载机制

3.1 ApplicationContext的实现类有

3.2 加载容器

3.3 获取实例

 Spring的配置文件

 p和c命名空间

 Bean的作用域

1、prototype

 2、singleton

 3、其他

 Bean的自动装配

byType

 IoC中使用注解

先导入配置

1、@Autowired

2、@Qualifier

3、@Resource

4、@Component

 5、衍生的注解

举个例子——使用Java的方式配置Spring

总结对比

0x02 代理模式

2.1 静态代理

2.2  动态代理

代码实现

总结三部曲:

优点

0x03 AOP

3.1 什么是AOP

 3.2  AOP在Spring中的使用

3.3 使用Spring实现AOP

方法一:通过 Spring API 实现

方法二:自定义类来实现Aop

方法三:使用注解实现

参考资源:


前言:     

     随着 Java Web 技术的不断发展, Java 开发 Web 项目由最初的单纯依靠 Servlet (在
Java 代码中输出 HTML )慢慢演化出了 JSP (在 HTML 文件中书写 Java 代码)。虽然JSP 的出现在很大程度上简化了开发过程和减少了代码量,但还是对开发人员不够友好,所以慢慢地又出现了众多知名的开源框架,如 Struts2 Sping Spring MVC Hibernate和 MyBatis 等。
        目前很多成熟的大型项目在开发过程中都使用这些开源框架,而框架的本质是对底层信息的进一步封装,目的是使开发人员将更多的精力集中在业务逻辑中。针对框架的审计则需要我们对框架本身的执行流程有一定程度的了解,根据框架的执行流程逐步追踪,从而发现隐藏在项目代码中的种种安全隐患。

0x01、IoC


是指应用程序中对象的创建、销毁等不再由程序本身编码实现,而是由外部的Spring容器在程序运行时根据需要注入到程序中,降低了对象间的依赖关系。

举个例子:

不同动物,移动方式不相同,有的跑(run),有的飞(fly)

1、创建接口类Moveable

public interface Moveable {
    void move();
}

2、创建类Animal,实现Moveable

public class Animal implements Moveable {
    private String animalType;  //何种动物
    public void setAnimalType(String animalType) {
        this.animalType = animalType;
    }
    private String moveMode;  //如何move
    public void setMoveMode(String moveMode) {
        this.moveMode = moveMode;
    }
public void move() {  //move接口的实现
	String moveMessage= animalType + " can " + moveMode;
         System.out.println(moveMessage);
    }

3、添加配置



	
		
		      Bird
		
		  fly   
	

4、创建测试类Test

class Test{
    public static void main(String []args){
        //创建Spring容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
        //从容器中获取Animal类的实例,没有new对象,而是由name属性在容器里面直接取
        Moveable animal = (Moveable) ctx.getBean("animal"); 
        //调用move方法
        animal.move();//运行结果:Bird can fly 
    }
}

5、修改moveMessage和moveMode的值


	  Dog   
	  run   

再次运行程序,结果变为:Dog can run

解释

  • 之前我们创建对象是通过:

        类型  变量名 = new 类型();

        即:Animal animal = new Animal();

  • 现在通过xml配置


      Dog  

这里的id = 变量名,class=new 的对象,name相等于给对象中的属性设置一个值。

        配置起来可能比较繁琐,springboot解决了这个问题,但是通过spring容器来调用对象,实现了解耦合,IOC控制发生了反转。

Java代码审计前置知识——Spring框架AOP和IoC_第1张图片

  • DI依赖注入形式


  1. D:bean对象的创建依赖于容器
  2. I:bean对象中的所有属性,由容器来注入

 2.1 setter方法注入

如上代码中,Animal类中的animalType和moveMode属性的值,都是通过相应的设值方法setAnimalType( )和setMoveMode( ),将配置文件bean.xml中指定的值(value部分)分别注入给这两个属性。

 public void setAnimalType(String animalType) {
        this.animalType = animalType;
    }

2.2 构造方法注入

依赖关系是通过类构造函数建立,容器通过调用类的构造方法,将其所需的参数值注入其中。

 public class Animal implements Moveable {//Moveable接口不变
    private String animalType;
    private String moveMode; 
public Animal(String animalType, String moveMode) {//构造函数,有两个参数
        this.animalType = animalType;
        this.moveMode = moveMode;
    }
}

修改bean.xml文件的内容,其中,元素的内容如下:



	

或者



	

  • IoC的装载机制


Spring通过ApplicationContext(或BeanFactory)接口来实现对容器的加载。

3.1 ApplicationContext的实现类有

(1) ClassPathXmlApplicationContext:从classpath下加载配置文件
(2) FileSystemXmlApplicationContext:从文件系统中加载配置文件

3.2 加载容器

我们可以通过任一实现类来将配置文件中定义的Bean加载到容器中。如:  //创建Spring容器,bean.xml保存于类路径下

  ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");//获取上下文

或者是:

 ApplicationContext ctx =   new FileSystemXmlApplicationContext("classpath:bean.xml");

3.3 获取实例

//从容器中获取Animal类的实例:bean配置id为animal的对象
Moveable animal = (Moveable) ctx.getBean("animal"); 

  •  Spring的配置文件


Java代码审计前置知识——Spring框架AOP和IoC_第2张图片

  •  p和c命名空间


通过前面xml配置,我们要写大量的,为了简化我们引入了P (set注入)

首先导入url

xmls:p="http://www.springframework.org/schema/p"

举上面例子:


   

同理我们引入了C注入,它适合构造函数注入(p和c不能同时使用)


   
  •  Bean的作用域


1、prototype

Java代码审计前置知识——Spring框架AOP和IoC_第3张图片

 2、singleton

Java代码审计前置知识——Spring框架AOP和IoC_第4张图片

 3、其他

其他的在web开发中会具体使用,这里不再展开。

  •  Bean的自动装配


自动装配就是查找匹配的bean的过程。容器能自动完成装配,包括按名称、按类型、利用构造函数等,相当于隐式装配。

 

Java代码审计前置知识——Spring框架AOP和IoC_第5张图片

 

byType

会自动在容器上下文中查找,和自己属性类型值相同的Bean



    
    	

  •  IoC中使用注解


在Spring项目中,既可以使用XML来配置bean的信息,也可以使用注解达到简化配置文件的目的。

先导入配置





再写好相应的Bean

Java代码审计前置知识——Spring框架AOP和IoC_第6张图片

 

1、@Autowired


Spring 2.5 引入了 @Autowired 注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作

	@Autowired(request=false)//对需要类成员变量的方法使用自动装配注解,说明这个属性可以为null
		public void setPrinter(MessagePrinter printer) {
		       this.printer = printer;
		}
也可在私有成员变量上直接加注解@Autowired: 
		@Autowired
		private MessagePrinter printer;//可以省略相应的setter方法

2、@Qualifier


如果一个Bean的属性可能来自多个其它的候选Bean,导致Spring无法确定使用哪一个Bean,当Spring容器在启动时就会抛出 异常。Spring 允许我们通过 @Qualifier 注解指定注入Bean的名称。

例如

 @Autowired//对类的私有成员变量使用自动装配注解
		   private @Qualifier(value="screen")MessagePrinter printer;

  • @Qualifier 通常与 @Autowired 结合使用,且置于成员变量类型的前面。@Qualifier(value=“screen”) 中的 screen是 Bean 的名称,可简写为 @Qualifier(“screen”)所以,这里自动注入的策略就从byType转变成byName了。

3、@Resource


@Resource 的作用相当于 @Autowired,只不过 @Autowired 按 byType 自动注入,而@Resource 默认按 byName 自动注入,当Type和Name都找不到的时候会报错。

@Resource和@Autowired的区别

  1. 都是用来自动装配的,都可以放在属性字段上
  2. @Autowired 通过byType的方式实现
  3. @Resource默认通过byname的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错

4、@Component


虽然我们可以通过 @Autowired 或 @Resource 在 Bean 类中使用自动注入功能,但是还是需要在XML文件中定义 。那么能否也通过注解来定义Bean,从XML配置文件中完全移除Bean定义的配置呢?通过Spring 2.5 提供的 @Component注解就可以达到这个目标。

Java代码审计前置知识——Spring框架AOP和IoC_第7张图片

 5、衍生的注解


@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层

  • dao 【@Repository】
  • service 【@Service】
  • controller 【@Controller)

这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配Bean

举个例子——使用Java的方式配置Spring


完全不使用xml配置

1、编写一个实体类 User

package com.example.pojo;
 
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
// 这里这个注解的意思,就是说明这个类被Spring接管了,注册到了容器中 
@Component
public class User {
    private String name;
 
    public String getName() {
        return name;
    }
 
    // 属性注入值
    @Value("John")
    public void setName(String name) {
        this.name = name;
    }
 
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}

2、新建一个config配置包,编写一个MyConfig配置类

package com.example.config;
 
import com.example.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
// 这个也会Spring容器托管,注册到容器中,因为他本米就是一个@Component 
// @Configuration表这是一个配置类,就像我们之前看的beans.xml,类似于标签
@Configuration
@componentScan("com.example.pojo")  // 开启扫描
@Import(MyConfig2.class)    // 导入别的bean配置
public class MyConfig {
 
    // 注册一个bean , 就相当于我们之前写的一个bean 标签 
    // 这个方法的名字,就相当于bean 标签中的 id 属性 ->getUser
    // 这个方法的返同值,就相当于bean 标签中的class 属性 ->User
    @Bean
    public User getUser(){
        return new User();    // 就是返回要注入到bean的对象!
    }
}

3、测试

@Test
public void test(){
    // 如果完全使用了配置类方式去做,我们就只能通过 Annotationconfig 上下文来获取容器,通过配置类的class对象加载!
    ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(MyConfig.class);    // class对象
    User user = (User) applicationContext.getBean("getUser");    // 方法名getUser
    System.out.println(user.getName());
}

关于这种Java类的配置方式,在 SpringBoot 和 SpringCloud中随处可见

总结对比


xml 与注解:

  • xml更加万能,适用于任何场合,维护简单方便
  • 注解不是自己类使用不了,维护相对复杂

xml 与注解最佳实践:

  • xml用来管理bean;
  • 注解只负责完成属性的注入
  • 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的支持


 

 

0x02 代理模式


 

2.1 静态代理


先以中介代理引入这个概念

Java代码审计前置知识——Spring框架AOP和IoC_第8张图片

 

1、抽象角色:一般由接口或者抽象类来解决

//租房
public interface Rent {
public void rent();
}

2、真实角色:被代理的角色

//房东
public class Host implements Rent {
public void rent() {
System.out.println("房东要出租房子!");
    }
}

3、代理角色:代理真实角色后,一般会做一些附属操作

public class Proxy implements Rent{
private Host host;
public Proxy(){
public Proxy(Host host) {
this.host = host;
} //传入房东

public void rent() {
 seeHouse();
 host.rent(); //根据传入的房东实现租房操作
 hetong(); 
 fare();}

public void seeHouse(){
System.out.println("中介带你看房")}//看房
public void hetong(){
System.out.println("签租赁合同");}//收中介费
public void fare(){
System.out.println("收中介费");}

}

4、客户访问

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

分析:在这个过程中,你直接接触的就是中介,就如同现实生活中的样子,你看不到房东,但是你依旧租到了房东的房子通过代理,这就是所谓的代理模式。

优点

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
  • 公共也就就交给代理角色!实现了业务的分工。
  • 公共业务发生扩展的时候,方便集中管理。

缺点 :

  • 一个真是类对应一个代理角色,代码量翻倍,开发效率降低 .

2.2  动态代理


Java代码审计前置知识——Spring框架AOP和IoC_第9张图片

  • 动态代理的角色和静态代理的一样 .
  • 动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
  • 动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
  1. 基于接口的动态代理—-JDK动态代理 【我们这里使用】
  2. 基于类的动态代理—-cglib
  3. java字节码实现—- javasist 来生成动态代理

JDK的动态代理需要了解两个类

核心 : InvocationHandler 调用处理程序类和 Proxy 代理类

  • InvocationHandler:调用处理程序
public interface InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口,每个代理实例都有一个关联的调用处理程序。

Object invoke(Object proxy, 方法 method, Object[] args);

当在代理实例上调用方法的时候,方法调用将被编码并分派到其调用处理程序的invoke()方法。

参数

  • proxy – 调用该方法的代理实例
  • method -所述方法对应于调用代理实例上的接口方法的实例。方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口
  • args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integerjava.lang.Boolean 

Proxy : 代理

public class Proxy extends Object implements Serializable

Proxy 提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口。 代理实例是代理类的一个实例。

public static Object newProxyInstance(ClassLoader loader, 类[] interfaces, InvocationHandler h) throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序

参数

  • loader – 类加载器来定义代理类
  • interfaces – 代理类实现的接口列表
  • h – 调度方法调用的调用处理函数

代码实现

(上面概念不太好理解,通过代码示例会容易接受)


接口 Rent.java

//接口
public interface Rent {
    public void rent();
 
}

接口Host实现类 Host.java

//接口实现类
public class Host implements Rent{    
    public void rent() {
        System.out.println("房东要租房子");
    }
}

代理角色的处理程序类 ProxyInvocationHandler.java

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;
    }
 
    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                rent.getClass().getInterfaces(),this);
    }
 
    // 处理代理实例并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        // 动态代理的本质,就是使用反射机制实现的
        // invoke()执行它真正要执行的方法
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
 
    //看房
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    //收中介费
    public void fare(){
        System.out.println("收中介费");
    }
}

总结三部曲:

  1. 被代理的接口,要有set方法(这里就是rent)
  2. 生成代理类,只需要改被代理的角色(rent)
  3. 处理代理实例,并防回结果,修改method.invoke(角色,args)

用户Client.java

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

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

优点

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

0x03 AOP


3.1 什么是AOP


AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

Java代码审计前置知识——Spring框架AOP和IoC_第10张图片

 3.2  AOP在Spring中的使用


提供声明式事务,允许用户自定义切面

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

Java代码审计前置知识——Spring框架AOP和IoC_第11张图片

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

Java代码审计前置知识——Spring框架AOP和IoC_第12张图片

 

3.3 使用Spring实现AOP


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


   org.aspectj
   aspectjweaver
   1.9.4

方法一:通过 Spring API 实现


主要是Spring API 接口实现

  • 首先编写我们的业务接口和实现类

UserService.java

package service;
public interface UserService {   
        public void add() ;
        public void delete() ;
        public void query() ;
        public void update();
}

UserService 的实现类 UserServiceImpl.java

package service;
 
public class UserServiceImpl implements UserService {
 
    public void add() {
        System.out.println("add增");
    }
    public void delete() {
        System.out.println("delete删");
    }
    public void update() {
        System.out.println("update改");
    }
    public void query() {
        System.out.println("query查");
    }
}
  • 然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
   //method : 要执行的目标对象的方法
   //objects : 被调用的方法的参数
   //Object : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
    }
}
public class AfterLog implements AfterReturningAdvice {
    //returnValue 返回值
    //method被调用的方法
    //args 被调用的方法的对象的参数
    //target 被调用的目标对象
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回的结果为"+returnValue);
    }
}
  • 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 


 
    
    
    
    
 
    
    
        
        
        
        
        
    
 
  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.search();
}

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

方法二:自定义类来实现Aop


主要是切面定义,目标业务类不变依旧是userServiceImpl

  • 写我们自己的一个切入类
public class DiyPointCut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 去spring中配置


 


    
    
        
        
        
    
  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
}

方法三:使用注解实现

  • 编写一个注解实现的增强类
@Aspect // 标注这个类是一个切面
public class AnnotationPointCut {
    @Before("execution(* com.example.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("---------方法执行前---------");
    }
 
    @After("execution(* com.example.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("---------方法执行后---------");
    }
 
    //在环绕增强中,我们可以给定一个参数,代表我们要获取切入的点
    @Around("execution(* com.example.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        System.out.println("签名:"+jp.getSignature());    // 获取签名
        //执行目标方法proceed
        Object proceed = jp.proceed();
        System.out.println("环绕后");
        System.out.println(proceed);
    }
}
  • 在Spring配置文件中,注册bean,并增加支持注解的配置


  • 测试
@Test
public void test(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 动态代理代理的是接口!!
    UserService userService = (UserService) context.getBean("userService");
    userService.add();
}

Java代码审计前置知识——Spring框架AOP和IoC_第13张图片

 我计划把MyBatis和SQLi一起总结,今天就先到这里。

参考资源:

6、IOC创建对象方式_哔哩哔哩_bilibili

你可能感兴趣的:(Javaee,java,开发语言,java-ee,spring,mybatis)