Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib

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

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

1.导入Jar包
Spring 注解开发相比较XML而言需要额外导入spring-aop-4.2.4.RELEASE.jar
在这里插入图片描述
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第1张图片
2.编写sercie和dao
xml做法 : ,用的方式创建对象
注解做法 : spring2.5引入 @Component 注解 如果放置到类的上面,相当于在spring容器中定义

创建CustomerService.java类,并在类上添加注解

/*
 * @Component注解放置到类上
 * 相当于在spring容器中定义:
 * 其中id属性默认bean的名字是类名的小写
 * --------------------------------------
 * @Component(value="customerService") //自定义bean的名字
 * 相当于spring容器中定义:
 */
@Component(value="customerService")
public class CustomerService {
	//保存业务方法
	public void save(){
		System.out.println("CustomerService业务层被调用了...");
	}
}

配置注解开启和注解Bean的扫描。
配置的示例如下:配置applicationContext.xml



	
	
	
	
	

测试:

public void test(){
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");		
	CustomerService customerService = (CustomerService)ac.getBean("customerService");
	customerService.save();
}

扩展优化:
1.注解扫描配置
在配置包扫描的时候,spring会自动开启注解功能,所以,注解开启功能可以不配置。
即去掉:,因为 具有 作用 !

2.衍生注解的问题
实际开发中,使用的是@Component三个衍生注解(“子注解”)
子注解的作用:有分层的意义(分层注解)。

Spring3.0为我们引入了组件自动扫描机制,它可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中管理。除了@Component外,Spring提供了3个功能基本和@Component等效的注解功能介绍
@Service用于标注业务层组件、(如Service层)
@Controller用于标注控制层组件(如struts中的action层)
@Repository用于标注数据访问组件,(如DAO层组件)。
而@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

通过@Service和@Controller和@Repository来注解

@Service(value="customerService")
public class CustomerService {
	public void save(){
		System.out.println("CustomerService业务层被调用了...");
	}
}

@Repository(value="customerDao")
public class CustomerDao {
	public void save(){
		System.out.println("CustomerDao层被调用了...");
	}
}

1.2 Bean属性的依赖注入

1.简单数据类型依赖注入(了解)
通过@value注解,可以完成简单数据的注入

@Service(value="customerService")
public class CustomerService {
	
	//简单类型的成员变量
	@Value("Rose")    //参数的值简单类型
	private String name="Jack";
	
	public void save(){
		System.out.println("CustomerService业务层被调用了...");
		System.out.println(name);
	}
}

2.复杂类型数据依赖注入
将dao类对象注入到service类进行应用

1.使用@value结合spEL

@Service(value="customerService")
public class CustomerService {
	
	@Value("#{customerDao}")
	private CustomerDao customerDao;
	
	public void save(){
		System.out.println("CustomerService业务层被调用了...");
		customerDao.save();
	}
}

前提:service类和dao类都被Spring管理,spEL表达式中的字符串是bean节点的id/name属性

2.使用@Autowired 结合 @Qualifier(常用)

(1)_单独使用@Autowired ,表示按照类型注入,会在spring容器中查找CustomerDao的类型,对应的,class的属性值,如果找到,就可以匹配

//默认按照类型注入
@Autowired	
private CustomerDao customerDao;

存在问题:1.多个类实现同一个接口,此时就会随机找一个实现类赋值;2.同一个类在Spring中有多个bean,所以我们需要更精确的属性赋值

(2)_使用@Autowired + @ Qualifier , 表示按照名称注入,回到spring容器中查找customerDao的名称,对应,id的属性值,如果找到,可以匹配。

@Autowired
@Qualifier("customerDao")
private IcustomerDao customerDao;

Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第2张图片
3.JSR-250标准(基于jdk) 提供注解@Resource
单独使用@Resource注解,表示先按照名称注入,会在spring容器中寻找customerDao的名称,对应的,id的属性值,如果找到就匹配customerDao的类型,对应的,class的属性值,如果找到,就匹配

