Spring

Spring

IOC

  • 传统方式实现类的实现

  • UserService类


package com.Test0415.springDemo;
/**
 * 用户管理的业务层接口
 * @author admin
 *
 */
public interface UserService {
	public void save();
}


  • UserServiceImple 即上述的实现类

package com.Test0415.springDemo;
/**
 * 用户管理的业务层实现类
 * @author admin
 *
 */
public class UserServiceImpl implements UserService {

	@Override
	public void save() {
		// TODO Auto-generated method stub
		System.out.println("UserService is running...  powered by jdbc");
	}
	
}

  • UserServiceHibernate 类,即UserService的另一个实现类

/**
 * 用户管理的业务层实现类
 * @author admin
 *
 */
public class UserServiceHibernateImpl implements UserService {

	@Override
	public void save() {
		// TODO Auto-generated method stub
		System.out.println("UserService is running...  powered by Hibernate");
	}
	
}


@Test
/**
* 传统方法调用
*/
public void demo1() {
    // TODO Auto-generated method stub
    UserService userService = new UserServiceImpl();
    userService.save();
}

  • 试想一下,如果你想把类UserService的实现类换成UserServiceHibernate类,这里还好只有一个实现
  • 只用将 UserService userService = new UserServiceImpl(); 换成 UserService userService = new UserServiceHibernateImpl();
  • 一旦实例化对象过多,那么修改起来就是个灾难

如果底层的实现类切换了,需要修改源代码,能不能不修改程序源代码对程序进行扩展?

  1. 接口编程

    • 原本是面向接口编程,先编写一个接口类,再可以书写多种实现类,以此可以实现多态。
    • 接口和实现类有耦合(切换底层实现类,需要修改源代码才能剩下扩展)
  2. 工厂模式

    • 通过静态方法返回 实现类
    • 通过工厂方法的不同静态方法,可以返回多种实现类
    • 原本面向接口需要修改的许多代码,这里只需要修改一个工厂类就行了
  3. 工厂+反射+配置文件:实现解耦

Spring

IOC的实现原理

将接口和实现类交给Spring实现

  • src 中新建一个xml文件,作为配置文件,默认为applicationContext.xml
  • ./spring-framework-4.3.7.RELEASE/spring-framework-reference/html/xsd-configuration.html中41.2.12 the beans schema
  • 上述为Spring依赖包中对 applicationContext.xml 的约束条件

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 上述为约束,必须带有 -->
	<!-- Spring入门的配置 -->
	<bean id="UserDao" class="com.Test0415.springDemo.UserServiceImpl"></bean><!-- 之前书写的类的实现类 -->

</beans>

  • 实现代码

@Test
/**
* 通过Spring IOC 
*/
public void demo2() {
    // 创建Spring的工厂
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = (UserService)applicationContext.getBean("UserDao");
    userService.save();
}

IOC和DI

  • IOC:控制反转,将对象的创建权反转给Spring

  • DI:依赖注入(必须有IOC的环境),Spring管理这个类的时候将类的依赖的属性注入(设置)进来

  • 面向对象中有:

    • 依赖
    • 聚合
    • 即成
  • 何为依赖?

    • 通过下述代码可以说 B 依赖了 A

class A{

};

class B{
    public void xxx(A a){

    }
};

  • 何为继承?
    • 通过下述代码可以说 B 继承了 A

class A{

};

class B extends A{

}

  • 何为依赖注入?
    • 前提要求是A,B类必须已经交给Spring管理了

B 类需要依赖 A类的时候,再Spring里面可以把A的值设置下

实例

  • 我们给UserServiceImpl类添加一个 String name;属性
  • 相当于UserServiceImpl类依赖的 String 类

package com.Test0415.springDemo;
/**
 * 用户管理的业务层实现类
 * @author admin
 *
 */
public class UserServiceImpl implements UserService {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	@Override
	public void save() {
		// TODO Auto-generated method stub
		System.out.println("UserService is running..." + name);
	}
	
}

  • 传统方式设置name
    • 这种方法不能 面向对象编程
    • 同时必须调用方法来设置

