1. Spring概述
Spring核心:IoC(Inverse of Control 控制反转): 将对象创建权利交给Spring工厂进行管理。比如说:Book book = new Book();
Book book2 = Spring工厂.getBook();AOP(Aspect Oriented Programming 面向切面编程),基于动态代理的功能增强方式。 2. IOC控制反转(DI依赖注入)IoC底层实现:工厂(设计模式)+反射(机制) + 配置文件(xml)。降低程序间的耦合性2.1实例化Bean的四种方式
2.1.1.无参数构造器 (最常用)第一种方式 无参数构造器 (最常用)//1。默认构造器(spring在创建bean的时候自动调用无参构造器来实例化,相当于new Bean1())public class Bean1 { }在spring容器applicationContext.xml中配置
<!-- 实例化 bean的四种方式 --> <!-- 1.默认构造器实例化对象 --> <bean id ="bean1" class="cn.itcast.spring.b_xmlnewbean.Bean1" /> public class SpringTest { @Test public void test(){ //创建spring工厂 ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml"); //1.默认构造器获取bean对象 Bean1 bean1 = (Bean1) ac.getBean("bean1"); System.out.println(bean1); }} 2.1.2.工厂方法//第三种bean,实例工厂方式创建public class Bean3 {}
//实例工厂:必须new工厂--》bean
public class Bean3Factory { //普通的方法,非静态方法 public Bean3 getBean3(){ //初始化实例对象返回 return new Bean3(); }} <!-- 3:实例工厂的方式实例化bean --> <bean id="bean3Factory" class="cn.itcast.spring.b_xmlnewbean.Bean3Factory"/> <!-- factory-bean相当于ref:引用一个bean对象 --> <bean id="bean3" factory-bean="bean3Factory" factory-method="getBean3"/> @Test public void test(){ //先构建实例化获取spring的容器(工厂、上下文) ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); //3.实例工厂 Bean3 bean3=(Bean3) applicationContext.getBean("bean3"); System.out.println(bean3); } 此工厂方法包括:静态工厂方法和实例工厂方法2.1.3.FactoryBean方式
public class Bean4 {}
//4。实现FactoryBean接口的方式//泛型:你要返回什么类型的对象,泛型就是什么public class Bean4FactoryBean implements FactoryBean<Bean4>{ //用来获取bean的实例,对象 public Bean4 getObject() throws Exception { //写一些初始化数据库连接等等其他代码 return new Bean4(); } public Class<?> getObjectType() { return null; } public boolean isSingleton() { return false; }} <!-- 4.实现FactoryBean接口实例化对象 --> <!-- spring在实例化Bean4Factory的时候会判断是否实现了FactoryBean接口,如果实现了就调用getObject方法返回实例 --> <bean id="bean4" class="cn.itcast.spring.b_xmlnewbean.Bean4Factory" /> 2.2. Bean属性的依赖注入3种方式2.2.1构造器参数注入constructor-arg//目标,构造器参数注入,new car直接将参数的值直接赋值public class Car { private Integer id; private String name; private Double price; //有参构造 public Car(Integer id, String name, Double price) { this.id = id; this.name = name; this.price = price; } //取值要用getter public Integer getId(){ return this.id; } @Override public String toString() { return "Car [id=" + id + ", name=" + name + ", price=" + price + "]"; }}
<!-- 构造器注入属性的值 --> <bean id="car" class="cn.itcast.spring.e_xmlpropertydi.Car"> <!--constructor-arg:告诉spring容器,要调用有参构造方法了,不再调用默认的构造方法了 new Car(1,"宝马",99999d) 参数第一组:定位属性 * index:根据索引定位属性,0表示第一个位置 * name:根据属性参数名称定位属性 * type:根据属性数据类型定位属性 参数第二组:值 * value:简单的值,字符串 * ref:复杂的(由spring容器创建的bean对象) --> <!-- <constructor-arg index="0" value="1"/> --> <constructor-arg index="0" name="id" value="1"/> <!-- <constructor-arg name="name" value="宝马1代"/> --> <constructor-arg name="name" > <value>宝马2代</value> </constructor-arg> <constructor-arg type="java.lang.Double" value="99999d"/> </bean> 2.2.2.setter方法注入 property/**
* 定义人类 * setter方法属性注入 * 相当于new Person(); */public class Person { private Integer id; private String name; private Car car; //必须提供setter属性方法 public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setCar(Car car) { this.car = car; } @Override public String toString() { return "Person [id=" + id + ", name=" + name + ", car=" + car + "]"; }}
<!-- setter方法属性注入:调用默认构造器,相当于new Person() --> <bean id="person" class="cn.itcast.spring.e_xmlpropertydi.Person"> <!-- property:专门进行setter属性注入用的标签 。 * name:setter方法的属性的名字,例如SetXxx-那么name的属性值为xxx。 * value:简单的值 * ref:bean的名字,对象的引用 --> <property name="id" value="1001"/> <property name="name" value="Tom"/> <!-- <property name="car" ref="car"/> --><!--等同于--> <property name="car"> <ref bean="car"/> </property> </bean> 2.3最终xml和注解混合配置第一步:(1)创建ProductDao类//产品的数据层public class ProductDao { public void save(){ System.out.println("查询保存到数据口--数据层调用了"); }}
(2)创建ProductService类//产品的业务层public class ProductService { //注入dao //强调:注入必须是bean注入bean @Autowired private ProductDao productDao; //产品的保存 public void save(){ System.out.println("产品保存了,--业务层"); //调用dao层 productDao.save(); }} 第二步:使用XML的方式完成Bean的定义创建applicationContext-mixed.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- xml方式定义bean --> <bean id="productDao" class="cn.itcast.spring.b_mixed.ProductDao"/> <bean id="productService" class="cn.itcast.spring.b_mixed.ProductService"/> <!-- 需要单独开启注解功能 --> <context:annotation-config/></beans> 备注:这里配置 <context:annotation-config> 才能使用 @PostConstruct @PreDestroy @Autowired @Resource <!-- 需要在spring容器中单独开启注解功能 --> <context:annotation-config/>提示:因为采用注解开发时, <context:component-scan> 具有<context:annotation-config>的功能 。
如果没有配置注解扫描,需要单独配置 <context:annotation-config>, 才能使用注解注入! 注意:IOC:控制反转,将对象创建管理的权利交给spring容器,获取对象通过spring工厂创建Bean的定义在xml中配置,Bean属性注入通过注解方式 2.3.Spring的Web集成直接new ClassPathXmlApplicationContext()有什么缺点?缺点:在创建Spring容器同时,需要对容器中对象初始化。而每次初始化容器的时候,都创建了新的容器对象,消耗了资源,降低了性能。解决思路:保证容器对象只有一个。解决方案:将Spring容器绑定到Web Servlet容器上,让Web容器来管理Spring容器的创建和销毁分析:ServletContext在Web服务运行过程中是唯一的, 其初始化的时候,会自动执行ServletContextListener 监听器 (用来监听上下文的创建和销毁),具体步骤为:
编写一个ServletContextListener监听器,在监听ServletContext到创建的时候,创建Spring容器,并将其放到ServletContext的属性中保存(setAttribute(Spring容器名字,Spring容器对象) )。 我们无需手动创建该监听器,因为Spring提供了一个叫ContextLoaderListener的监听器,它位于spring-web-3.2.0.RELEASE.jar中。第一步:导入spring web的jar
第二步:在web.xml 配置Spring的核心监听器 <!-- spring的核心监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 这种方式存在问题,发现spring的BeanFactory没有初始化,说明没有找到spring容器,即applicationContext.xml文件为什么没有找到applicationContext.xml文件呢?因为此时加载的是WEB-INF/applicationContext.xml,而不是src下的applicationContext.xml文件原因:找到ContextLoaderListener.class,再找到ContextLoader.class,发现默认加载的WEB-INF/applicationContext.xml解决方案:需要在web.xml中配置,加载spring容器applicationContext.xml文件的路径
<!-- spring的核心监听器 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 全局参数变量 --> <context-param> <param-name>contextConfigLocation</param-name> <!-- applicationContext.xml文件的位置,使用classpath定义 --> <param-value>classpath:applicationContext.xml</param-value> </context-param> 3. AOP面向切面编程3.1.AOP概述AOP是Spring提供的关键特性之一。AOP即面向切面编程,是OOP编程的有效补充。使用AOP技术,可以将一些系统性相关的编程工作,独立提取出来,独立实现,然后通过切面切入进系统。从而避免了在业务逻辑的代码中混入很多的系统相关的逻辑——比如权限管理,事物管理,日志记录等等。这些系统性的编程工作都可以独立编码实现,然后通过AOP技术切入进系统即可。从而达到了将不同的关注点分离出来的效果3.2. AOP实现原理AOP分为静态AOP和动态AOP。静态AOP是指AspectJ实现的AOP,他是将切面代码直接编译到Java类文件中。动态AOP是指将切面代码进行动态织入实现的AOP。Spring的AOP为动态AOP,实现的技术为:JDK提供的动态代理技术 和 CGLIB(动态字节码增强技术)。尽管实现技术不一样,但都是基于代理模式,都是生成一个代理对象。3.2.AOP的两种代理模式
第一种:JDK动态代理,针对目标对象的接口进行代理 ,动态生成接口的实现类 !(必须有接口)第二种:Cglib的引入为了解决类的直接代理问题(生成代理子类),不需要接口也可以代理。3.3.1.JDK代理模式
主要使用到 InvocationHandler 接口和 Proxy.newProxyInstance() 方法。JDK动态代理要求被代理实现一个接口,只有接口中的方法才能够被代理。其方法是将被代理对象注入到一个中间对象,而中间对象实现InvocationHandler接口,在实现该接口时,可以在 被代理对象调用它的方法时,在调用的前后插入一些代码。而 Proxy.newProxyInstance() 能够利用中间对象来生产代理对象。插入的代码就是切面代码。所以使用JDK动态代理可以实现AOP。被代理对象实现的接口,只有接口中的方法才能够被代理:public interface UserService { public void addUser(User user); public User getUser(int id);}被代理对象:public class UserServiceImpl implements UserService { public void addUser(User user) { System.out.println("add user into database."); } public User getUser(int id) { User user = new User(); user.setId(id); System.out.println("getUser from database."); return user; }}代理中间类:import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class ProxyUtil implements InvocationHandler {
private Object target; // 被代理的对象 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("do sth before...."); Object result = method.invoke(target, args); System.out.println("do sth after...."); return result; } ProxyUtil(Object target){ this.target = target; } public Object getTarget() { return target; } public void setTarget(Object target) { this.target = target; }}测试:import java.lang.reflect.Proxy;import net.aazj.pojo.User;public class ProxyTest {
public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的对象 ProxyUtil proxyUtils = new ProxyUtil(proxyedObject); // 生成代理对象,对被代理对象的这些接口进行代理:UserServiceImpl.class.getInterfaces() UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), UserServiceImpl.class.getInterfaces(), proxyUtils); proxyObject.getUser(1); proxyObject.addUser(new User()); }}执行结果:do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....我们看到在 UserService接口中的方法addUser 和 getUser方法的前面插入了我们自己的代码。这就是JDK动态代理实现AOP的原理。我们看到该方式有一个要求,被代理的对象必须实现接口,而且只有接口中的方法才能被代理。 3.3.2.CGLIB代理模式字节码生成技术实现AOP,其实就是继承被代理对象,然后Override需要被代理的方法,在覆盖该方法时,自然是可以插入我们自己的代码的。因为需要Override被代理对象的方法,所以自然CGLIB技术实现AOP时,就必须要求需要被代理的方法不能是final方法,因为final方法不能被子类覆盖。package net.aazj.aop;import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;public class CGProxy implements MethodInterceptor{
private Object target; // 被代理对象 public CGProxy(Object target){ this.target = target; } public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy proxy) throws Throwable { System.out.println("do sth before...."); Object result = proxy.invokeSuper(arg0, arg2); System.out.println("do sth after...."); return result; } public Object getProxyObject() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); // 设置父类 // 设置回调 enhancer.setCallback(this); // 在调用父类方法时,回调 this.intercept() // 创建代理对象 return enhancer.create(); }}public class CGProxyTest { public static void main(String[] args){ Object proxyedObject = new UserServiceImpl(); // 被代理的对象 CGProxy cgProxy = new CGProxy(proxyedObject); UserService proxyObject = (UserService) cgProxy.getProxyObject(); proxyObject.getUser(1); proxyObject.addUser(new User()); }}输出结果:do sth before....getUser from database.do sth after....do sth before....add user into database.do sth after....我们看到达到了同样的效果。它的原理是生成一个父类enhancer.setSuperclass(this.target.getClass())的子类enhancer.create(),然后对父类的方法进行拦截enhancer.setCallback(this). 对父类的方法进行覆盖,所以父类方法不能是final的。 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中的AOP的缺陷:因为Spring AOP是基于动态代理对象的,那么如果target中的方法不是被代理对象调用的,那么就不会织入切面代码。解决方法:将当前代理对象暴露出去,然后获取当前代理对象,在调用响应的方法。