@Resource
private IcustomerDao customerDao;

如果@Resource注解上添加name名称,则只按照名称进行匹配

@Resource(name="customerDao")
private IcustomerDao customerDao;

4.JSR-330标准(jdk) 提供 @Inject (麻烦点)
需要先导入 javax.inject 的 jar ,在课前资料中查找。
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第3张图片
使用@Inject注解,则按照类型注入,

@Inject//默认按照类型注入
private CustomerDao customerDao;

使用@inject和@Named注解,则按照名称注入

@Inject//默认按照类型注入
@Named("customerDao")//按照名字注入,必须配合@Inject使用
private CustomerDao customerDao;

总结:

  • @Value+spEL:表示通过id/name注入
  • @Autowired:按照类型注入
    @Autowired+ @Qualifier:表示通过id/name注入
  • @Resource:表示先按照名称注入,找不到,则会按照类型注入
    @Resource(name=“customerDao”):表示通过id/name注入
  • @Inject:默认按照类型注入
    @Inject+@Named:按照名字注入

1.3 Bean的初始化和销毁

使用注解定义Bean的初始化和销毁
回顾配置文件的写法:

注解的写法:
(1)当bean被载入到容器的时候调用setup ,
注解方式如下: 
@PostConstruct 
初始化
(2)当bean从容器中删除的时候调用teardown(scope= singleton有效)
注解方式如下:
@PreDestroy  
销毁

使用 @PostConstruct 注解, 标明初始化方法 —相当于 init-method 指定初始化方法
使用 @PreDestroy 注解, 标明销毁方法 ----相当于 destroy-method 指定对象销毁方法

@Repository(value="customerDao")
public class CustomerDao{
	 public CustomerDao(){
		 System.out.println("CustomerDao构造器被调用了");
	 }
	 
	 //初始化后自动调用
	 @PostConstruct
	 public void init(){
		System.out.println("CustomerDao-init初始化时调用");
	 }
	 
	 //bean销毁时调用
	 @PreDestroy
	 public void destroy(){
		System.out.println("CustomerDao-destroy销毁时调用");
	}
}

测试:

public void test() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");		
	
	//方案1
	((ClassPathXmlApplicationContext)ac).close();
	
	//方案2
	//通过反射机制调用close方法,接口只是引用一个对象,对象本身有这个方法
	Method method = ac.getClass().getMethod("close");
	method.invoke(ac);
}

注意:如果要执行对象的销毁方法
条件一: 单例Bean (在容器close时,单例Bean才会执行销毁方法 )
条件二: 必须调用容器 close 方法

1.4 Bean的作用域

通过@Scope注解,指定Bean的作用域(默认是 singleton 单例)

//测试生命周期过程中的初始化和销毁bean
@Component("lifeCycleBean")
//@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope("prototype")//默认是单例(singleton),更改为多例(prototype)
public class LifeCycleBean {

}

1.5 XML和注解混合配置 (重点)

一个项目中xml和注解同时存在

XML 完成Bean定义,注解完成Bean属性注入

public class CustomerService {
	
	@Autowired
	private CustomerDao customerDao;
	
	public void save(){
		System.out.println("产品保存了,--业务层");
		customerDao.save();
	}
}

public class CustomerDao{
	
	public void save(){
		System.out.println("查询保存到数据口--数据层调用了");
	}
	
}





备注:这里配置
才能使用 @PostConstruct @PreDestroy @Autowired @Resource

提示:因为采用注解开发时, 具有的功能 。如果没有配置注解扫描,需要单独配置 , 才能使用注解注入!


2.Spring的web集成

1.新建web项目,导入jar包以及applicationContext.xml和log4j.properties文件
2. 创建cn.itcast.spring.service包,编写HelloService.java 业务类

//业务层
public class HelloService {
	
	public void sayHello(){
		System.out.println("嘿,传智播客。。。。");
	}

}