public void demo1(){
    // UserService user = new UserServiceImpl();
    // UserService 没有 serName 方法,所以不能接口编程
    UserServiceImple user = new UserServiceImpl();
    user.setName("test");
    user.save();
}

  • 交给Spring管理

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 上述为约束,必须带有 -->
	<!-- Spring入门的配置 -->
	<bean id="UserDao" class="com.Test0415.springDemo.UserServiceImpl">
        <property name="name" value= "test" /> <!-- 通过Spring的xml设置参数值 -->
    </bean><!-- 之前书写的类的实现类 -->

</beans>

Spring的工厂

  • Spring提供了两个工厂类
    • BeanFactory : 老版本的工厂类
    • ApplicationContext : 新版本的工厂类

Spring

ApplicationContext 继承了 BeanFactory

BeanFactory

  • BeanFactory:在调用getBean的时候,才会生成类的实例

ApplicationContext

  • ApplicationContext:加载配置文件的时候,就会将Spring管理的类都实例化
  • ApplicationContext有两个实现类:
    • ClassPathXmlApplicationContext:用来加载类路径下的配置文件(就是你的src文件夹下的applicationContext.xml)
    • FileSystemXmlApplicationContext:用来加载文件系统下的配置文件(硬盘内的applicationContext.xml)

FileSystemXmlApplicationContext的使用


public void demo3() {
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("/Users/admin/Destop/applicationContext.xml");
    // 可以指定路径
    UserService userService = (UserService)applicationContext.getBean("UserDao");
    userService.save();
}

Spring的配置

XML的提示的配置

  • Scheme的配置

复制 http://www.springframework.org/schema/beans/spring-beans.xsd
在Eclipse的Preference中xml catalog,选择 add 复制到 key 中
key type选择为 Scheme Location
location中选择下载的Spring Scheme 中 /bean/中的对应Spring版本

Spring

Bean属性相关配置

  • id:使用了约束中的唯一约束。不能出现特殊字符

  • name:没有使用唯一约束(理论上,name可以出现重复的,实际上不能出现重复的)。可以出现特殊字符

  • class:生成类的全路径

  • bean标签的生命周期配置

    • destroy-method:等于一个对应方法的名字,用在工厂对象关闭时销毁时调用(Bean是单例创建的并且工厂关闭)
    • init-method:等于一个对应方法的名字,用于在实例化的时候调用这个函数

public class UserServiceImpl implements UserService {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void setup() {
		System.out.println("该类被创建了");
	}
	public void Destroyable() {
		System.out.println("该类被销毁了");
	}
	@Override
	public void save() {
		// TODO Auto-generated method stub
		System.out.println("UserService is running..." + name);
	}
	
}


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Spring入门的配置 -->
	<bean id="UserDao" class="com.Test0415.springDemo.UserServiceImpl" init-method="setup" destroy-method = "destory">
		<property name="name" value="test" />
	</bean><!-- 之前书写的类的实现类 -->
	
	
</beans>

Bean的作用范围控制

  • scope属性:Bean的作用范围
    • singleton:默认,单例模式创建对象
    • prototype:多例模式
    • requets:应用在web中,Spring创建类,将这个类存在request范围中
    • session:应用在web中,Spring创建类,将这个类存入session范围中
    • globalsession:应用在web中,必须在prolet环境下使用(存在这里后,子网站也可以使用)

<bean id="UserDao" class="com.Test0415.springDemo.UserServiceImpl" init-method="setup" >
		<property name="name" value="test" />
</bean><!-- 之前书写的类的实现类 -->


@Test
public void demo1(){
    ApplicationContext applicationContext = new FileSystemXmlApplicationContext("/Users/admin/Destop/applicationContext.xml");
    // 可以指定路径
    UserService userService = (UserService)applicationContext.getBean("UserDao");
    System.out.println(userService);
    UserService userService2 = (UserService)applicationContext.getBean("UserDao");
    System.out.println(userService2);
}

