目录
一、基于注解的DI注入(使用更常见)
1.环境搭建:导入aop包(spring-aop-4.1.6.RELEASE.jar),添加context约束头信息(组件扫描器)
(1)约束头地址:XML Schemas :: Spring Frameworkhttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#xsd-schemas-context
(2)想要使用注解注入值或者使用注解去让spring去管理类的话,需要在配置文件中加一个扫描标签<context:component-scan base-package="要扫描的包名"></context:component-scan>。
- 注意:base-package属性的值只需要写完整的包名就可以了,不需要加类名,如果加类名的话会报错
2.常用注解:
(1)@Component:修饰一个类,将这个类交给Spring容器管理。
(2)@Value:给属性的基本数据类型赋值,可以放在属性上,也可以放在属性的set方法上。
(3)@Scope:指定类是单态模式还是原型模式。
(4)@Autowired:设置对象类型的属性的值(byType)。但它是按照对象的类型来注入,跟注解的名字无关。
(5)@Resource:完成对象类型值的注入(与Autowired的界限没有那么分明了),按照名字注入(byName)。
3.案例:
(1)spring.xml配置文件中的内容
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"
xmlns:context="http://www.springframework.org/schema/context"
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">
<!--当前配置文件被加载的时候会扫描指定的包-->
<context:component-scan base-package="com.myspring.pojo"></context:component-scan>
</beans>
(2)Users类中的内容
java
package com.myspring.pojo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
/**
* @Component 这个注解是告诉spring容器,你在指定扫描com.myspring.pojo包下所有的类是时,
* 捕捉这个@Component,就是要把当前类交给spring容器去管理
* 相当于spring容器做了一件事:Users users = new Users(),并放入spring容的bean的缓存池中
* 类似于前面学习的:<bean id="users" class="com.myspring.pojo.Users"></bean>
* 变量名默认为类名的首字母小写形式
*/
@Component("u") //@Component("u") --> Users u = new Users()
@Scope("prototype") //@Scope("prototype") 这个注解如果不写,默认是@Scope("singleton")
public class Users implements Serializable {
@Value("小莉") //@Value("小莉") --> u.setUserName("小莉")
private String username;
@Value("123") //@Value("123") --> u.setPassWord("123")
private String password;
// @Resource //这个注解的意思是:从spring容器中的bean缓存池里通过id名字的方式注入当前对象的实例
@Autowired //这个注解的意思是:从spring容器中的bean缓存池里通过类型的方式注入当前对象的实例(开发中这个更常用)
private Account account;
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Users{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", account=" + account +
'}';
}
}
(3)Demo案例中的内容
java
package com.myspring.test;
import com.myspring.pojo.Account;
import com.myspring.pojo.Users;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Demo {
/**
* 控制反转
*/
@Test
public void run(){
//加载spring.xml配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
//根据id从bean缓存池中获取Users对象
// Users users = (Users)ac.getBean("users");
Users users = (Users)ac.getBean("u");
System.out.println("users: " + users);
Users users2 = (Users)ac.getBean("u");
System.out.println("users2: " + users2);
System.out.println("---------------------------------");
// Account account = (Account) ac.getBean("account");
// System.out.println(account);
}
}
二、Spring的IOC(注解)
1.@Component:表示当前修饰的类交给Spring容器管理,修饰一个类,将这个类交给Spring管理。与@Component相同功能的还有三个衍生注解,都是用来修饰类:
(1)@Repository:添加在mapper的接口类上(数据访问层)。
(2)@Service:添加在Service实现类上(业务层)。
(3)@Controller:添加在Controller类上(控制层)。
(4)@Configration:添加在用于配置信息的类上(springboot中)。
三、Bean的其他的注解
1.Bean的作用范围的注解:
(1)@Scope("singleton"):默认是单态模式(常用)。
(2)@Scope("prototype"):原型模式(常用)。
四、IOC的XML和注解开发比较
1.使用注解注入必须要有源代码,而使用XML注入不需要有源码。
2.XML可以使用在任何场景中,开发中用来注入框架实例,基于xml操作的是.class文件。
3.注解它有些地方用不了,就是这个类如果不是自己写的,就无法使用注解(注解必须写在源代码上),在开发中,用来注入自己写的java类,基于注解操作的是源代码。
五、AOP编程介绍
1.什么是面向切面编程AOP?
(1)在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
2.为什么学习AOP?
(1)对程序进行增强:不修改源码的情况下,AOP可以进行权限的校验,日志记录,性能监控,事务控制。
3.Spring的AOP的由来:
(1)AOP最早由AOP联盟的组织提出的,指定了一套规范,Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范。
六、Spring底层的AOP原理
1.动态代理(静态代理):
(1)JDK动态代理:面向接口的,只能对实现了接口的类产生代理。
(2)Cglib动态代理(类似于JavaSsit第三方代理技术):对没有实现接口的类产生代理对象(生成子类对象)。
(3)如果类实现了接口,Spring就使用JDK动态代理,如果没有实现接口,就使用Cglib动态代理,Spring底层可以动态切换。
2.什么是代理:
(1)代理模式(Proxy Pattern):代理模式是Java常见的设计模式之一。所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理对象,来间接的调用实际的对象。通俗的来讲代理模式就是我们生活中常见的娱乐圈的经纪人或租房圈的中介。
3.为什么使用代理模式:
(1)隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
(2)开闭原则:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要修改委托类,符合代码设计的开闭原则。
(3)代理模式的优点:
- 代理模式能将代理对象与真实对象被调用的目标对象分离。
- 一定程度上降低了系统的耦合度,扩展性好。
- 保护目标对象。
- 增强目标对象。
(4)代理模式的缺点:
- 代理模式会造成系统设计中类的数目的增加。
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。
4.代理模式的实现:
(1)代理模式可以分为两种:静态代理和动态代理。
(2)静态代理:
- 静态代理中,代理类和被代理类必须实现了相同的接口,然后再代理类中持有被代理类的对象,然后使用代理类对象去调用代理类中的方法就可以了。
- 接口中的内容:
java
package com.proxystatic;
public interface StarBussiness {
//面谈
void face();
//签合同
void contrack();
//预定飞机票
void orderPlane();
//表演
void sing();
//收钱
void getMoney();
}
- 代理类中的内容
java
package com.proxystatic;
public class StarProxy implements StarBussiness {
//持有和管理明星,作为属性
private Star star;
public StarProxy(Star star){
this.star = star;
}
@Override
public void face() {
System.out.println("经纪人面谈代言的业务");
}
@Override
public void contrack() {
System.out.println("经纪人签合同");
}
@Override
public void orderPlane() {
System.out.println("经纪人安排行程订机票");
}
//核心功能
@Override
public void sing() {
//这个需要明星本人参与
star.sing();
}
@Override
public void getMoney() {
System.out.println("经纪人收款");
}
}
- 目标类中的内容
java
package com.proxystatic;
public class Star implements StarBussiness {
@Override
public void face() {
}
@Override
public void contrack() {
}
@Override
public void orderPlane() {
}
@Override
public void sing() {
System.out.println("明星本人去表演,商业代言");
}
@Override
public void getMoney() {
}
}
(3)动态代理(动态代理中分为两种实现方式 ):
-
使用JDK的Proxy类实现动态代理:
-
想要使用JDK实现动态代理,那么被代理的类,也就是需要增强的类必须实现了接口。
-
创建一个类,再类中写一个方法,方法的返回值和参数都是Object,然后再方法中new一个增强功能的类的对象。
-
获取需要被增强的类的class文件,使用对象名.getClass()方法获取。
-
返回一个代理实例,代理实例使用Proxy.newProxyInstance(需要增强的类的class文件对象.getClassLoader(),需要增强的类的class文件对象.getInterfaces(),new InvocationHandler())来获取:
- getClassLoader()是拿到调用这个方法的类对象的类加载器。
- getInterfaces()是拿到调用这个方法的类对象的接口。
-
new InvocationHandler()这个方法时,会new出一个匿名内部类,这个类中会实现一个invoke方法,这个方法有三个参数,一个是proxy,一个是method,一个是args:
- proxy:动态生成代理的代理对象。
- method:目标方法(renting)的方法对象。
- args:renting方法中的参数。
-
再这个invoke()方法中使用之前new出来的增强功能的类的对象去调用要增强的方法,使用method参数.invoke方法去调用被增强类中要增强的方法,方法中需要传入两个参数,第一个是之前声明自定义方法时那个Object类型的参数,第二个时invoke方法中args参数。
-
案例:
- 需要增强功能的类所实现的接口:
javapackage com.proxyjdk; /** * @Description:com.proxyjdk 定义租房的业务 * @version:1.0 */ public interface JdkProxyRent { //出租业务 void renting(); }- 需要增强功能的类(被代理的类):
javapackage com.proxyjdk; /** * @Description:com.proxyjdk 房子的房主 * @version:1.0 */ public class HouseOwner implements JdkProxyRent { @Override public void renting() { System.out.println("谭女士的房子要出租"); } }- 增强功能的类(代理类):
javapackage com.proxyjdk; /** * @Description:com.proxyjdk (当前这个类看作切面类)链家提供房子出租代理业务 * @version:1.0 */ public class MyAsPent{ public void before(){ System.out.println("链家的小陈带顾客看房子"); } public void after(){ System.out.println("链家的小陈完成房子出租之后的服务:签合同等"); } }- JDK实现动态代理类:
javapackage com.proxyjdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @Description:com.proxyjdk 基于JDK的动态代理(当前这个类是把HouserOwner中的renting方法与MyAsPent中的before方法和after方法组织起来) * @version:1.0 */ public class JdkProxyFactory { /** * * @param target 被代理对象(谭女士的房子) * @return */ public static Object getProxyBean(Object target){ //创建功能增强的对象(链家) MyAsPent as = new MyAsPent(); //获取要被代理的对象(HouseOwner)的字节码对象 Class c = target.getClass(); return Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new InvocationHandler() { /** * 这个方法是动态生成代理对象的类 * @param proxy 动态生成代理的代理对象 * @param method 目标方法(renting)的方法对象 * @param args renting方法中的参数 * @return * @throws Throwable * * 下面的方法的动作 --> 给谭女士的房子出租动态的织入增强的功能 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //出租之前的功能增强(带顾客看房) as.before(); //链家提供出租业务 Object obj = method.invoke(target, args); //出租之后的增强(售后服务) as.after(); return obj; } }); } }- 使用:注意,再使用的时候需要使用多态的方式,也就是不管是再new对象,还是再接收代理工厂返回的内容,都需要使用接口来接收:
javapackage com.proxyjdk; public class Demo { public static void main(String[] args) { JdkProxyRent mistan = new HouseOwner();//接口的引用指向了实现了这个接口的实例 //创建代理对象 JdkProxyRent proxyBean = (JdkProxyRent) JdkProxyFactory.getProxyBean(mistan); proxyBean.renting(); } }
-
-
使用CGLIB实现动态代理:
- CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它被许多框架所使用,其底层是通过使用一个小而块的字节码处理框架ASM(Java字节码操控框架)转换字节码并生成新的类。因此CGLIB要依赖于ASM的包。
- JDK的的动态代理机制只能代理实现了接口的类,而对于没有实现接口的类就不能使用JDK的Proxy类生成代理对象。
- CGLIB是针对类来实现代理的,他的原理是对指定的目标类生成一个子类并通过回调的方式来实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。也就是创建了一个被代理类的子类对象,把被代理类看作父类, 把子类看作的代理类,最后实际运行的是子类代理类。
- 使用:
-
添加jar包(cglib.jar和asm.jar,再MyBatis框架中有这两个包)。
-
和之前使用JDK的动态代理方法差不多,先创建一个类,再类中写一个自定义方法,方法的返回值类型和参数都是Object。
-
在这个方法中new一个代理类,也就是增强功能类的对象。还要获取到被代理类的字节码对象,获取字节码对象和之前的JDK动态代理一样,使用这个自定义方法的参数.getClass(),就可以获取到了,因为传入的这个参数对象是被代理类。
-
需要new一个Enhancer对象,这个对象代理对象的模板对象。
-
使用Enhancer对象名.setSuperclass(传入被代理类对象)将被代理对象设置为父类。
-
使用Enhancer对象名.setCallback(new MethodInterceptor(){})去设置一个回调函数,new MethodInterceptor()这个类时,会new一个匿名内部类,内部类中会自动重写一个intercept方法,这个方法中会有四个参数,分别是:o,method,objects,methodProxy:
- o:代理对象的应用。
- method:目标对象的方法对象。
- objects:目标方法的参数列表。
- methodProxy:目标对象的方法对象的代理对象。
-
再这个intercept()方法中使用之前new出来的代理类的对象去调用要增强的方法,使用method参数.invoke方法去调用被增强类中要增强的方法,方法中需要传入两个参数,第一个是之前声明自定义方法时那个Object类型的参数,第二个时intercept方法中objects参数。
-
最后,需要给我们的自定义的方法返回一个被代理对象的子类,使用Enhancer对象名.create()方法创建一个被代理对象的子类。
-
案例:
- 需要增强功能的类(被代理对象)所实现的接口:
javapackage com.proxycglib; public interface CglibProxyRent { //出租业务 void renting(); }- 需要增强功能的类(被代理的类):
javapackage com.proxycglib; public class HouseOwner implements CglibProxyRent { @Override public void renting() { System.out.println("谭女士的别墅要出租"); } }- 增强功能的类(代理类):
javapackage com.proxycglib; /** * @Description:com.proxyjdk (当前这个类看作切面类)链家提供房子出租代理业务 --> 功能的增强类 * @version:1.0 */ public class MyAspect { public void before(){ System.out.println("链家的小陈带顾客看房子"); } public void after(){ System.out.println("链家的小陈完成房子出租之后的服务:签合同等"); } }- Cglib实现动态代理类:
javapackage com.proxycglib; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * @Description:com.proxycglib Cglib技术的动态代理类 * @version:1.0 */ public class CglibProxyFactory { /** * * @param target 被代理的对象(谭女士的别墅) * @return */ public static Object getProxyBean(Object target){ //创建代理类对象,也就是功能增强对象(链家) MyAspect as = new MyAspect(); //获取被代理类的字节码对象 Class c = target.getClass(); //Cglib提供创建代理对象的模板对象 Enhancer enhancer = new Enhancer(); //被代理对象作为父类,设置为父类 enhancer.setSuperclass(c); //设置回调函数 enhancer.setCallback(new MethodInterceptor() { /** * * @param o 代理对象的应用 * @param method 目标对象的方法对象(renting()的对象) * @param objects 目标方法的参数列表 * @param methodProxy 目标对象的方法对象的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { //链家带看房子 as.before(); //出租 Object obj = method.invoke(target, objects); //售后 as.after(); return obj; } }); return enhancer.create(); //返回被代理对象的子类 } }- 使用:注意,这里可以不使用接口,可以直接使用类,不影响:
javapackage com.proxycglib; public class Demo { public static void main(String[] args) { HouseOwner misTan = new HouseOwner(); //Cglib代理 CglibProxyRent lianjia = (CglibProxyRent) CglibProxyFactory.getProxyBean(misTan); lianjia.renting(); } }
-
-
两种动态代理的区别:
- Cglib动态代理的性能高于JDK的动态代理。
- JDK的动态代理是采用反射实现。
- Cglib动态代理是直接修改字节码实现。