3.创建cn.itcast.spring.servlet包,编写HelloServlet.java ,调用HelloService类

public class HelloServlet extends HttpServlet {
	
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
		HelloService helloService=(HelloService)applicationContext.getBean("helloService");
		helloService.sayHello();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.doGet(request, response);
	}

}

4.将程序部署到tomcat测试:

总结:

直接new ClassPathXmlApplicationContext()有什么缺点?
缺点:在创建Spring容器时,需要对容器中的对象进行初始化,而每次初始化容器,都创建了新的容器对象,消耗了资源,降低了性能

解决思路:保证容器对象只有一个
解决方案:将Spring容器绑定到web servlet容器上,让web容器来管理spring容器的创建和销毁

分析:servletContext在web服务运行过程中是唯一的,其初始化时,自动运行servletContextListener监听器(用来监听上下文的创建和销毁),具体步骤为:
编写一个ServletContextListener监听器,在监听ServletContext到创建的时候,创建Spring容器,并将其放到servletContext的属性中保存(setAttribute)

我们无需手动创建该监听器,因为spring已经提供了一个叫做contextloaderListener的监听器,它位于spring-web-4.2.4.RELEASE.jar中。

步骤

1.导入spring web的jar
在这里插入图片描述
2.在web.xml中配置spring的核心监听器



	org.springframework.web.context.ContextLoaderListener

3.启动tomcat服务器,发现报异常了
在这里插入图片描述
根据异常提示:发现spring的Beanfactory没有初始化,说明没找到spring容器,也就是applicationContext.xml文件

为什么没有找到applicationContext.xml文件呢?因为此时加载的是WEB-INF/applicationContext.xml,而不是src下的applicationContext.xml文件
原因:找到ContextLoaderListener.class,再找到ContextLoader.class,发现默认加载的WEB-INF/applicationContext.xml
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第4张图片
解决方法:在web.xml中配置,加载spring容器中的applicationContext.xml文件的路径



	org.springframework.web.context.ContextLoaderListener

  


	contextConfigLocation
	
	classpath:applicationContext.xml

重新启动tomcat服务器,没有异常,问题解决。
第五步:修改Servlet代码。在Servlet 中通过ServletContext 获取Spring容器对象

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	ApplicationContext applicationContext = 
			(ApplicationContext)this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	HelloService helloService = (HelloService) applicationContext.getBean("helloService");
	helloService.sayHello();
}

第二种方式:使用WebApplicationContextUtils (推荐)

WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

3.Spring的junit测试集成

Spring提供spring-test-4.2.4.RELEASE.jar 可以整合junit。
优势:可以简化测试代码(不需要手动创建上下文)

使用spring和junit集成
1.项目导入junit 开发包
2.导入spring-test-4.2.4.RELEASE.jar
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第5张图片
3. 通过@RunWith注解,使用junit整合spring,通过@ContextConfiguration注解,指定spring容器的位置

//测试spring的bean的某些功能
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class SpringTest {
	
	@Autowired
	private HelloService helloService;
	
	@Test
	public void testSayHello(){
		//测试业务功能
		helloService.sayHello();
	}
}

@RunWith(SpringJUnit4ClassRunner.class)//junit整合spring的测试,立马开启了spring的注解
@ContextConfiguration(locations=“classpath:applicationContext.xml”)//加载核心配置文件,自动构建spring容器

Spring的junit测试集成最大的优点是,能在测试类中自动加载spring容器,不需要我们手动加载

4.通过@Autowired注解,注入需要测试的对象
在这里注意2点:
(1)将测试对象注入到测试用例中
(2)测试用例不需要配置context:annotion-config/,因为使用测试类运行的时候,会自动启动注解的支持。

//使用注解注入要测试的bean
@Autowired
private HelloService helloService;

4.AOP面向切面编程的相关概念

4.1 什么是AOP ?