通过上述代码运行发现:setup初始化函数只使用以此,且两次println的值相同,说明地址一样
可以证明,默认情况下scope=”singleton“单例模式

Spring属性注入

把这个类依赖的属性的值注入进去

  • 方法1-构造方法

public class User{
    private String name;
    private String password;
    public User(String name, String password){
        this.name = name;
        this.password = password;
    }
}

  • 方法2-set方法

public class User{
    private String name;
    public void setName(String name){
        this.name = name;
    }
}

  • 接口注入

public interface Injection{
    public void setName(String name);
}

public class User implements Injection{
    pricate String name;
    public void setName(String name){
        this.name = name;
    }
}

Spring 支持前两种

  • 构造方法属性注入
public class User{
    private String name;
    private String password;
    public User(String name, String password){
        this.name = name;
        this.password = password;
    }
}

<bean id="User" class="***.**.**">
    <constructor-arg name="name" value="test" />
    <constructor-arg name="password" value="123456" />
</bean>
  • set方法的属性注入
<bean id="User" class="**.***.**">
    <property name="name" value="test" />
    <property name="password" value="123456">
</bean>
  • 如果set方法中注入的是一个对象
    • value是用于给普通变量赋值
    • ref 用于给其他的类设置id或name。
public class Car{
    String name;
    int price;
};

public class User{
    Car car;
    String name;
    String age;
}

<bean id="Car" class="**.***.**">
    <property name="name" value="大众"/>
    <property name="price" value="80000">
</bean>

<beann id="User" class="**.***.**">
    <property name="name" value="test" />
    <property name="age" value="20"/>
    <property name="car" ref="Car">
</bean>

P名称空间的属性注入(Spring2.5之后的版本)

  • 通过P名称空间完成属性注入

    • 普通属性写法: P:属性名=“值”
    • 对象属性: P:属性名-ref=“值”
  • 必须先引入P名称空间

public class Car{
    String name;
    int price;
};

public class User{
    Car car;
    String name;
    String age;
}

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- Spring入门的配置 -->
    <beann id="Car" class="**.***.**" p:name="大众" p:price="80000"></bean>
	
</beans>

SpEl的属性注入(Spring3.0版本之后提供)

  • SpEL:Spring Expression Language,Spring的表达式语言

    • 语法:
    • #{SpEL}
  • 整数

    • <property name=“count” value="#{5}" />
  • 小数

    • <property name=“frequency” value="#{89.7}" />
  • 科学计数法

    • <property name=“capacity” value="#{1e4}" />
  • String类型的字面值可以使用单引号或者双引号作为字符串界定符。

    • <property name=“name” value="#{‘moonlit’}" />或者
  • 还可以使用布尔值true和false。

    • <property name=“enabled” value="#{true}" />
  • 引用Bean,属性和方法

  • 引用其他对象

    • 通过Bean的id,进行注入
<bean id="test1" value="**.***.***" />
<bean ..>
    ..
    <property name="instruct" value="#{test1}"/>
    ..
</bean>
  • 引用其他对象的属性
<bean id="car" class="**.***.***"> 
    <property name="song" value="#{kenny.song}" />
</bean>

kenny是Bean id 而 song 是属性的名字,相当于用kenny对象的song属性为自己的car对象赋值

  • 调用其他方法
<property name="song" value="songSelector.selectSong()">

调用了Bean Id 为 songSelector 的对象的selectSong() 方法,并将放回值植入到song属性中

  • 复杂类型的注入(map,list等)

<bean id="helloServerNoWithArgs" class="com.ctgu.***.HelloWorldImpl" />
<!-- 通过对象的set方法设置值,name为参数名,value为设置的值 -->
  <property name = "sets">
    <set>
      <value>1</value>
      <value>2</value>
      <value>3</value>
      <value>4</value>
    </set>
  </property>
<!-- 通过上述方法对Set对象赋值 -->

  <property name="list">
    <list>
      <value>1</value>
      <value>2</value>
      <value>3</value>
      <value>4</value>
    </list>
  </property>
