1 Spring概述
企业级 Java 应用程序开发框架。
开源的 Java 平台。
轻量级的框架,其基础版本只有 2 MB 左右的大小。
扩展性好。
我的理解:Spring是一个解决bean(对象)的创建以及与其他对象之间依赖关系的一种框架(容器)。且可以和洽谈的一些框架进行整合使用,例如struts2、mybatis、hibernate等。
那么我们为什么需要这个东西?这个就要说到三成架构了,在古老的文明中,有model、control、view三层共同完成一个业务功能的持久化,view需要control的对象实例也就要new,control也需要model,这就很难受了,而且还不好管理,耦合度又高(牵一发而动全身,需要修改某一层的代码,其他层也必须改而且必须重新修改后编译后才行),这很没必要,所以Spring这个东东就来解救我们这些可怜的码农了。而且对于频繁的对象创建一方面消耗资源、影响程序性能另一方面这些创建的对象也有一个缓存过程,极度占用内存好吧(请看单例模式的好处)。
2、Spring IOC
SpringIOC(控制反转)其实就是利用Java反射机制+Xml解析实现的,用来创建bean的容器。以前创建对象需要new一下,现在不用了直接注入到IOC,然后再拿就可以了。
DI(依赖注入)就是用来管理IOC里面的bean之间的依赖关系一种容器,比如User这个类有name、email、address等属性,这些属性的赋值 就是这个东东干的。
2.1 简单来一个例子
添加依赖:
<!-- 引入Spring-AOP等相关Jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
新建Spring管理的类
@Data
public class UserEntity {
private String name;
private Integer age;
public UserEntity() {
System.out.println("默认构造函数UserEntity");
}
@Override
public String toString() {
return "UserEntity{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
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" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--id不能重复,否则报错-->
<bean id="userEntity1" class="com.spring3.model.UserEntity"/>
</beans>
测试:
@Test
public void test(){
ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
// UserEntity entity1 = (UserEntity) applicationContext.getBean("userEntity1");
// UserEntity entity12 = (UserEntity) applicationContext.getBean("userEntity1");
// entity1.setName("xiaomign");
// entity1.setAge(23);
// System.out.println(entity1);
}
取消注释执行后,“默认构造函数UserEntity”仅仅输出了一次,不难发现这个是单例模式。不取消注释执行时,“默认构造函数UserEntity”还是输出了一次,表名这个东西和你在哪用它无关,他不是你用他他才创建的,一看就是饿汉式,饿汉式就代表了什么呢?线程安全咯。。。。
2.2 作用域
singleton作用域:Spring bean默认就是singleton作用域也就是单例,Spring IOC容器只会创建该bean定义的唯一实例,该实例被存储到单例缓存(singleton Cache)中,所有针对该bean的后续请求和引用都将返回被缓存的对象实例。singleton作用域和GOF设计模式中的单例是完全不同的,单例设计模式表示一个ClassLoader中 只有一个class存在(classloader详解),而这里的singleton则表示一个容器对应一个bean,也就是说当一个bean被标识为singleton时 候,spring的IOC容器中只会存在一个该bean。
prototype作用域:每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)都会产生一个新的实例,相当于一个new的操作,Spring不会对prototype bean的生命周期负责,不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法,而对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。 清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责。(让Spring容器释放被singleton作用域bean占用资源的一种可行方式是,通过使用 bean的后置处理器,该处理器持有要被清除的bean的引用。)
request作用域:表示针对每一次http请求都会产生一个bean的新实例,同时该bean仅在该request内有效
session作用域:针对每一次http请求都会产生一个新的Bean,同时该bean仅在当前http session内有效。
2.3 IOC创建对象
1、无参构造函数(2.1)
2、有参构造函数
application.xml
<bean id="userEntity1" class="com.spring3.model.UserEntity"> <constructor-arg name="name" value="xiaoming"/> <constructor-arg name="age" value="123"/> </bean>
UserEntity.java
public UserEntity(String name, Integer age) { System.out.println("非默认构造函数UserEntity"); this.name = name; this.age = age;}
加载后发现输出“非默认构造函数UserEntity”代表调用有参狗造函数。
3、工厂创建对象
工厂类:
public class UserEntityFactory {
//实例工厂类创建对象
public UserEntity getInstance(){
UserEntity userEntity=new UserEntity();
return userEntity;
}
//静态工厂类创建对象
public static UserEntity getStaticInstance(){
UserEntity userEntity=new UserEntity();
return userEntity;
}
}
applicationContext.xml
<!--通过实例工厂--> <bean id="factory" class="com.spring3.model.UserEntityFactory"/> <bean id="userEntity2" factory-bean="factory" factory-method="getInstance"/> <!--静态工厂方法--> <bean id="userEntity3" class="com.spring3.model.UserEntityFactory" factory-method="getStaticInstance"/>
2.4 依赖注入
1、有参构造函数注入(2.3)
2、setter方法注入
<!-- setter方法注入--> <bean id="userEntity4" class="com.spring3.model.UserEntity"> <property name="name" value="xiaoming"/> <property name="age" value="12"/> </bean> <bean id="userDao" class="com.spring3.dao.UserEntityDao"> <property name="userEntity" ref="userEntity4"/> </bean>
userEntity4这个bean是先调用UserEntity的无参构造函数,然后根据property中的name比如age,然后去class根据反射机制看看UserEntity是否有一个setAge这样的函数,如果有就直接使用value传入直接调用即可。没有?肯定报错啦
而userDao和上面类似,只不过使用的userEntity4是已经创建好的而已,不过也是去找setUserEntity方法进行set注入的。
3、p名称空间
<bean id="userEntity5" class="com.spring3.model.UserEntity" p:name="xiaoming" p:age="12"/> <bean id="userDao2" class="com.spring3.dao.UserEntityDao" p:userEntity-ref="userEntity5"/>
直接给属性注入值
4、注解(主流)
注解可以简化Spring的IOC容器的配置。
步骤:
1、先引入context名称空间 xmlns:context="http://www.springframework.org/schema/context"
2、开启注解扫描<context:component-scan base-package="你要扫描的包的全路径,比如com.spring3.model"></context:component-scan>
3、使用注解,通过注解的方式把对象加入到IOC容器,这些注解默认的bean的id就是该类、属性等的第一个字母小写的,所以总会有冲突,一般在其后加一个()指定即可。
@Data
@Repository(value = "userEntityDao7")
public class UserEntityDao {
@Resource(name = "userEntity7")
private UserEntity userEntity;
public void add(){
System.out.println("UserEntityDao.add()");
System.out.println(userEntity);
}
}
@Component 指定把一个对象加入IOC容器
@Repository 作用同@Component; 在持久层使用
@Service 作用同@Component; 在业务逻辑层使用
@Controller 作用同@Component; 在控制层使用
@Resource 属性注入,默认按照名称userEntity注入:
@Resource
private UserEntity userEntity;
比如这个默认就去找bean id为userEntity3的bean:
@Resource(name = "userEntiy3")
private UserEntity userEntity;
如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。@Resource注解在字段上,且这个注解是属于J2EE的,减少了与spring的耦合。最重要的这样代码看起就比较优雅。
@Autowired 属性注入
与@Resource一样都是用来装配bean的,可以写在字段或者setter方法上。只是@Autowired默认是按照类型装配的(这个注解式Spring提供的 ),默认情况下,要求改实例(依赖)必须存在,如果要允许为Null,可以设置required为false。也可以结合@Qualifier注解进行名称的匹配上会用:
@Autowired(required = false)@Qualifier("userEntity7")
private UserEntity userEntity;
总结:
1) 使用注解,可以简化配置,且可以把对象加入IOC容器,及处理依赖关系(DI)
2) 注解可以和XML配置一起使用。
3 Spring AOP
3.1 代理模式概述
代理是一种设计模式,提供了对目标对象的另外一种访问方式。这样做的好处?屏蔽真目标对象,安全是必须的,延迟加载(主要用在对一些大的对象的加载时,加载其轻量的代理对象,需要时再加载目标类)等。
涉及对象:代理对象和目标对象
3.1.1静态代理
实现和目标对象一样的接口。
interface House{
void maifang();
}
class XiaoMing implements House{
@Override
public void maifang() {
System.out.println("小明,买房咯!!!!!");
}
}
class Proxy implements House{
//代理对象
private XiaoMing xiaoMing;
public Proxy(XiaoMing xiaoMing){
this.xiaoMing=xiaoMing;
}
@Override
public void maifang() {
System.out.println("中介,开始替你买房");
xiaoMing.maifang();
System.out.println("中介,替你买房结束");
}
}
public class StaticHouse {
public static void main(String[] args) {
House house = new Proxy(new XiaoMing());
house.maifang();
}
做到在不修改目标对象的功能前提下,对目标对象功能扩展。
缺点:
1、因为代理对象需要和目标对象实现一样的接口,所以会有很多代理类
2、一旦增加接口方法,目标对象和代理对象都要维护
怎么办呢?据说代理工厂,不过我不清楚,有时间了解后再写上来
3.1.2 动态代理
与静态代理区别:
1、代理对象不需要实现接口
2、代理对象的生成,是利用JDKAPI,动态的在内存中构建代理对象
3.1.2.1 jdk代理:
|-- Proxy
static Object newProxyInstance(
ClassLoader loader, 指定当前目标对象使用类加载器
Class<?>[] interfaces, 目标对象实现的接口的类型
InvocationHandler h 事件处理器
)
interface House{
void maifang();
}
class XiaoMing implements House{
@Override
public void maifang() {
System.out.println("小明,买房咯!!!!!");
}
}
//jdk动态代理
class JdkProxy implements InvocationHandler{
private Object object;
public JdkProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中介,开始替你买房");
Object invoke = method.invoke(object, args);
System.out.println("中介,替你买房结束");
return invoke;
}
}
public class StaticHouse {
public static void main(String[] args) {
XiaoMing xiaoMing = new XiaoMing();
JdkProxy jdkProxy = new JdkProxy(xiaoMing);
House house = (House) Proxy.newProxyInstance(xiaoMing.getClass().getClassLoader(), xiaoMing.getClass().getInterfaces(), jdkProxy);
house.maifang();
}
}
总结:代理对象可以不实现接口,但是目标对象一定要实现接口。否则不能使用动态代理
如果有一个目标对象,想要功能扩展,但是目标对象没有实现接口,怎样扩展呢?
以子类的方式 class subclass extends ..{}-----------cglib
3.1.2.2 Cglib代理
也叫子类代理,在内存中构建一个子类对象从而实现目标对象的功能扩展
jdk限制:使用动态代理的对象必须实现一个或者多个接口,如果过想代理没有实现接口的类,就只能使用CGLIB代理
CGLIB:一个强大的高性能的代码生成包,可以在运行期扩展Java类与实现Java接口。。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
底层是通过一个小而快的字节码处理框架ASM,来转换字节码生成新的类。不鼓励直接使用ASM,因为他要你对jvm内部结构包括class文件的格式和指令集都很熟悉。
1、引入cglib-jar文件,Spring自带有
2、引入后。就可以在内存中动态构建子类
3、代理的类不能为final,否则报错
4、目标对象的方法如果为final/static,那么就不会被拦截。即不会执行目标对象额外的业务方法。
在Spring的AOP编程中,
如果加入容器的目标对象有实现接口,用JDK代理;
如果目标对象没有实现接口,用Cglib代理;
public class Cglib implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("我是买房中介 , 开始监听你买房了....");
Object invokeSuper = methodProxy.invokeSuper(o, args);
System.out.println("我是买房中介 , 开结束你买房了....");
return invokeSuper;
}
}
class Test22222 {
public static void main(String[] args) {
Cglib cglib = new Cglib();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(XiaoMing.class);
enhancer.setCallback(cglib);
Hose hose = (Hose) enhancer.create();
hose.mai();
}
}
3.1.3 AOP
对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要用在:日志记录、事务处理、权限控制、异常处理、性能统计等方面。
说白了就是将日志、事物等这些代码从业务逻辑中划分出来,通过对这些行为的分离,我们希望可以将他们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP就是让关注点代码和业务逻辑代码分离。这样理解就是很多功能都有重复的代码,AOP就是将这些代码抽取然后再在运行的时候将这些代码动态的植入到这些功能中。
关注点:重复代码
切面:关注点形成的类
切入点:执行目标对象的方法,可以通过切入点表达式,指定拦截哪些类的哪些方法; 给指定的类在运行的时候植入切面类代码。
1、注解方式
<!--开启AOP注解--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
@Aspect 指定一个类为切面类
@Pointcut("execution(* com.itmayiedu.service.UserService.add(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知: 目标方法之前执行
@After("pointCut_()") 后置通知:目标方法之后执行(始终执行)
@AfterReturning("pointCut_()") 返回后通知: 执行方法结束前执行(异常不执行)
@AfterThrowing("pointCut_()") 异常通知: 出现异常时候执行
@Around("pointCut_()") 环绕通知: 环绕目标方法执行
//声明切面
@Aspect
//注入到Spring容器
@Component
public class Aop1 {
@Before("execution(* com.spring4.dao.UserEntityDao.add(..))")
public void begin(){
System.out.println("前置通知");
}
@After("execution(* com.spring4.dao.UserEntityDao.add(..))")
public void after(){
System.out.println("后置通知");
}
@AfterReturning("execution(* com.spring4.dao.UserEntityDao.add(..))")
public void afterRunning(){
System.out.println("运行通知");
}
@AfterThrowing("execution(* com.spring4.dao.UserEntityDao.add(..))")
public void afterThrowing(){
System.out.println("异常通知");
}
@Around("execution(* com.spring4.dao.UserEntityDao.add(..))")
public void aroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("我是环绕通知-前");
proceedingJoinPoint.proceed();
System.out.println("我是环绕通知-后");
}
}
UserEntityDao userEntityDao = (UserEntityDao) applicationContext.getBean("userEntityDao7");
userEntityDao.add();
这样即可看到效果。
2、xml方式
<!-- 切面类 -->
<bean id="aop2" class="com.spring4.aop.Aop2"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定义一个切入点表达式: 拦截哪些方法 -->
<aop:pointcut expression="execution(* com.spring4.dao.UserEntityDao.add(..))" id="pt"/>
<!-- 切面 -->
<aop:aspect ref="aop2">
<!-- 环绕通知 -->
<aop:around method="aroud" pointcut-ref="pt"/>
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知: -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>