AOP (Aspect Oriented Programing) 称为:面向切面编程,它是一种编程思想。

AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

切面:需要代理一些方法和增强代码 。

4.2 AOP的应用场景

场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )

4.3 Spring AOP编程两种方式

简单的说,Spring内部支持两套AOP编程的方案:

  • Spring 1.2 开始支持AOP编程 (传统SpringAOP 编程),编程非常复杂 ---- 更好学习Spring 内置传统AOP代码
  • Spring 2.0 之后支持第三方 AOP框架(AspectJ ),实现另一种 AOP编程 – 推荐

4.4 AOP编程相关术语

AOP思想编程的机制
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第6张图片
AOP的相关术语

  • Aspect(切面): 是通知和切入点的结合,通知和切入点共同定义了关于切面的全部内容—它的功能、在何时和何地完成其功能
  • joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
  • Pointcut(切入点):所谓切入点是指我们要对哪些joinpoint进行拦截的定义.
    通知定义了切面的”什么”和”何时”,切入点就定义了”何地”.
  • Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  • Target(目标对象):代理的目标对象
  • (织入):是指把切面应用到目标对象来创建新的代理对象的过程.切面在指定的连接点织入到目标对象
  • Introduction(引入):在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.

通过案例解释AOP相关概念
需求:UserDao中有5个方法,分别是save()、update()、delete()、find()、login();在访问UserDao的save()、update()、delete()方法之前,进行记录日志的操作。
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第7张图片
Aspect切面(类):增强代码 Advice(writeLog方法)和 切入点 Pointcut(save,update,delete) 的结合。换句话说:对哪些方法进行怎样的代码增强。

5.AOP编程底层实现机制(了解)

AOP 就是要对目标进行代理对象的创建, Spring AOP是基于动态代理的,基于两种动态代理机制: JDK动态代理和CGLIB动态代理

5.1.JDK动态代理

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

过程要点:
1.必须对接口生成代理
2.采用Proxy类,通过newProxyInstance方法为目标创建代理对象

该方法接收三个参数
(1)目标对象类加载器
(2)目标对象实现的接口
(3)代理后的处理程序InvocationHandler
使用 Proxy类提供 newProxyInstance 方法对目标对象接口进行代理
在这里插入图片描述

参数说明:
loader:定义代理类的类加载器
interfaces:代理类要实现的接口列表
h:指派方法调用的调用处理程序

3.实现InvocationHandler 接口中 invoke方法,在目标对象每个方法调用时,都会执行invoke

示例:需求:对目标对象中存在保存和查询的方法,在执行保存的方法的时候,记录日志
1.编写业务接口,接口中定义save()和find()的方法。

//接口(表示代理的目标接口)
public interface ICustomerService {
	//保存
	public void save();
	//查询
	public int find();
}

2.编写业务类,类要实现接口

//实现类
public class CustomerServiceImpl implements ICustomerService{

	public void save() {
		System.out.println("客户保存了。。。。。");
	}

	public int find() {
		System.out.println("客户查询数量了。。。。。");
		return 100;
	}
}

3.使用JDK代理完成
有三种方法可以完成JDK动态代理:
(1)在内部实现new InvocationHandler(),指定匿名类

//专门用来生成jdk的动态代理对象的_通用
public class JdkProxyFactory {
	//成员变量
	private Object target;
	//注入target目标对象
	public JdkProxyFactory(Object target){
		this.target = target;
	}
	
	public Object getProxyObject(){
		/*
		 * 参数1:目标对象的类加载器
		 * 参数2:目标对象实现的接口
		 * 参数3:回调方法对象
		 */
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){

			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				//如果是保存的方法,执行记录日志操作
				if(method.getName().equals("save")){
					writeLog();
				}
				//目标对象原来的方法执行
				Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
				return object;
			}
			
		});
	}
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}
}

注意:此处的InvocationHandler是java.lang.reflect.InvocationHandler

(2)传递内部类的对象,指定内部类