<!-- 通过上述方法对List对象赋值 -->

  <property name="strings">
    <array>
      <value>1</value>
      <value>2</value>
      <value>3</value>
      <value>4</value>
    </array>
  </property>
<!-- 通过上述方法对数组赋值 -->

  <property name="map">
    <map>
      <entry key="a" value="b">   </entry>
      <entry key="b" value="b">   </entry>
      <entry key="c" value="b">   </entry>
      <entry key="d" value="b">   </entry>
    </map>
  </property>
<!-- 通过上述方法对Map对象赋值 -->

IoC容器装配Bean_基于注解配置方式

Bean的定义(注册)–扫描机制

  • 配置注解Bean的扫描
    • base-package:设置为配置进来的包
<?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" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 配置spring的注解扫描 -->
	<!-- 开启注解扫描 -->
	<context:annotation-config />
	<!-- 配置bean扫描的目录 -->
	<!-- base-package:自动扫描配置进来的包和子包下面的所有的bean(带springbean注解的pojo) -->
	<context:component-scan base-package="com.Test**.***.**"></context:component-scan>
</beans>
  • 使用注解
    • 使用@Component注解来标注的bean
    • 如果注解后面什么都不加,默认的bean的名字就是类名的小写
package cn.Test0415.newBean;

import org.springframework.stereotype.Component;

@Component //代表该pojo可以背Spring来管理
public class CustomerDAO {
	public void save() {
		System.out.println("持久层方法被调用");
	}
}

@Component(value="customerTest") //value值可以用来定义bean的名字
public class CustomerTest {
	public void save() {
		System.out.println("持久层方法被调用");
	}
}

@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    CustomerService customerService = applicationContext.getBean("customerService");
    customerService.save();
}
  • 除了@Component之外,还有三个衍生注解:@Repository,@Service, @Controller
  • 相比于@Component,三个衍生注解用的机会更多

三个衍生注解具有分层意义(分层注解)

  • @Repository:用于对DAO实现类进行标注
  • @Service:用于对Service实现类进行标注
  • @Controller:用于对Controller实现类进行标注

写法与@Component相似

注解的依赖注入

  • Spring3.0后,提供@Value注解,可以完成简单的数据注入
@Service("customerService")
public class CustomerService{
    //简单数据类型的注入
    @Value("Rose")
    private String name;
    // 等价于 private String name="Rose"
    
    //客户保存
    public void svae(){
        System.out.println("业务层....");
    }
}
  • 复杂对象的注入
    • 注解实现属性依赖注入,将注解加在set*** 方法上或者属性定义上
  1. 使用@Value结合SpEL
class test{
    @Value("#{customerDao}")
    private CustomerDao customerDao;

    public void save(){
		System.out.println("业务层....");
    }    
}

  1. 使用@Autowired结合@Qualifier (单独使用@Autowired按照类型注入)

方法1

//通过autowried,默认通过Bean类型注入
@Autowired
private CustomerDao customerDao;

方法2

//根据bean的名字注入,一旦指定了这个注解,就不会使用类型注入
// 但Qualifier 必须与 @Autowired一起使用
@Autowired
@Qualifier("customerDao")
private CustomerDao customerDao;

方法3 - JSR-250标准

  • 提供了@Resource
// JSR-250标准
@Resource//默认是根据类型传入
private CustomerDao customerDao;

@Resource(name="customer")
private CustomerDao customerDao;

JSR-330标准

  • 需要引入额外的jar包

  • 提供了@Inject(有点麻烦)

@Inject //默认按照类型注入
private CustomerDao customerDao;
@Named("CustomerDao")
private CustomerDao customerDao;

bean的初始化和作用域—注解方式

初始化

  • Spring初始化Bean或者销毁Bean时,有时需要做一些处理工作
  • 因此Spring 可以再创建和拆解Bean的时候调用Bean的两个周期方法
<bean id="foo" class="**.***.**" init-method="setup" destroy-method="desroy">
</bean>
  • 当bean被载入到容器时调用setup函数
    • 注解方法:@PostConstruct
    • 初始化
    • 相当于init-method指定初始化方法
  • 当bean从容器中删除的时候调用destroy
    • 注解方法:@PreDestroy
    • 销毁
    • 相当于detroy-method指定对象销毁方法