//专门用来生成jdk的动态代理对象的_通用
public class JdkProxyFactory {
	//成员变量
	private Object target;
	//注入target目标对象
	public JdkProxyFactory(Object target){
		this.target = target;
	}
	
	public Object getProxyObject(){
		/*
		 * 参数1:目标对象的类加载器
		 * 参数2:目标对象实现的接口
		 * 参数3:回调方法对象
		 */
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new MyInvocationHandler());
	}
	
	//自己制定内部类:类的内部可以多次使用类型
	private class MyInvocationHandler implements InvocationHandler{

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			//如果是保存的方法,执行记录日志操作
			if(method.getName().equals("save")){
				writeLog();
			}
			//目标对象原来的方法执行
			Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的返回值
			return object;
		}
		
	}
		
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}
}

(3)直接使用调用类作为接口实现类,实现InvocationHandler接口

//专门用来生成jdk的动态代理对象的-通用
public class JdkProxyFactory implements InvocationHandler{
	//成员变量
	private Object target;
	//注入target
	public JdkProxyFactory(Object target) {
		this.target = target;
	}
	
	public Object getProxyObject(){
		//参数1:目标对象的类加载器
		//参数2:目标对象实现的接口
		//参数3:回调方法对象
		/**方案三:直接使用调用类作为接口实现类,实现InvocationHandler接口*/
		return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
	}
	
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}

	//参数1:代理对象
	//参数2:目标的方法对象
	//参数3:目标的方法的参数
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//如果是保存的方法,执行记录日志操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目标对象原来的方法执行
		Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象
		return object;
	}

}

4.使用SpringTest.java进行测试

//目标:使用动态代理,对原来的方法进行功能增强,而无需更改原来的代码。
//JDK动态代理:基于接口的(对象的类型,必须实现接口!)
@Test
public void testJdkProxy(){
	//target(目标对象)
	ICustomerService target = new CustomerServiceImpl();
	//实例化注入目标对象
	JdkProxyFactory jdkProxyFactory = new JdkProxyFactory(target);
	//获取Proxy Object代理对象:基于目标对象类型的接口的类型的子类型的对象
	ICustomerService proxy = (ICustomerService)jdkProxyFactory.getProxyObject();
	//调用目标对象的方法
	proxy.save();
	System.out.println("————————————————————————————————————————");
	proxy.find();
}

5.在控制台查看输出结果
Spring(2)_通过注解配置bean/Aop底层实现/JDK动态代理和Cglib_第8张图片
从结果上看出:在保存方法的前面,输入了日志增强。
最后,使用断点查看JDK代理,生成的代理对象
在这里插入图片描述

说明
Interface ICustomerService{
    //目标接口
}
Class CustomerServiceImpl implements ICustomerService{
    //目标类实现接口
}
JDK代理对接口代理
Class $Proxy2 implements ICustomerService{
//JDK代理类是目标接口的实现类
ICustomerService customerService = new CustomerServiceImpl(); 
public void save() {
    writeLog()
	customerService.save();
}

public int find() {
	int returnValue = customerService.find();
    return returnValue;
}

//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

}

注意:
JDK动态代理的缺点: 只能面向接口代理,不能直接对目标类进行代理 ,如果没有接口,则不能使用JDK代理。

5.2.Cglib动态代理

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

什么是cglib ?
CGLIB(Code Generation Library)是一个开源项目!是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

该代理方式需要相应的jar包,但不需要导入。因为Spring core包已经包含cglib ,而且同时包含了cglib 依赖的asm的包(动态字节码的操作类库)

示例:
1.将spring的核心jar导入进来,因为spring的包,包含了cglib的包
在这里插入图片描述
2.编写业务类,创建类ProductService.java,类不需要实现接口

//没有接口的类
public class ProductService {
	public void save() {
		System.out.println("商品保存了。。。。。");
		
	}

	public int find() {
		System.out.println("商品查询数量了。。。。。");
		return 99;
	}
}