@Componenet//先添加到bean中
public class LifeRecycleBean{
    public LifeRecycleBean(){
        System.out.printfln("bean构造的时候...");
    }
    @PostConstruct //相当于<bean>中的init-method
    public void initBean(){
        System.out.println("initBean...");
    }
    @PreDestroy//相当于<bean>中的destryo-method
    public void destroyBean(){
        System.out.println("destroyBean...");
    }
}

作用域

  • 使用注解@Scope
@Componenet//先添加到bean中
@Scope("singletons")
public class LifeRecycleBean{
    public LifeRecycleBean(){
        System.out.printfln("bean构造的时候...");
    }
}

XML和注解的混合配置

早期更多使用XML,后期使用注解,故很多工程是XML和注解混合使用

  • 一个项目中xml和注解都有(@Authwired能够属性注入,但是2.5之前不能用注解定义Bean)
    • Spring2.0就有@Authwired
    • Spring2.5之后才有@Component
    • XML完成Bean定义
    • 注解完成Bean属性注入

所以我们只需要,按照以前在XML中定义Bean,同时开启注解,就能混合使用

<!-- 开启注解 -->
<context:annotation-config />

<!-- 定义bean -->
<bean id="customer" class="**.**.**" /> 

Spring的Web整合

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
    //response.getWriter().append("Served at: ").append(request.getContextPath());
    //Web层调用业务层
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    HelloService helloService = (HelloService)applicationContext.getBean("HelloServiceImpl");
    helloService.sayHello();
}

直接new ClassPathXmlApplicationContext()有什么缺点?

  • 缺点:在创建Spring容器的同时,需要对容器汇中对象初始化。而每次初始化容器的时候都创建了新的容器对象,消耗资源,降低性能。
  • 解决思路:保证容器对象只有一个
  • 解决方案:将Spring容器绑定到Web Servlet容器上,让Web容器来管理Spring容器的创建和销毁
  • 分析:
    • ServletContext在Web服务器运行过程中是唯一的
    • 其初始化的时候,会自动执行ServletContextListerner监听器(用来监听上下文的创建和销毁)
      • 编写一个ServletContextListener监听器,在监听ServlerContext创建的时候,创建Spring容器
      • 将容器放到ServletContext的属性中保存(setAttribute(Spring容器的名字,Spring容器对象)
    • 我们无需手动创建该监听器,因为Spring提供了一个叫ContextLoadListener的监听器,它位于Spring-web.jar中

第一步

  • 在web.xml中配置Spring的核心监听器
<!-- 配置Spring的核心监听器 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
</listener>
<!-- Web上下文的全局参数 -->
<context-param>
    <param-name>contextConfigLocation</param-name>
    <!-- value:核心配置文件 -->
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
  • Spring会自动在ClassPath中找applicationContext.xml文件
  • 当配置完成之后,通过下一方法取得ApplicationContext
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
    //Web层调用业务层
    ApplicationContext applicationContext = (ApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
    HelloService helloService = (HelloService) applicationContext.getBean("HelloServiceImpl");
    helloService.sayHello();
}

通过上述配置的方法,Spring会把applicationContext加载到ServletContext中,每次访问通过getServletContext即可获得ApplicationContext对象
该配置之后,每次获得的aplicationContext都是同一个对象

Spring的junit测试集成

  • Spring提供test.jar 可以整合junit
  • 好处:可以简化测试代码,不用手动创建上下文

步骤:

  1. 导入junit开发包
  2. 导入Spring-test.jar
  3. 通过@RunWith注解,使用junit整合Spring
//测试用例初始化的时候,会自动创建Spring容器对象
// 1.Spring整合junit
@RunWith(SpringJUnit4ClassRunner.class)//Junit的注解
// 2. 配置Spring核心配置文件
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class Test{
    void sayHello(){
        System.out.println("hello world");
    }
}
  1. 通过@Test可以对某个方法,功能进行运行。通过这种方法可以只运行sayHello方法
public class Test{
    @Test
    void sayHello{
        System.out.println("hello test...");
    }
}

AOP

什么是AOP

  • AOP(Aspect Oriented Programing):面向切面编程
  • AOP采用横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视,事务管理,安全检查,缓存)
  • 通过给原来的对象创建代理对象,在不修改原对象的代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务进行增强

AOP的应用场景

  1. 记录日志
  2. 监控方法运行时间(监控性能)
  3. 权限控制
  4. 缓存优化(第一次调用查询数据库,将查询结果放入内存对象,第二次调用,直接从内存对象返回,不需要查询数据库)
  5. 事务管理(调用方法前开启事务,调用方法后关闭事务)

编程的两种方法

  1. Spring AOP 使用纯Java实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强代码
  2. AspectJ是一个基于Java语言的AOP框架,Spring2.0开始,Spring AOP 运入对AspectJ的支持

Spring1.2 开始支持AOP编程,编程比较复杂,更好学习Spring内置传统AOP代码
Spring2.0 开始支持第三方AOP框架(AspectJ),实现另一种AOP编程,比较推荐

AOP编程的相关术语

Spring

  1. Joinpoint:连接点:所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
  2. Pointcut:切入点:所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
  3. Advice:通知/增强:所谓通知或增强是指拦截到Joinpoint之后所要做的事情就是通知,通知分为 前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  4. Introduction:引介:引介是一种特殊的通知,再不修改类代码的前提下,Introduction可以在运行期间为类动态地添加一些方法或Field
  5. Target:目标对象:代理的目标对象
  6. Weaving:织入:是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
  7. Proxy:代理:一个类被AOP织入增强之后,就产生一个结果类
  8. Aspect:切面:是切入点和通知(引介)的结合
  • 动态代理:在虚拟机内部,运行的时候,动态生成代理类(运行时生成,runtime生成),并不是真正存在的类
  • 静态代理:存在代理类

底层实现机制

  • jdk的动态代理
  • cdlib动态代理

JDK动态代理

  • JDK动态代理,针对目标对象的接口进行代理,动态生成接口的实现类(必须有接口

过程要点

  1. 必须对接口生成代理
  2. 采用Proxy对象,通过newProxyInstance方法对目标创建代理对象,该方法接受三个参数:
    1. 目标对象加载器
    2. 目标对象实现的接口
    3. 代理后的处理程序InvocationHandler
    • 使用Proxy提供newProxyInstance方法对目标对象接口进行代理
  3. 实现InvocationHandler接口中invoke方法,在目标对象每个方法中调用时,都会执行invoke

Spring

写法1

  • 匿名函数的方法创建一个InvocationHandler对象,实现invoke方法
package com.Test.AOP.Proxy;

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

public class JDKProxyFactory {
    public Object getProxyObject(Object target) {//获取代理对象
        // 第一个参数:目标对象类加载器
        // 第二个参数:目标对象对接口,回调的接口
        Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass(), new InvocationHandler() {
			
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // TODO Auto-generated method stub
                return null;
            }
        })
    
        return null; 
	}

写法2

  • 实现接口的方法,实现invoke函数
package com.Test.AOP.Proxy;

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

public class JDKProxyFactory implements InvocationHandler{
    public Object getProxyObject(Object target) {//获取代理对象
    	// 第一个参数:目标对象类加载器
    	// 第二个参数:目标对象对接口,回调的接口
    	return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this.invoke(proxy, method, args) );
    
    }   
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	// TODO Auto-generated method stub
    	return null;
    }
}

手动书写一个Proxy代理

  • CumstoerService
package com.Test.AOP.Proxy;
//客户接口类
public interface CumstoerService {
	public void save();
	public void update();
}
  • CumtoerServiceImpl
    • CumtoerService的实现类
package com.Test.AOP.Proxy;

public class CumtoerServiceImpl implements CumstoerService {

	@Override
	public void save() {
		// TODO Auto-generated method stub
		System.out.println("save is running");
	}