//cglib动态代理工厂:用来生成cglib代理对象
public class CglibProxyFactory implements MethodInterceptor{
	//声明一个代理对象引用
	private Object target;
	//注入代理对象
	public CglibProxyFactory(Object target) {
		this.target = target;
	}
	
	//获取代理对象
	public Object getProxyObject(){
		//1.代理对象生成器(工厂思想)
		Enhancer enhancer = new Enhancer();
		//2.在增强器上设置两个属性
		//设置要生成代理对象的目标对象:生成的目标对象类型的子类型
		enhancer.setSuperclass(target.getClass());
		//设置回调方法
		enhancer.setCallback(this);
//		Callback
		//3.创建获取对象
		return enhancer.create();
	}

	
	//回调方法(代理对象的方法)
	//参数1:代理对象
	//参数2:目标对象的方法对象
	//参数3:目标对象的方法的参数的值
	//参数4:代理对象的方法对象
	public Object intercept(Object proxy, Method method, Object[] args,
			MethodProxy methodProxy) throws Throwable {
		//如果是保存的方法,执行记录日志操作
		if(method.getName().equals("save")){
			writeLog();
		}
		//目标对象原来的方法执行
		Object object = method.invoke(target, args);//调用目标对象的某个方法,并且返回目标对象方法的执行结果
		return object;
	}
	
	//写日志的增强功能
	//Advice通知、增强
	//记录日志
	private static void writeLog(){
		System.out.println("增强代码:写日志了。。。");
	}
	
}
//cglib动态代理:可以基于类(无需实现接口)生成代理对象
@Test
public void testCglibProxy(){
	//target目标:
	ProductService target = new ProductService();
	//weave织入,生成proxy代理对象
	//代理工厂对象,注入目标
	CglibProxyFactory cglibProxyFactory = new CglibProxyFactory(target);
	//获取proxy:思考:对象的类型
	//代理对象,其实是目标对象类型的子类型
	ProductService proxy=(ProductService) cglibProxyFactory.getProxyObject();
	//调用代理对象的方法
	proxy.save();
	System.out.println("————————————————————————————————————————");
	proxy.find();
	
}

最后,使用断点查看cglib代理,生成的代理对象
在这里插入图片描述

说明
Class ProductService{
    //目标类
}
Cglib对类代理
Class ProductService$$EnhancerByCGLIB$$df9980d0 extends ProductService{
//CGLIB代理类是目标类的子类
ProductService productService= new ProductService(); 
public void save() {
    writeLog()
	productService.save();
}

public int find() {
	int returnValue = productService.find();
    return returnValue;
}

//记录日志
private static void writeLog(){
	System.out.println("增强代码:写日志了。。。");
}

}

5.3.代理知识小结

区别:

  • JDK代理:基于接口的代理,一定是基于接口,会生成目标对象的接口类型的子对象
  • Cglib代理:基于类的代理,不需要基于接口,会生成目标对象类型的子对象

代理知识总结:

  • spring在运行期,生成动态代理对象,不需要特殊的编译器.
  • spring有两种代理方式:
    1.若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理。
    2.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
  • 使用该方式时需要注意:
    1.对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,所以spring默认是使用JDK代理。对类代理是让遗留系统或无法实现接口的第三方类库同样可以得到通知,这种方式应该是备用方案。
    2.标记为final的方法不能够被通知。spring是为目标类产生子类。任何需要被通知的方法都被复写,将通知织入。final方法是不允许重写的。
    3.spring只支持方法连接点:不提供属性接入点,spring的观点是属性拦截破坏了封装。
    面向对象的概念是对象自己处理工作,其他对象只能通过方法调用的得到的结果。

提示:

  • Spring AOP 优先对接口进行代理 (使用Jdk动态代理)
  • 如果目标对象没有实现任何接口,才会对类进行代理 (使用cglib动态代理)

你可能感兴趣的:(Spring,动态代理)