	@Override
	public void update() {
		// TODO Auto-generated method stub
		System.out.println("update is running");
	}

}
  • JDKProxyFactory 代理工厂类
package com.Test.AOP.Proxy;

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


public class JDKProxyFactory implements InvocationHandler{
    // 目标对象
    private Object target;

    public JDKProxyFactory(Object targegt) {
    	// TODO Auto-generated constructor stub
    	this.target = targegt;
    }

    public Object getProxyObject() {//获取代理对象
        // 第一个参数:目标对象类加载器
        // 第二个参数:目标对象对接口,回调的接口

        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    @Override
    // 增强代码都在这里实现
    // 第一个参数:代理对象 第二个参数:获取目标原来的方法 第三个参数:原来目标对象方法的参数
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    	// 调用原来对象的方法
    	// 在方法调用之前增强
    	System.out.println("before invoke");
    
    	// 第一个参数:目标对象  第二个参数:目标对象的参数
    	// 因为第一个参数是目标对象,而目标对象在其他函数,所以定义了 private Object 
    	Object object = method.invoke(this.target, args);
    
        // 在方法调用之后增强
        System.out.println("after invoke");
    
        return object;// 讲原来的目标对象返回
    }
}
  • 测试代码
//使用JDK动态代理,增强代码
@Test
public void testAfter() {
    // new一个对象,然后对对象生成代理对象,调用代理对象对方法
    CumstoerService targegt = new CumtoerServiceImpl();//目标对象,要增强对目标
    //生成代理类
    JDKProxyFactory jdkProxyFactory = new JDKProxyFactory(targegt);
    CumstoerService cumstoerService = (CumstoerService) jdkProxyFactory.getProxyObject();
    //调用接口的方法
    cumstoerService.save();
    cumstoerService.update();
}
  • 结果输出是这样子的:
    • before invoke
    • save is running
    • after invoke
    • before invoke
    • update is running
    • after invoke

Cglib动态代理

  • Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理

  • CGLIB(Code Generation Library)是一个开源项目,是一额强大的,高性能,高质量的code生成类库

  • 可以在运行期扩展Java类和实现Java接口

  • 该方法需要相应的jar包,但不需要导入,因为Spring core包已经包含cdlib,而且同时包含Cglib依赖的asm包

写一个没有接口的类

package com.Test.AOP.Proxy;

//产品类:没有接口
public class ProduckService {
	public void save() {
		System.out.println("save is running");
	}
	
	public void update() {
		System.out.println("update is running");
	}
}

  • 生成cglib代理的工厂类
package com.Test.AOP.Proxy;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

// Cglib动态代理工厂
public class CglibProxy implements MethodInterceptor {
	
	private Object target;
	public CglibProxy(Object target) {
		this.target = target;
	}
	
	public Object getProxyProject() {
		//1 使用代理生成器
		Enhancer enhancer = new Enhancer();
		//2 在增强其上设置两个属性
		//2.1 要代理的类
		enhancer.setSuperclass(target.getClass());
		//2.2 设置回调接口,接口中可以写增强的代码
		enhancer.setCallback(this);
		//3. 创建代理子对象
		return enhancer.create();
	}

	//增强的代码写在这里
	//第一个参数:代理对象 第二个参数:目标方法 第三个参数:目标参数 第四个参数:代理之后的方法
	@Override
	public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {
		// TODO Auto-generated method stub
		//执行原代码之前
		System.out.println("before invoke");
		// 执行原来目标的方法
		Object object = arg1.invoke(target, arg2);
		
		//执行原代码之后
		System.out.println("after invoke");
		return object;
	}
	
}

  • 测试代码
@Test
public void CglibProxy() {
    //确定目标,生成代理对象,调用代理对象方法
    ProduckService target = new ProduckService();
    //生成代理对象
    CglibProxy cglibProxy = new CglibProxy(target);
    ProduckService produckService = (ProduckService) cglibProxy.getProxyProject();
    produckService.save();
    produckService.update();
}
  • 代码输出
before invoke
save is running
after invoke
before invoke
update is running
after invoke